github wonderingStars/foxsdr v0.99.74
FoxSDR 0.99.74

3 hours ago

FoxSDR 0.99.74 puts GAIN and SQUELCH dials, each with an AUTO key, beside VOLUME on the top bar; folds the STATUS column and each decoder's settings away; renames the rail's VIEW bank TOOLS and moves the Display settings to the top of SYSTEM, folded; lets the ADS-B map list only the aircraft in view and draws an aircraft with no altitude in grey; accepts the address of a third-party plugin site in the plugin store; gives a plugin setting that names a file a BROWSE FILES key; and lets a plugin you have granted and armed send transmit audio through FoxSDR. Switching automatic gain off now gives the radio its manual gains back and reads them again, so the Source sliders and the GAIN dial show what the radio holds (on a B200 the radio fell about 35 dB while the readback still said 76.0 dB). POCSAG 1.0.3 now sets up the receiver itself, defaults to text, offers numeric and function-code modes, detects baud rate and polarity, and opens its settings and the received messages together. Opening Language & country no longer freezes the window while FoxSDR looks for Chinese, Japanese and Korean fonts. The new interface words are in all 33 languages. The plugin catalogue also gained five plugins on 12 October 2026, Packet TNC, Radio Email, Propagation Tools (with VOACAP built in), Solar System View and DAB / DAB+ Radio (whose DAB+ sound needs an ffmpeg you install yourself; the plugin ships no AAC decoder), each marked EXPERIMENTAL and available for Windows only. The plugin ABI is still 3; the host API level goes from 1 to 2.

Still not fixed, and known: no real radio has been driven by the new dials, only a fake one, and automatic squelch has been measured on synthetic noise, not on air, so a continuous broadcast tuned in from a quiet frequency is still taken for the noise floor and automatic squelch will close on it; the B200 itself has not been tried since the gain readback fix; nothing has transmitted, because no Pluto was attached and no RF was radiated, and the only module that has ever asked for the transmitter is a test fixture; the ADS-B on-screen list has not been seen with real aircraft, nor the grey over real map tiles; nothing was fetched from a real plugin site, and a plugin module with no install record (hand-copied, or its record lost) still competes by version as hand-copied modules always have; nobody has looked at the folded Display on screen; the Linux builds, including the file dialog that uses zenity or kdialog, are compiled by CI only and have not been run; the language list was tested against a simulated slow disk only, and a user whose Windows language is Chinese, Japanese or Korean while the interface is in another language still waits for the font search once, on that section's first frame; the specific POCSAG transmission originally reported as missing was not available for comparison; the new words in thirty-three languages are machine translations that nobody has read; and one start-up test (test_startup_state) lost its captured output in one whole-suite run and again in 1 of 44 runs on its own, for a reason that is not established, although it passed in the last whole-suite run.


SHA-256 of foxsdr-setup-0.99.74.exe:
3fad3bc9dd6b824b36ffe1674b9e5376ebd615eba4466a6f1c70dce9c86b401f

SHA-256 of FoxSDR-0.99.74-x86_64.AppImage:
a841ae0cb18bffe46b32e97ec597d0e9a338fbb82d6248697896591b0e9723ef

SHA-256 of foxsdr-0.99.74-linux-x64.tar.gz:
784adb11b59d5003018ad01ca1427218cf8f0d0a22626d969cb8e53fef5a4475

SHA-256 of FoxSDR-0.99.74-aarch64.AppImage:
8cf5b26c9d1899f4ecec9243de9e1f3fe99ec9e35eae375a99d05d3f5b45f5ea

SHA-256 of foxsdr-0.99.74-linux-arm64.tar.gz:
833f2db1243fa9b25db549519d6cf7e4b537b1b7a4f03c5eb2ad2bb6c473b2c0

POCSAG

POCSAG opens its settings and received messages together, and configures the receiver automatically. With POCSAG 1.0.3 fitted, opening or starting the plugin selects NFM, its declared bandwidth and flat audio while keeping a manually tuned paging frequency. The plugin defaults to text, offers numeric and function-code modes on its own settings page, and detects baud rate and polarity automatically. Its UK preset tunes 153.350 MHz with a 20 kHz bandwidth. Explicit preset buttons still tune their named frequencies; existing plugins keep their behavior.

Verified on Windows with 65 decoder checks, nine host test suites, a known text recording through the actual application, and live message output from a USRP B200 at 153.350 MHz. The specific transmission originally reported as missing was not available for comparison; receive windows without traffic produced no message rows. RF gain was observed rather than optimized by this change.

The language list

Opening Language & country no longer freezes the window while it looks for Chinese, Japanese and Korean fonts. To list each language by its own name (日本語, 한국어) the window has to know which of those languages this computer can draw, and to find out it read Microsoft YaHei, JhengHei, Yu Gothic and Malgun Gothic from the Windows font folder (14 to 21 MB each) inside one frame, one byte at a time, and then read the ones it used a second time to put them in the text atlas. On a laptop with a slow disk that was a frozen window of many seconds, and the freeze monitor caught it twice in the field: 0.99.70 (signature CE8EBCD986C97CEB, stopped reading a font file) and 0.99.71 (signature D28A155E1A128889, a 13.6 second frame in the frame-start step, 32 plugins fitted and the audio starving). A font file is now read in one call (YaHei, 19.7 MB: 85 to 89 ms before, 11 ms after, on the development computer with the file already in the system cache), each file is read once, and the search for the first usable font of each of the four scripts runs on a thread of its own, started when the section opens. With every font read made to take 400 ms the call that opens the list returned in 0.1 ms, where the same call took 2.9 s before. The trade is one or two frames of boxes: until the search finishes (39 ms in a run of the real application on the development computer, longer on a slow disk) the native names are drawn as boxes, the rows can already be picked, and a language the computer turns out not to be able to draw is refused afterwards with the same message as before. Two things remain. The thread that draws the window still works out which script each language needs (6 to 8 ms for the 34 listed here). And a user whose Windows language, or the language of the country chosen in this section, is Chinese, Japanese or Korean while the interface is in another language waits for the search once, on the section's first frame, for about the length of four of those file reads, because the two lines that ask whether that language can be drawn ask the exact question (668 ms and 674 ms measured with every read made to take 150 ms; the freeze threshold is 5 s, so it is reached only if a disk takes about a second for each 20 MB file); the previous code read the same four files on the window thread in the next frame, byte by byte and twice over, and those two lines are not yet moved. The search keeps the file it found for each script only until the list's names are in the text atlas: the files it merely looked at (JhengHei and Yu Gothic, 35 MB, because YaHei already draws their names) are then let go, so the memory held after opening the list is what it always was, and choosing Japanese or Traditional Chinese afterwards reads its file once more, in one call.

Verification details

POCSAG 1.0.3 or newer can configure reception in FoxSDR 0.99.74 or newer when opened or started without moving away
from a manually tuned paging channel. Its optional preset flags apply the
declared demodulator and bandwidth even when the frequency already matches a
preset, and request flat audio by switching receiver de-emphasis Off. Explicit
preset buttons still tune their named frequencies; other plugins retain their
existing behavior.

An additional opt-in preset flag opens the shared Decoder output window for a
text decoder alongside its own settings panel. POCSAG uses it so the settings
page and received message text are both visible. Plugins without text output
ignore the flag; existing plugins that omit it retain their normal windows.

The plugin ABI remains 3. Synthetic decoder and isolated application checks
establish the software behavior; live reception depends on the signal available
to the radio.

Windows verification used the actual POCSAG 1.0.3 DLL: 65 decoder checks and
nine relevant host test suites passed. With the reception setup and the
message-window change each removed, the checks written for them failed; with
them restored the builds passed. A separately generated FM IQ recording displayed POCSAG TEXT TEST
in the real application's Decoder output window with the settings page also
visible. Its saved configuration read back 153.350 MHz, NFM, 20 kHz bandwidth
and de-emphasis Off. Tests used isolated application profiles.

One ten-second USRP B200 capture at 153.350 MHz also produced coherent
alphanumeric output at 2400 bit/s when replayed through the final application
and plugin. Another capture produced no message rows. Displayed row counts
include repeated playback and are not counts of distinct transmissions. The
specific transmission reported as missing was not available for comparison.

Direct Soapy/UHD reception on the same B200 also ran successfully with
SOAPY_SDR_PLUGIN_PATH both cleared and set. The clear-variable run produced
live message output; the later set-variable run produced no rows during its
receive window. Both used 2 MS/s, TX/RX and a displayed PGA gain of 30 dB,
without stream-error or overflow entries. Different receive windows do not
establish a cause for the difference in message counts. RF gain was observed,
not optimized by this change. These verification runs used isolated profiles;
the saved user profile was not replaced.

The language list's font search is covered by test_font_scan, which makes every
font read slow on purpose and checks that each system font file is opened once
(and that choosing Japanese afterwards opens its file a second time, and no other
file, with nothing held in memory beyond the atlas in between),
that asking for the names returns at once while the files are being read, that
the three-way answer (Yes, No, Checking) agrees with the exact one when the search
is done, that the one-call file read returns the bytes a byte-by-byte read does,
that the search finds the faces the on-demand walk finds, and that a search still
running when the program exits is joined rather than terminated. Against the
previous code it failed 9 checks, among them the 2.9 s return and the two reads of
msyh.ttc and malgun.ttf; with the change it passes, and removing each of the
behaviours it is named after (waiting in applyPending, the kept bytes, the
release of the kept files the names did not use, the non-blocking answer, the
exit-time join, the full-length read, the search's first candidate) makes it fail
again. The 350-test suite passed on Windows before the release of the unused kept
files was added; after it, the eight font, i18n and CJK tests were run again and
pass. A run of the
real application with the section open and a Japanese country chosen started the
search, adopted its answer 39 ms later, merged the names' faces on the next frame
and shut down cleanly. Not verified: a slow disk (only a simulated one), and the
Linux build, which only CI compiles.

Features and fixes, 2026-10-12

Seven changes were asked for on 2026-10-12 (five by testers; the plugin sites and the file-path key by the owner). They are described below together with the gain readback fix that the live check of the dials found and the transmit grant.

The top bar now carries GAIN, SQUELCH and VOLUME dials in the order the signal goes through the system, with an AUTO key under the first two: the gain dial sets the radio's first gain stage and its key is the Auto gain switch, and the squelch dial sets the squelch threshold while its key keeps it 8 dB above the measured noise floor - and while the Airband monitor is listening, both act on the monitor's own squelch instead. The dials are one size, answer a drag or the wheel, and print their setting under them. GAIN reads N/A with no radio and AUTO while automatic gain is on, and is dead in both; its key is dark, with a tooltip saying why, on a radio that has no automatic gain. On the Airband monitor the dial reads and sets its squelch (-110 to 0 dB, the Airband section's own slider) and leaves the receiver's alone, with a line in its tooltip saying so; AUTO there is the section's "Above the noise" rule, 8 dB over the median channel level, re-applied every quarter second. Everywhere else automatic squelch measures the quietest the channel has been over the last ten seconds and re-sets the threshold every quarter second, and it does not count what it reads while the squelch is open, so a carrier does not teach it the floor and close the squelch on itself. A hand on the dial, either squelch slider, the squelch keys, the Above the noise key or the web control puts it back to manual, and it is remembered across restarts (squelchAuto in config.json, one setting for both squelches).

Making room costs the deck 8% of its size at the window a fresh install opens with: the cluster is 111 units wider, and the SAMPLE RATE and VOLUME meters are kept on the bar by drawing the deck at 0.92 there (full size from a bar of 1313; the enlarged counters at 0.77). The smallest scale falls from 0.62 to 0.57 so the three dials still fit the narrowest window, and the bias tee key keeps a 12-pixel floor. A one-decoder mute banner sits above the meters, not in the middle, at 1600 x 1000. Two geometry faults the new widths exposed are fixed: the mute banner is measured against the meters where they are drawn (scaled with the deck), and the "do the meters fit" rule has the float tolerance its twin always had.

Verified on Windows with a fake radio behind the real window: the application test starts the application once for each scenario and presses and twists the dials through its own input script, aimed from the interface census, 218 checks. The four new parts are on the deck at 1600 x 1000 and 1280 x 720 in the right order and clear of every other part, with both meters still on the bar at 1280 x 720; a press of the gain key switches the radio's automatic gain on and a second press off, a radio without automatic gain is asked for nothing, a half turn of the gain dial from 10 dB asks for 43.3 dB, and a 34-frame hand's turn asks the radio 6 times and ends on 41.6 (41.5 expected). With the receiver running on white noise at -50 and -30 dB the saved squelch threshold is -53.5 and -33.5, 20.0 apart; with the noise at -50 dB for four seconds and then a 30 dB louder carrier held for thirteen it is still -53.6, where a three-second ungated floor gave -23.7. With the Airband monitor listening on two ticked rows, a quarter turn of the dial moves the monitor's squelch from -55 to -18.3 and leaves the receiver's at -100; AUTO puts it at -65 and -44 for noise at -50 and -30 dB (about 20 apart) and the receiver's untouched; the dial, the Airband slider and the Above the noise key each turn AUTO off; stopped, the dial is the receiver's again. The noise-floor tracker has 74 checks of its own, including a 20-second carrier after the floor is learnt, a first estimate formed with the gate open, and a rise under the threshold followed within the window, and each behaviour was removed in turn to see its test fail. The Airband application test (316 checks) passes after the section gained census rects: its runs lasted 4.4 to 4.9 s against 2.0 s of holds, so the display was pacing them. Seven new strings and one more for the Airband tooltip are translated in all 33 catalogues, so the i18n test passes.

Not verified: no real radio (RTL-SDR, Airspy or B200) was driven by the new dials, only the fake, and automatic squelch and the Airband rule were measured on synthetic noise, not on air. A noise rise past the threshold opens the squelch and holds readings back, so a squelch open for thirty seconds without a break is taken as the floor having risen and is learnt (a safety valve added so the squelch cannot sit open on a noisier antenna for ever); a carrier that holds the channel longer than that, or a continuous broadcast tuned in from a quiet frequency, is still taken for the floor and automatic squelch will close on it.

The STATUS column on the right of the receiver view folds away, the way the FUNCTION SELECT rail does. A >> key at the left end of the STATUS title row (the end nearest the spectrum) folds the column to a 34-pixel strip, and a << key on the strip brings it back; the spectrum and waterfall take the width that frees, remembered in the configuration file as statusCollapsed. The tester asked for it from the point of view of whether the status information is useful to someone who is looking at the spectrum: "Just like you added the << and >> to expand and collapse the Function Select panel on the left side, can you do this for the right side Status panel?" The REQUEST A FEATURE and REPORT A BUG / DISLIKE keys live in that column, so they are not shown while it is folded and come back when it is opened.

Verified in the real application on the receiver view at 1366 x 768 and 1920 x 1080: the open column is 230 pixels wide with the key at its left end; one scripted press leaves a 34-pixel strip, the receiver view grows right by exactly 196 pixels (230 less 34) and keeps its left edge, and the saved configuration reads statusCollapsed: true; a folded configuration starts folded from the first frame; one press of the strip's key reopens the column and puts the view back where it was. A window that is too narrow for the full column (1100 x 700) still drops the open column, as before, but shows the folded strip, because the strip needs 34 pixels beside the spectrum's 520 rather than 230. The STATUS title was measured against the key in all 34 languages at three interface sizes (102 titles, none under the key). The rail's flag is separate and does not follow it. With the toggle removed the new unit test and the application test fail; with the field removed from the debounced save's comparison the unit test fails. A rendered screenshot of the open and folded states was read by eye.

Two new strings are for translation ("Show the Status panel" and "Fold the Status panel away to give the spectrum its width"), and both are translated in all 33 catalogues, so the language-catalogue test passes. The patch view and scope mode have no status column and are unchanged. Not verified: a real mouse press on the key (the scripted press goes through the application's own input queue), and any run at an interface size other than 100%.

The rail's VIEW bank is now called TOOLS, the Display settings have moved to the top of the SYSTEM bank, and Display starts folded and stays the way you left it. A tester who used the Airband feature often had to collapse the Display section every time FoxSDR started, and pointed out that the scopes, the scanner and the airband monitor are tools rather than view options. TOOLS (still the third key, still F3) now holds the demod scope, the radar scope, bookmarks, the scanner and the airband monitor; Display (interface size, theme, the spectrum's dB range, the band plan, the counter and the aircraft icons) is the first section of SYSTEM. It is folded on a fresh install and for every existing configuration, and the configuration file records whether you left it open (displayOpen), so opening it once keeps it open. A key binding saved for this bank under its old id (bankView) still works.

Verified on Windows with the real application: a fresh configuration draws the Display header first in SYSTEM with its body undrawn and saves displayOpen: false; a saved displayOpen: true draws the body from the first frame and keeps it; pressing the header opens it and the file says true, pressing it again folds it and the file says false; the TOOLS bank no longer has a Display section and still has every other section it had, and a saved open state survives a session spent in that bank. With the saved state removed, or the section open by default again, those checks fail, and the bank-name and save-debounce checks fail when the word VIEW or the field is put back (34 application checks, 3,214 configuration checks, 73 bank checks, 4,104 key-binding checks). With this change alone the full suite of 350 passed except the translation check, which then lacked the two new words; the 33 catalogues now carry them and it passes.

All 33 catalogues now carry "TOOLS" and "Bank: TOOLS" and no longer carry "VIEW" and "Bank: VIEW". The bank-key width check (every language's five bank words whole in a key that holds 68.4 px at the smallest lettering) cut the first Russian and Spanish words, ИНСТРУМЕНТЫ and HERRAMIENTAS at 72.1 px, so those two languages read СЕРВИС and HERRAM.; the other 31 passed as translated. Not verified: nobody has looked at the folded Display on screen; it was checked through the interface census and the saved file. On a fresh install the interface-size row is one click deeper (Ctrl+= and Ctrl+- do not need it open).

Each decoder's settings now sit in a folded "Plugin settings" line on the DECODE bank, so scrolling down through the decoders no longer means scrolling past every plugin's fields. In the "Turn on and off plugins" section, a plugin that has a settings form of its own shows one closed line under its keys and its Mute audio while running box; a click opens the form and a second click folds it away. It starts folded each time FoxSDR runs, as the same form already does in the plugin's own window, and the values typed in it are saved as before, open or not. The fold is kept against the plugin's id (the module file name without its version) and no longer against the plugin's place in the list, so installing, updating or removing another plugin does not move an open fold onto a different plugin's row.

Verified on Windows with a new application test, test_decoder_settings_fold_app, which runs the real application in one viewport with the settings fixture plugin fitted and the DECODE bank up, and reads the interface census. As the application opens, the settings line is folded and the form is not in the layout; one click opens it and the row below the plugin moves down 139 px, the height of the three-field form (193 px in the final build, where the fixture has gained a fourth, path field); a second click puts that row back exactly where it was folded (887 px both times). Before the change the same run reported no folded line, and the form was drawn inline with that row 116 px lower (1003 px); with the line made open by default the test fails on the default and on the height, and with it folded again the test passes. The neighbouring suites that draw this rail or its forms, test_decoder_window_app, test_plugin_inuse_app, test_plugin_settings_ui, test_running_view and test_i18n, pass. No new interface text: "Plugin settings" is the line the plugin windows already use, so the translation catalogues are unchanged.

That a fold stays with its plugin across a rescan is by construction (the row's id is the plugin's run id) and is not exercised by a test, because no bounded run changes the plugin list mid-way. Only plugins that declare a settings form have the line; plugins without one are unchanged. The test fits the settings fixture only; the Demod Analyzer and the FT8 plugin themselves were not fitted in any check.

The ADS-B map can list only the aircraft you can see, and an aircraft with no altitude is now grey instead of red. A new On screen only tick beside the target count limits the aircraft list to the part of the map currently in view, so zoomed in on an airfield or an air show the list follows the picture instead of naming everything the receiver has heard; the count then reads "3 of 41 targets", an empty view says "No targets on screen.", and the setting is remembered between runs. An aircraft that reports no altitude is drawn in a mid grey with the hollow marker it already had, where it used to take the aircraft kind red that looked like the lowest altitude band (below 1,000 ft, orange). The altitude key on the map gains a "no altitude" row at its foot, shown only when an aircraft is actually drawn grey. Ships, APRS stations and satellites keep their own colours, an emergency squawk still outranks everything, and a squawking aircraft with no altitude stays in the emergency hue.

Both changes answer the tester's request of 2026-10-12. What is on screen is decided by the same projection arithmetic the map draws with, edge to edge with no margin: a point exactly on the edge counts, a point a hair outside does not, a view centred on 179.5 E takes in a target at 179.8 W, and Mercator and equirectangular views are each answered in their own projection (52.4 N in a 4-degree view centred on 51 N is 20 px from the top of an equirectangular map and 152 px above the top of a Mercator one). The grey is neutral 150 on all three channels, luma 150 in the middle of the 90 to 210 window the altitude palette is held to, and 42.1 away in CIE Lab from its nearest altitude band, further than the closest pair of bands are from each other (38.8); black was refused because it vanishes on the dark map. The list's rail chip keeps counting every live target on the page.

Verified with the track-metrics, map-view and configuration suites, including a draw-data check that an aircraft with no altitude draws its callsign in grey and in nothing red while a ship, a station, a satellite and a squawking aircraft keep their colours, and that the legend grows by exactly one row step (15 px at the test font) only when a grey aircraft is drawn alongside banded ones. The checks for the grey rule, the legend row, the on-screen test and the saved setting were each shown to fail when that behaviour was removed. Not yet seen in the running application: the toolbar tick and the filtered list are compile-checked and share their counting code with the list heading, but no ADS-B plugin or aircraft was available to drive them, and the grey has not been looked at over real OpenStreetMap tiles. The radar scope keeps its own, paler grey for the same case. The switch applies to every map page and to the aircraft list only; the satellites window has its own list and is unchanged.

The plugin store takes the address of a third-party plugin site: press SITES in the store's top bar, paste the address of someone else's index.json, tick the box that says FoxSDR has not reviewed it, and press ADD, and the store lists, installs and updates that site's plugins beside the official ones, each marked THIRD PARTY. Up to eight sites are kept in the configuration file (pluginSites). A site's plugin carries the THIRD PARTY badge on its card and "from " on its page, and always shows the notice box ("This plugin comes from , not from the FoxSDR catalogue. FoxSDR has not reviewed it.", then the maker's own notice if there is one) with the tick that gates the key. Under the top bar each site has a status line: how many plugins it listed, why it could not be read (in amber, the reason word for word), and what it hid - "N hidden: same id as a plugin already listed" (the FoxSDR catalogue always wins an id: a site can never replace, update or retire an official plugin) and "N hidden: files not on the site's own host" (a site's files and pictures must be served from the host of its index.json). A site's plugin is known by its site as well as its id, so an official plugin and a site's plugin of the same id can never update, retire or stand in for each other; GET EVERYTHING, UPDATE ALL and the web page leave a site's plugins out; and a site's plugin may not overwrite a file that belongs to another plugin. The loader holds the same line: a module installed from a site never takes a declared name from an official plugin, whatever the versions - the official module runs, and the site's is listed in the Fitted modules window as not loaded with "Not loaded: it declares the same name as the official plugin ..." (before this, the higher version won, so a site's module declaring an official plugin's name with a higher version switched the official one off). Every download still goes through the same gate as any plugin: https only, the exact ABI, the sha256 the site published, the byte cap, no redirect to another host. REMOVE forgets a site and deletes its kept copy; what was installed from it stays installed, the Fitted modules window says it came from a site that was removed, and it gets no more updates. docs/THIRD-PARTY-SITES.md tells a plugin author how to host a site, including the same-host rule and the declared-name rule.

Verified on Windows with the project's own suites: 1,351 checks in the plugin repository suite (the merge, the host lock, the (source, id) joins, the manifest, the file rule, the site index fetch), 283 in the kept-copy suite, 3,222 in the configuration suite, 379 in the store view suite, 207 in the real-AppWindow catalogue suite, 627 in the health suite, 221 in the store application suite, which drives the real cascade.exe against a second local index as a site (the badge on its card and on no official card, the clashing id hidden and counted, the page's notice and greyed key, the SITES panel, ADD refused until the consent is ticked, ADD writing the address to the saved config, REMOVE deleting the kept copy), and, for the loader rule, 365 checks in the plugin host suite and 48 in a new suite that scans two real builds of one probe module through the real host and the real application with install records saying which is whose (official 1.0.0 beside a site's 1.1.0: 1.0.0 runs, 1.1.0 is unmapped and says why; two site modules: 1.1.0 runs; no records: 1.1.0 runs). Each of 31 deliberate breakages - the merge keeping id collisions, the host lock dropped at the merge and in install(), each join by id alone, the file rule removed, the badge not drawn, ADD working without the consent, the loader rule removed, the origins not stamped or not handed to the host, and the rest - made the suite named for it fail, against a passing baseline; with the loader rule removed the host suites fail 15 checks of 365 and 15 of 45. The 35 plugin host, runner, store, catalogue, config and health suites pass; test_i18n passes now that the 33 catalogues carry the 28 new or reworded strings (the rail tooltip gains one sentence) and have dropped the old tooltip. Nothing was fetched from a real site: the transport is the test stand-in and the timeouts (4 s to connect, 8 s to receive, 1 MiB) are read off the code, not measured against a dead host; the layout was checked by rectangles, not by eye; Linux builds only in CI.

The store's top-bar width check (search field, both tabs, CHECK AGAIN and SITES in 900 px, in every language) cut the first Polish and Swedish words for SITES, which needed 907 and 915 px, so Polish reads WWW and Swedish SAJTER; the other 31 passed as translated. What remains: the loader rule rests on the install records, so a module with no record (hand-copied, or its record lost) still competes by version as every hand-copied module always has.

A plugin setting that names a file now has a BROWSE FILES key beside its box, so the path to a program such as nrsc5 no longer has to be typed or pasted. A plugin declares the setting as the new CASCADE_SETTING_PATH kind (3) in its settings form; the host draws the usual text box, shortened by the key, and the key opens the system's file dialog - Windows' own open-file dialog, modal to the FoxSDR window, opening in the folder of the path already there and offering programs first when the plugin's placeholder ends in .exe; on Linux zenity, else kdialog, and with neither installed the box stands alone with no key. The chosen path is stored exactly as a typed one is, so the plugin sees its settings change and reads the path with settings_get; cancelling changes nothing, and the host never opens or checks the file. A host older than 0.99.74 skips such a field, so a plugin using it must still work with the value unset. The plugin ABI is still 3; plugin_abi.h gains one line, which the plugin repository's copy must receive too. It was asked for by the owner on 2026-10-12: "have you made it easy for the user to add the nrsc5 path to the sdr software".

Verified on Windows: the real settings fixture module's form now has a path field and its value reaches the plugin through settings_get with settings_seq moved (322 checks in test_plugin_settings_ui, which also presses the drawn key with a real mouse click, covers a chosen path, a cancel, a path over the field's limit and one with a control character); the real application with the fixture fitted draws exactly one key once the plugin's "Plugin settings" line on the DECODE bank is opened - and none while it is closed - a scripted press saves the chosen path under the plugin's name in the config, a cancelled press and a press that never completes save nothing, and the path and key survive a relaunch (28 checks in test_plugin_pathpick_app, which reads the closed line's rectangle from the interface census and clicks it open ahead of every press; the dialog's answer is supplied by the bounded-run seam FOXSDR_FILE_PICK; without the click it fails 3 checks and says no key was found); and the real Windows dialog, driven by a second thread, shows the requested title owned by the window it was given, returns no path when closed, returns the full UTF-8 path of a file with an accented name when only the name is typed (which also proves the start folder), and opens when the start folder does not exist (52 checks in test_file_pick). Each behaviour was removed in turn and the test named after it then failed. A one-off run (not repeated on the final build) left the dialog open in the real application for about five and a half minutes: no hang entry in the diagnostic log, no slow frame, nothing saved after Cancel.

The key is labelled BROWSE FILES, not BROWSE, because BROWSE already means the plugin store's Browse tab in the catalogues (ENTDECKEN in German, EXPLORAR in Spanish and Portuguese, КАТАЛОГ in Russian and Ukrainian); it and the dialog's three words ("Choose a file", "Programs (.exe)", "All files (.*)") are translated in all 33 catalogues. Not verified: the Linux zenity/kdialog path compiles clean against stubbed declarations but has not run (its test runs only on the Linux CI); and a file chosen through the real dialog inside a single run of the full application is covered in two pieces (the dialog, then the seam's commit), not end to end.

Switching automatic gain off now gives the radio its manual gains back and reads them again, so the Source section's sliders and the GAIN dial show what the radio really holds. The live check of the dials on the B200 found it, and it was already there before them: after the Auto gain switch was turned off the radio fell about 35 dB (the in-channel level went from -40.7 to -75.4 dBFS) while the readback and the dial still said 76.0 dB, and setting 76.0 again put it right. The window read the gains back after the switch for an Airspy only. Now every radio that accepts the switch is handled in changeAutoGain, which the Auto gain box, the AUTO key and the web control all use. Switching ON keeps the numbers the user had set and reads every stage back, so the dial shows what the radio reports while it gains itself. Switching OFF gives every stage its kept value again (for a radio that was opened already on automatic gain, the value the open read), drops without a word a step the radio turns down, as the dial's own steps are, reads every stage back, and writes one diagnostic line, source: manual gain restored after auto gain off. A switch that changes nothing leaves the gains alone, and the box still shows what the radio accepted rather than what its autoGain() says, as it always has for a radio that is not an Airspy.

Verified on Windows with a fake radio that does what the B200 does, falling to the bottom of every stage when it leaves automatic gain, opened both as a Soapy device the B200's way and as a native one (227 checks in test_autogain_app). With LNA 33 and PGA 21 set, ON and then OFF leaves the radio at 33 and 21 and the mirror equal to the radio, after one call per stage and one log line, and again on a second round; a radio opened already on automatic gain gets back the 10 and 12 its open read; a radio that reports 17 dB on every stage while it gains itself shows 17 after ON and still gets 33 and 21 back after OFF; a PGA that refuses its step is dropped with no error line while the LNA comes back and the mirror says 0 for the PGA; a refused ON touches no gain; a radio with no stages logs nothing. On the unfixed code the same checks fail 16 of 208, each OFF step reading "the radio holds LNA 0 PGA 0, the mirror says 33 21", which is the finding itself; six deliberate breakages (the re-apply removed, the readback on ON removed, the kept values ignored, the readback after the re-apply removed, the no-change early return removed, the box taking autoGain()'s word) made it fail with 15, 1, 1, 2, 11 and 1 failed checks. Not verified: the B200 itself after the fix, because the fake reproduces what the live check measured and not the radio; what a B200 reports while its AGC is on is not known, and the kept values make the answer irrelevant to what comes back. An Airspy goes through the same re-apply and readback (its AGC switches are part of its gain mode, so the box follows the radio's own readback there); no Airspy was attached.

A plugin can now send transmit audio through FoxSDR, only to a radio you have opened, only if you granted that module TRANSMIT and armed the TRANSMIT page for it, and never for longer than the latch's 60 seconds. The new capability CASCADE_CAP_TX_AUDIO (host API level 2) hands the host 48 kHz mono audio through a pull() function, the way CASCADE_CAP_AUDIO_OUT hands audio the other way. A module that wants to transmit shows "Can transmit through FoxSDR" in the plugin store and on its plate. Once it has asked, its page in Fitted modules offers GRANT TRANSMIT (per module, off by default, saved by file name like the other two grants), and the TRANSMIT page gains ARM FOR PLUGINS with 5 MIN, 15 MIN, 60 MIN and OFF keys, a countdown that names the module, and a DATA (flat) tick that sends plugin audio as NFM with pre-emphasis off. The module chooses nothing else: the frequency, the power and the mode stay the ones on the page.

The key is open only while the grant, the armed window, a live TRANSMIT page, a fresh frame tick and the module's own active() are all true together. A hand on PTT or the latch always takes the transmitter first, and the web control and rigctld still cannot key it. The window is never saved: nothing in config.json can arm anything, and starting FoxSDR again always starts disarmed. The window closes, not merely pauses, when it times out, when you press OFF, when the page is closed or rolled up, when the radio is closed or faults, when the grant is withdrawn, when the program stops responding, when a configuration is loaded, and when the 60 second cap fires. Only one module holds the transmitter at a time (the first granted module in load order, for the length of a transmission); a second one waits and its plate says so. Audio that is not a number is replaced by silence and anything past full scale is clamped, a negative pull() result stops that module's transmit audio, and the diagnostic log records tx: armed <n> min, tx: plugin <name> keyed, tx: plugin <name> unkeyed (<reason>) and tx: disarmed (<reason>), never a frequency. The rule in docs/TRANSMITTING.md and in the transmitter's header that said only a hand could key it now says a hand, or a plugin you have granted and armed.

Verified on Windows with the new test_tx_audio (352 checks: the ABI surface, all 256 combinations of the eight keying conditions, the arming window, the ring that never blocks, the runner's single holder, the transmitter's cap, faults, hand precedence and hostile audio), test_tx_audio_app (283 checks, the real application window with a real fixture plugin DLL and a fake transmit sink), test_tx_audio_page_app (26) and test_tx_audio_plate_app (31), the last two through the real cascade.exe. The 17 related suites all passed together (100% tests passed, 0 tests failed out of 17), and the two timing-sensitive new suites passed six runs in a row. Each gate was then removed in the source to see a test fail: of 37 such runs, 33 made a test fail. Of the four that did not, two altered code that had since been replaced (the replacements, tried the same way, do make a test fail), and two are second copies of a rule that is enforced twice on purpose (the page-live input, and the application's own re-check of the holder's grant); removing both copies of the page-live rule makes test_tx_audio_app fail (18 of 283 checks).

Nothing has transmitted: no Pluto was attached and no RF was radiated, every transmit test ends in a fake sink, and the only module that has ever asked for the transmitter is the test fixture. The 60 second cap was checked by shortening the latch's own timeout (400 ms) through its existing test knob, not by waiting a minute. What remains is a live check with a Pluto into a dummy load and a real Winlink-compatible plugin, and a clicked ARM-to-keyed run through cascade.exe with an open radio, which a bounded run cannot do today. The plugin ABI version is unchanged; the host API level goes from 1 to 2.

Tested together

The changes above were built and tested separately and then combined in one build; this section says what was checked on the combination.

The first six (the decoder settings fold, the STATUS fold, the ADS-B map, the TOOLS bank, the dials and the plugin sites) were combined first. Only the TOOLS bank conflicted with another change, the STATUS fold, in the configuration loader, saver and field list, and in the application window's save comparison, apply and read-back; the resolution keeps both fields once in each of those places. Each of the five new configuration keys (statusCollapsed, displayOpen, mapListOnScreenOnly, squelchAuto, pluginSites) is present once in the structure, the loader, the saver, the save comparison, applyConfig, currentConfig and the fuzz seed. The combined tree builds without an error in Release (MSVC), twice, and the combination needed no code change beyond the fuzz seed, which lacked squelchAuto and was given it, and two comments in the dial test that still said the VIEW bank and now say TOOLS.

The 72 suites that the six changes name in their own test sections were then run one at a time on the combination: 71 pass, including all the application tests (test_deck_knobs_app 152 s, test_airband_app 137 s, test_plugin_store_app 101 s, test_plugin_inuse_app 97 s, test_theme_census 69 s). The 72nd, test_i18n, failed 66 checks and nothing else: each of the 33 catalogues was 45 strings short and carried 3 that no source uses, which is the sum of what the six changes add (2, 5, 2, 8 and 28) and retire (the VIEW bank's two words and the old catalogue-address tooltip). The translation pass then added the 45 strings to, and dropped the 3 retired ones from, each of the 33 catalogues (py -3.14 tools/i18n_keys.py: 0 missing, 0 stale, 0 empty in every one), and test_i18n, test_i18n_glyphs, test_app_rail, test_plugin_store_view and test_tune_control pass.

The file-path key and the gain readback fix were added next, and the four new words they need ("BROWSE FILES", "Choose a file", "Programs (.exe)", "All files (.*)") were translated: all 33 catalogues read 0 missing, 0 stale and 0 empty, and test_i18n passes. The file-path change merged without a conflict; the only change it needed was in its own application test, which now opens the DECODE bank's folded "Plugin settings" line before it looks for the key. The sixteen suites that name those changes (test_plugin_settings_ui, test_file_pick, test_plugin_pathpick_app, test_plugin_ui, test_gui_disk_audit, test_excuse_cap, test_autogain_app, test_deck_knobs_app, test_i18n, test_i18n_glyphs, test_app_rail, test_plugin_store_view, test_decoder_settings_fold_app, test_theme_colours and the two configuration suites) passed one at a time. The whole suite at that point, 358 tests one at a time, ended with 357 passed and 1 failed in 2,663 s: test_startup_state, whose launched application ran to the end and saved its configuration but whose captured output was empty in that run, so the check for "rendered 3 frames" failed. Run alone it then passed 43 times and failed once in 44; nothing in this batch touches start-up or output capture, and a scratch folder kept from a failure of the same test on 8 October, on a 0.99.72 build, shows it has failed before. Its cause is not established.

The transmit grant was added last. It touches many of the same files as the earlier changes (plugin_abi.h, config.{hpp,cpp}, app_window.{cpp,hpp}, plugins_view.cpp, plugin_store_view.cpp, tests/CMakeLists.txt, the manual, PLUGIN-API.md, PRIVACY.md, DIAGNOSTICS.md) and applied to the combination without a conflict. The two places where the changes had to agree in words do: the sentences that said the console enforces two permissions now name three in one voice, and the ABI header holds the file-path setting and the transmit capability, CASCADE_CAP_ALL_KNOWN 0x7FFF and the three state flags once each; it is byte for byte the copy assembled independently for the plugin repository (SHA-256 511b5e86a8458a5a8cde1b158c82b25129ca820c5a305ad24216884cfe4ce54d for both). The 29 added and 4 reworded interface texts are in all 33 catalogues (py -3.14 tools/i18n_keys.py: 1964 translated, 0 empty, 0 missing, 0 stale in each), test_i18n runs 194,999 checks with none failing, and test_i18n_glyphs and test_app_rail pass. The combination builds in Release without an error, twice. The 22 suites the transmit change names (the four test_tx_audio ones, test_transmitter, test_transmit_page, test_pluto_tx, the plugin, configuration, census and translation suites) pass together in 119 s, and the whole suite, 362 tests one at a time, passed 362 of 362 in 2,567 s, test_startup_state among them: the intermittent output-capture failure noted above did not recur in this run, which does not show that it is fixed.

Not verified: the new words in all 33 languages, among them the 29 x 33 for the transmit grant, are machine translations that nobody has read, and the three minute keys' widths were measured by hand against the interface font (the widest, Vietnamese, takes 52 of the 54 px) and not drawn in the application for the Chinese, Japanese and Korean files, whose face is found on the machine at run time. The tests that remove a behaviour to see a check fail were run on each change before the changes were combined and were not repeated on the combination.

Don't miss a new foxsdr release

NewReleases is sending notifications on new releases.