- Use descriptor-relative POSIX deletion without following swapped symlinks.
- Reclaim terminal SSH sessions on the I/O thread without waiting for peers.
- Add symlink-swap, cancellation, and blackholed-session regressions.
- Document pathname traversal and temporary-publication limitations.
- Drain executor submissions and callbacks before teardown or retargeting.
- Preserve active callables during re-registration and reject stale deliveries.
- Handle inline and synchronous cross-thread executors without reentrant deadlocks.
- Restrict link/unlink publication to exclusively owned temporary files.
- Preserve colliding files when an exclusive temporary open fails.
- Document executor lifetimes and copy/remove move limitations.
- Add safe POSIX no-overwrite publication with explicit cleanup errors.
- Use short temporary basenames for atomic writes and copies.
- Document wrapped-zero InfoZIP rejection and add a 65,536-entry fixture.
- Add publication race, cleanup failure and long filename regressions.
- Replace suitable VFS string concatenation with String::format/formatTo.
- Pass SIGUSR1 through GDB so the interrupted-join test can finish.
- Allow native SFTP renames across aliases sharing a live connection.
- Bound ZIP metadata before decoding and during indexing; reject
inconsistent directory entry counts.
- Make Result assignment exception-safe with rollback and nothrow-move
constraints.
- Retry interrupted POSIX subprocess joins.
- Replace slow timing assertions and sleeps with deterministic checks
and gates; shorten watch and idle-timeout tests.
- Compare connected SSH peers and host keys before treating namespaces
as disjoint; reject ambiguous sessions before modifying destinations
- Reject overwrite renames between local hardlinks using native file
identity on POSIX and Windows
- Add regressions for hostname aliases, independent SSH servers and
hardlink moves, and document remote identity limitations
- Separate external archive opening from browsing inside eefm
- Cache the eefm icon for the Open in eefm action
- Resolve shortcut labels throughout eefm from registered keybindings
- Resolve equivalent local and SSH authorities before modifying transfer destinations.
- Reject uncertain cross-authority identity before copying or removing files.
- Emit RescanRequired for early native watch events instead of replaying stale payloads.
- Cap readAll requests at 256 KiB independently of reserved capacity.
- Handle UINT64_MAX read limits without overflow.
- Add regressions for aliases, watch ordering, bounded reads and throttled SFTP transfers.
- Add a read-only ArchiveProvider with optional runtime-loaded libarchive 3.x
- Stream archive entries with bounded indexes and backing-file revision checks
- Expose available formats and filters without a build-time dependency
- Extend eefm archive browsing and add extraction context-menu actions
- Merge extraction directories while requiring confirmation before overwriting files
- Add archive, extraction, naming and transfer-conflict regression tests
Recognize local .jar, .apk and .epub files alongside .zip using
case-insensitive extensions and the existing ZipProvider.
Centralize archive-location selection, add regression coverage,
and document the supported formats.
Validated: 1935 Linux tests, Windows and Emscripten builds,
and Wine ZIP-location tests.
Check the source provider's Remove capability before the copy-and-remove
fallback, after attempting a native rename. Moves from zip:// and res://
now return Unsupported without creating or overwriting destinations.
Add regression tests for files, directories, destination preservation,
and native renames without removal support. Document the requirement.
- Refuse unsafe SFTP overwrite and POSIX no-replace rename fallbacks.
- Add configurable SFTP operation deadlines and reconnect after timeout.
- Wake all Task waiters and support reentrant directory model dispatch.
- Ignore stale watch results and coalesce large directory listing batches.
- Report partial transfers and preserve skipped symlinks during moves.
- Open remote files as explicit local copies without overwriting prior edits.
- Prevent Xvfb reset crashes and retain server diagnostics.
- Add regression coverage and document the updated behavior.
- Add a read-only ZipProvider with streaming reads, cached metadata,
change notifications, and zip:///archive.zip!/member URIs.
- Support ZIP navigation and copying files out of archives in eefm.
- Extend Zip with explicit read-only access, optional resource
registration, and improved stream, CRC, and error handling.
- Add EE_VFS_API exports and --with-shared-vfs support; reject
combining shared VFS with static eepp.
- Fix polling watcher teardown from its own thread.
- Add regression tests and document ZIP and shared module behavior.
When its server stops answering, a hard NFS mount (or SMB, SSHFS...) blocks every call on it, and
the blocked thread can't be interrupted. One dead mount used to take over the I/O pool, and
with it every local folder and the eefm UI waiting on them.
LocalIOScheduler (VirtualFileSystem::localIO()):
- Calls on local disks run on the I/O pool. Calls under a network mount run on that mount's own
lane of two threads. Mounts come from the mount table (/proc/self/mountinfo, getfsstat,
getvfsstat, UNC and mapped drives on Windows), which never blocks.
- A call that takes longer than Config::networkMountTimeout (15 s by default) fails with
TimedOut ("server:/export is not responding"), and so does every call queued behind it. Until
the stuck call returns, further calls on that mount fail at once; when it returns, its result
is discarded and the mount works again. Timeouts are delivered on their own thread, so they
never wait for a free pool worker.
- A path through a symbolic link into a mount runs on that mount's lane. While any network
mount is mounted, the links are read on the I/O pool, stopping where the path enters a mount.
Renames, opens and watches route the same way. On Windows, links are not followed yet.
- A timed-out read never writes into the caller's buffer afterwards: it reads into its own
buffer, which is copied only while the caller still waits. A network write works from a copy.
Closing a directory whose read is stuck doesn't wait; the read closes it when it ends.
- The scheduler holds the pool weakly. Once the pool is released, every call, network ones
included, fails with Cancelled. Destroying the scheduler, a lane or the watchdog on one of
their own threads detaches that thread instead of joining it.
- submit(), submitResolved( path, followLast ), submitOnSameRoute() and submitCommitted()
let applications run their own blocking work on a path with the same protection.
LocalProvider:
- Runs its calls through the scheduler. Listing a local folder never asks a mount's server: a
network mount inside it is listed as a folder without metadata, and a link into a mount
without its target's type. A rename across mounts fails with Unsupported, so a move copies
and removes instead. localpath.hpp is replaced by FileSystem helpers.
- LocalWatchBackend watches directories on network mounts by polling through the VFS
(PollingWatchBackend), whose calls time out, instead of with efsw.
eefm:
- Header checks when opening files, trash operations and the trash's .trashinfo cleanup run
through localIO(). Each opened file is routed by its own path.
- A timed-out or lost location shows "(F5 to retry)".
Rename: VFS::FileInfo is now VFSFileInfo, as with VFSTask and VFSError. It no longer clashes
with System::FileInfo, so sources can use `using namespace EE::System;`.
Fix: DataDrop is EE_API. eefm's arkdrop.cpp failed to link DataDrop::find() on MSVC.
Docs: vfs.md documents network mounts and localIO().
Tests: LocalIOScheduler unit tests cover routing, timeouts, fail-fast, links into a mount,
caller memory, teardown from lane threads and a released pool.
Some applications drag things that only become files when they are dropped: an archive manager
drags entries it has not extracted, a browser an image it has not saved. These drags carry no file
list, so SDL's XDND support refuses them.
eepp (X11 with SDL3, getCapabilities().dataTarget):
- DragAndDrop::setFileSaveDropHandler(): drags using the XDS direct save protocol
(XdndDirectSave0), such as Engrampa and Xarchiver. The callback picks a local folder for the
drop position; the source then saves into it. XDS takes priority over a file list offered with it,
which such sources only fill with temporary copies.
- DragAndDrop::setDataDropHandler(): drags offering application-defined MIME types and no file
list. The callback receives each registered type's contents and the drop position.
- X11DropTarget takes over only the drags it accepts through the existing SDL3 X11 event hook,
which stays installed while a window has a handler. Callbacks run from DragManager::poll(),
outside native hooks, and may replace or remove handlers.
- Each drop receives its data in its own property, and replies must match its timestamp: a late
reply to an abandoned drop never completes a newer one. Queued drops belong to their data
handler registration (a never-reused generation), so a removed or replaced handler never
receives them. INCR, oversized or unreadable replies fail the drop instead of delivering empty
data.
- XErrorScope moves to x11errorscope.hpp, shared by the X11 drag source and drop target.
- Fix a use-after-free in the X11 drag source: destroying the drag preview pumps X events, which
could re-enter the source and destroy the same preview window again (the intermittent
"free(): invalid pointer" of eepp-native-dnd-tests). The preview is detached before it is reset,
in the X11 source and in both SDL preview backends.
- Wayland is unchanged: SDL owns its data device and refuses these offers.
eefm:
- Entries dragged out of Ark are extracted where they are dropped. Ark's drag only names a D-Bus
object; ArkDrop asks it to extract into the folder (org.kde.ark.DndExtract, as Dolphin does).
libdbus is loaded at runtime, and the drag's name, path and the destination are validated before
the call, since libdbus aborts on invalid ones.
- XDS drops save into the pane, or the folder row, under the pointer. Both kinds of drop refuse
remote folders, which the source cannot write to.
- Opening a local executable runs it in its folder: native programs (ELF / Mach-O) directly,
scripts ("#!") after a confirmation. The execute permission alone is not enough (FAT and NTFS
mounts set it on every file), and remote files are only opened.
Tests: eepp-native-dnd-tests --automated adds the drop target checks on X11 (data drop, INCR
reply, late reply, XDS save and refusal, removed and replaced handlers) from a scripted source.
The "skipped" exit code 77 is now named TestSkipped.
Docs: native_drag_and_drop.md describes receiving drags without files.
Architecture and file organization:
- VFS: TransferEngine moves to transferengine.hpp/.cpp; the blocking write steps shared by
writeAll() and transfers become detail::FileWriter (writeFully, makeDirs, resolveTarget,
replace). FileInfo code moves from error.cpp to fileinfo.cpp; InlineExecutor to
core/executor.cpp.
- SFTP: sshsession.hpp/.cpp is split into one file per class (SSHOperation, SSHChannelPump,
SSHIOContext, SSHConnection, SSHTransport and SSHSocketTransport / SSHChannelTransport /
SSHProcessTransport); the socket free functions become SSHSocket statics and WakeSignal is
SSHWakeSignal. SFTPProviderImpl is implemented in sftpproviderimpl.cpp. Watch backend headers
and SFTPProvider::impl() are no longer public.
- LocalProvider: the POSIX and Win32 implementations live in localproviderposix.cpp and
localproviderwin.cpp.
- UISystemIconTheme: the #if-switched backend becomes SystemIconBackend with
FreedesktopIconBackend, WindowsIconBackend and MacOSIconBackend; systemicons.hpp is gone.
- UIAbstractTableView owns focus-on-selection (backed by TableFlagFocusOnSelection), shared by
UITableView and UITreeView; UITableView's rubber band state reads its table flag.
- New UISceneNode::applyDefaultFont() / setFontRendering() and FontTrueType::filePathOf()
replace the copies in eefm, eterm and ecode.
- eefm: App is the shared context (as in ecode) owning PaneManager, FileOperations,
PlacesController, TerminalPanel and SettingsActions; Pane/FileTab live in pane.hpp, location
text helpers in Location. trash/dialogs/icons free functions become the Trash, Dialogs and
Icons classes; SSHPrompts has its own file; currentFileDrag() is FileTableView::currentDrag().
Deleting from the trash drops its .trashinfo through Trash::forget() on the pool.
Duplicates:
- URI: defaulted copy operations, shared authority copy, new hasSameAuthority().
- Sys::getDataPath() / getDataSearchPaths() replace six XDG_DATA_HOME/XDG_DATA_DIRS lookups;
FileSystem::fileNameSplitExtension() replaces two base/extension splits; lowerExtension and
toWide use FileSystem::fileExtension and String::toWideString; SSH authentication prompts
share requestExternal() bookkeeping.
Removed unused API: FileCursor, VFSProvider::nativeCopy and VFSCapability::NativeCopy,
Transfer::progress(), Task::onCompleteOn(), SmallUniqueFunction::fitsInline(), the Result
combinators, CancellationToken::canBeCancelled(), IOStreamVFS, VFS::instance() and the
deprecated System::VirtualFileSystem / System::VFS.
Mechanical: braces on multi-line control bodies added by the branch, struct field order without
interior padding, the debug.hpp include in task.hpp, doc comment fixes. Sources wrapped entirely
in a platform guard include only eepp/config.hpp before it, and their headers inside it.
Task API, for readability: Task<T>::create( resolve ) returns a pending task and fills a
copyable TaskResolver<T>, called as resolve( value ), which replaces the move-only TaskSource and
its task()-once rule (the last copy destroyed unresolved still abandons the task).
thenOn( executor, fn ) is now then( executor, fn ); then( fn ) keeps running on the resolving
thread.
onComplete( fn ) passes std::optional<T> (nullopt when abandoned) instead of the task.
TaskDiagnostic::TaskRetrievedTwice is gone.
SSHTaskOperation receives its resolver in its constructor instead of exposing task(), and the
polling watch backend no longer creates a task it immediately abandons while stopping.
Data safety (VFS):
- Copies write a temporary sibling and replace the destination only
once it is complete (as writeAll() already did): a failed overwrite
kept nothing of the old destination, and copying onto a hard link to
the source truncated the source itself. A destination symlink is
written through, and one that resolves to the source fails as the
same file.
- Moving a symlink to another file system is refused: the fallback copy
followed it and then removed the copied entries through the link,
emptying its target.
- Saves keep the exact mode of the file they replace: the temporary was
created with it, but filtered through the umask.
Deadlocks and blocking:
- writeAll( createParents ) waited on makeDirs() queued on its own
orchestration pool: one thread deadlocked at once, two with two
concurrent saves. Orchestration jobs now create parents directly.
- Copies and moves run on their own pool (Config::transferThreads), so
long transfers no longer delay saves, reads and recursive removals.
- Local watches are registered and removed on the I/O pool: efsw and the
directory checks touch the filesystem, which blocked the UI thread on
slow mounts.
SSH security:
- "@revoked" known_hosts keys are rejected; they were skipped as
unknown, so StrictHostKeyChecking accept-new trusted them.
- Sessions are shared only between configurations with the same
security policy (known hosts, host key checking, algorithms,
authentication settings), not just the same destination: a strict
alias reused a session a permissive one had accepted.
- A configured algorithm restriction that permits nothing this libssh2
supports fails the connection instead of using libssh2's defaults.
SSH liveness:
- Operations submitted to a failed or closed connection fail with
ConnectionLost instead of staying pending forever.
- ssh-agent requests run on a worker thread (SSHAgent, Unix-domain
sockets), with libssh2_userauth_publickey() and a sign callback that
waits without blocking: an agent waiting for a hardware key touch no
longer stalls every other session. Windows keeps libssh2's agent.
- Connecting no longer holds a helper thread while the user answers a
host key or password prompt: the setup continues from the session's
ready task, one ProxyJump hop at a time. Jump hosts only need to
authenticate, not to offer SFTP.
- Idle connections close after SFTPProviderConfig::idleTimeout. The old
check compared use_count() with 1 and never passed: connections now
track their users (open handles, pumps, jump children).
- A remote watch agent that never answers falls back to polling after
5 seconds; the directory listing waited for it indefinitely.
Core:
- CancellationSource::cancel() keeps the shared state alive while its
callbacks run: a callback destroying the source freed it mid-cancel
(heap use after free under AddressSanitizer).
- MainThreadExecutor::shutdown() detaches the executor from its
dispatcher node and drops queued and later functions.
- String::icompare(): case-insensitive comparison without allocations.
VFSDirectoryModel (UI thread):
- Names compare with String::icompare() instead of lowercased copies,
which made every sort, merge and insertion allocate (about 2x slower
when sorting).
- Sorting by type asks the type provider once per entry instead of
twice per comparison.
- The case-insensitive plain text filter keeps a search table instead
of lowercasing every name on each row rebuild.
- Watch events update the visible rows in place instead of rebuilding
them from every entry per event.
Drag and drop:
- The X11 drag source showed the pending badge over the source window
for every application, which broke the X11 drag and drop integration
tests in CI. DragOffer::sourceHandlesDrops now asks for it; eefm sets
it, since it completes those drops as its own drag.
eefm:
- Shutdown destroyed the main thread executor before the VFS and the
thread pool while continuations still held it: closing during a
transfer could dispatch through freed memory. Transfers are cancelled,
the VFS is destroyed, the executor is detached, and only then are the
scene, its pool and the executor destroyed.
- Remote files opened locally were downloaded to
/tmp/eefm/<scheme>/<host>/<name>: files with the same name from other
folders, users or ports overwrote each other, in a directory shared by
every user. They now go to a per-file folder (a hash of the complete
URI) in the user's cache.
Every fix comes with a regression test.
feat(eefm): drag files to other applications
X11 never offers a native drag to its source window, so a release over
it ended as a plain cancellation. DragResult::releasedOverSource now
tells the application, which can complete it as its own internal drop.
That lets applications start the native drag as soon as their own drag
begins (as file managers do: the files can then go to any window, even
one switched to with Alt+Tab mid-drag) and still handle drops on
themselves. The preview shows the pending badge over the source window
instead of the rejected one.
A drag of local files starts the system drag at once, along with eefm's
own, so the files can be dropped on any window, also after switching to
it with Alt+Tab mid-drag (starting it only once the window had lost
focus was refused on X11). eefm's drag keeps highlighting targets while
it sees the pointer, and waits for the system drag to end instead of
dropping on release:
- Released over eefm on X11 (releasedOverSource), it drops as eefm's
drag: panes, folders, tabs and places, with the Move/Copy menu.
- On Wayland, macOS and Windows that drop arrives as a file drop; being
eefm's own drag, it is resolved the same way (Move/Copy menu).
- Escape cancels both. Remote files keep the internal drag only.
The system drag shows eefm's drag label, which now has an icon (folders
tinted as in the rows) and the name or the item count. For several items
the icon is that of their most common type, the pressed row's on a tie. It
is rendered alone into the back buffer once, read back and handed to the
system as the preview image. It is cleared with the theme's background,
so the label's rounded corners match it where the preview is opaque
(X11).
Drops from other applications are still copied, now skipping files
already in the destination folder instead of duplicating them as
"(copy)".
- Load Wayland APIs dynamically and track genuine pointer press serials.
- Serve file URI lists on a dedicated queue and transfer thread.
- Render native drag icons with Copy, rejected and pending feedback.
- Use memfd_create on Linux with a portable temporary-file fallback.
- Suppress synthetic gesture releases and clean up on cancellation,
window destruction and process exit.
- Add isolated Weston integration tests and document API requirements.
Requests that hit a connection limit used to wait inside their worker,
polling every 10 ms, and an async request's timeout included that wait.
On a page with many resources from one origin, later requests timed out
while queued, and waiting requests tied up shared thread-pool workers
that other origins needed.
Connection queue:
- Serve blocking and async requests from one first-come, first-served
queue per pool. Requests waiting on one origin's limit never block
requests to other origins.
- In shared thread-pool mode, an async request runs on a worker only
after it has been granted a connection. Grants are handed to the
executor from a service thread, never from the thread releasing a
connection, which could be inside the executor's own lock.
- Requests hold their executor only weakly, so a request can never be
the last owner of the thread pool running it.
- Start an async request's timeout once it holds a connection. A
blocking call's timeout still covers the wait plus the transfer, and
a resumed download keeps its deadline across the HEAD probe.
- Wait on a condition variable instead of polling. Cancelling by ID,
shutdown() and Pool::close() release queued requests immediately.
- Let running requests and blocking callers take over a slot reserved
for queued work that has not started; that work queues again, first
in line. Without this, a redirect, retry or resumed download on a
busy executor could wait forever on slots reserved behind it.
- When queued work starts, switch to a connection that went idle while
it waited, instead of opening a new one.
- Replace an idle connection the server has closed in place, keeping
its slot.
Process-wide limit and idle expiry:
- Limit open sockets across all pools with
Http::setMaxOpenConnections() (default 256). At the limit, close the
least recently used idle socket in any pool, then a socket held for
queued work, and only wait while every socket is in active use.
- Wake waiters on the limit when a socket goes idle, and recheck a
change counter so a wake-up just before waiting is never missed.
- Only inspect a socket for reclaim once its connection is known to be
idle or reserved, never while a request is using it.
- Close idle connections from a background timer when idleTimeout
elapses, instead of only when a connection is next requested in the
same pool.
- The budget and its service thread are intentionally never destroyed,
so the thread is never joined during static destruction, which would
deadlock under the Windows loader lock.
Shutdown:
- Add Http::cancelAllRequests(), which cancels every queued and running
request in every pool and closes idle connections. A per-pool
generation counter makes running requests stop at their next check,
and the pools stay usable afterwards.
- Add Http::waitForConnectionsToClose().
- Engine cancels all requests as it starts shutting down. Once its
windows are closed, it waits up to 100 ms for sockets to close before
ending TLS.
- Check for cancellation every 25 ms instead of 100 ms while reading, so
requests stop within that wait.
TLS:
- SSLSocket::end() now waits while TLS sockets exist: the last socket
destroyed releases the backend, and a socket created in the meantime
keeps it.
- Set up the backend and register a new socket a
lock, so a concurrent end() cannot release the backend in between.
- Add SSLSocket::isInitialized() and a testing h
socket construction.
Replace thread-local connections with owned pools shared across workers.
Bound total and per-origin connections, expire idle sockets, and check
idle connections before reuse. Retry safe requests after stale-connection
races without replaying POST requests.
Fix response framing, chunk trailers, decompression completion, empty
compressed bodies, and handling of bytes beyond completed responses.
Give each WebResourceCache partition its own transport, preserving
connections across navigation and shared tabs. Close session transports
without blocking the UI thread. This substantially improves browsing
performance in the ui_html example.
Preserve every Set-Cookie field and attribute redirect cookies to the
client that received the response. Prevent rejected redirects from
assigning cookies to their destination.
Add regression coverage for pooling, framing, cancellation, TLS policy
isolation, browser sessions, and redirect cookie attribution
- Implement a Cocoa drag source on NSDraggingSession for SDL2 and SDL3.
- Use the offered snapshot as the drag image, or each file's Finder icon,
with AppKit's own count badge and copy cursor.
- Offer Generic alongside Copy only within the application, so SDL2's
Cocoa windows accept drops while other apps still see a copy-only offer.
- Report cancellation immediately and refuse the drop, since AppKit cannot
abort a running session.
- Fix macOS SDL3 builds: use pkg-config instead of the removed sdl3-config
and keep EE_SDL3_FROM_ROOTPATH for framework builds only.
- Build the native drag tests on macOS, add the Android drag sources, and
avoid the windows.h max macro in the Win32 source.
- Document macOS behavior and platform support.
- Warm the notification workload before timing one million calls
- Collect ten fixed enabled/disabled pairs with alternating order
- Preserve the 1.01x ratio-of-medians guard without conditional retries
- Validate reported measurements and retain JSON diagnostics on failure
- Prevent D-Bus service output from contaminating benchmark artifacts
- Run native AT-SPI checks after timing failures without masking CI failure
- Add deterministic runner tests and document the measurement protocol
- Introduce DragAndDrop with capability queries, copy-only file offers,
image previews, cancellation, and completion callbacks.
- Implement XDND and OLE sources with safe input recovery and window teardown.
- Keep SDL2 and SDL3 presentation and input handling in their own backends.
- Load X11 dynamically without adding a link dependency.
- Reuse Image, URI, and timing APIs; fix Image::fillWithColor().
- Add a manual playground, integration tests, and consumer documentation.
- Migrate SDK builds and packaging to SDL3 and fix bundled runtime lookup.
Async core (EE namespace, documented in docs/articles/async_core.md):
- Add Result<T, E>, Task<T>/TaskSource<T> with continuations and
abandonment, Executor, CancellationToken and SmallUniqueFunction
- Make ThreadPool an Executor with submit() returning a Task; work items
are move-only
- Add Scene::MainThreadExecutor to resume continuations on the UI thread
- Move URI to EE core (eepp/core/uri.hpp) with filesystem helpers
(fromFSPath, getFSPath, getParent); EE::Network::URI remains an alias
- Add System::SHA1 (streaming, modeled on MD5)
- Rename the pack path index to System::PackFileIndex; VirtualFileSystem
is kept as a deprecated alias
eepp-vfs module (src/modules/vfs, documented in docs/articles/vfs.md):
- URI-addressed VirtualFileSystem with pluggable providers: file://,
mem://, res:// (packs) and sftp://
- Async file and directory handles, metadata, space info, capabilities
and IOStream adapters
- Copy/move transfers with progress, cancellation and conflict options
- Change watching through efsw locally, efsw-agent over SSH for sftp://,
and a polling fallback
- VFSDirectoryModel for table views: sorting, name filtering (plain,
glob, regex), type and item-count columns, per-column CSS classes and
lifetime-safe executor delivery
SFTP:
- SSH sessions over libssh2 with ssh_config parsing (Host/Match,
ProxyJump, ProxyCommand), known_hosts verification, ssh-agent, key and
keyboard-interactive authentication, and connection pooling
- Built by default; opt out with --without-vfs-sftp
- Vendor libssh2 1.11.1 with the mbedTLS backend, and add Ed25519 host
and user keys to it through Monocypher 4.0.2 (mbedTLS has no Ed25519)
eefm (src/tools/eefm), a Dolphin-like file manager on eepp-vfs:
- Split panes, each with its own tabs, location bar, history and
filter bar
- Places panel with Places, Remote (saved locations and network mounts)
and Devices (with usage), editable, hideable and reorderable by drag
- Details view with drag and drop, clipboard interoperable with other
file managers, rename, new folder/file, trash (freedesktop, macOS,
Windows) and properties
- Open and Open With from shared-mime-info and desktop entries
- Persisted window, panes, tabs, columns, sort and places
UI and window fixes:
- Keep table column max widths across model updates and give the main
column the remaining width
- Count left margins when laying out push button icon and text
- Add Clipboard::setMimeData, getDataString and getTextMimeTypes
- Report Zip::close/addFile failures and free archives correctly
Tests and tooling:
- Unit tests for the async core, VFS providers, transfers, watching,
the directory model, ssh_config, SFTP (local sshd), the efsw agent
protocol, SHA1 and URI
- Add a VFS benchmark
- Update efsw with efsw-agent and its wire protocol
- Expose source text, heading levels, links, lists/items and image alt text
- Keep formatting wrappers transparent and nested controls accessible
- Route link activation through existing navigation and interception
- Project passive fragment focus to links while preserving control focus
- Notify clients of text, href, alt text and semantic role changes
- Map HTML semantics to AT-SPI, UIA and macOS accessibility APIs
- Avoid advertising selection capabilities for static HTML lists
- Preserve dormant allocation behavior without new per-widget caches
- Fix MinGW harness compatibility with both TreeScope enum spellings
- Add unit/native regression coverage and update accessibility docs
- focusable now targets the native control, without making the host another Tab stop.
- Overrides survive type changes, including transitions through hidden.
- Added regression coverage and updated documentation.
- Make UIWidget accessibility queries and actions virtual while retaining
centralized resolver defaults for built-in widgets.
- Honor custom semantics in hierarchy, focus, auditing, action validation,
and relation names while preserving one-hop relation resolution.
- Remove obsolete parent-change forwarding methods and test notifications
through actual scene operations.
- Restrict SceneNode's hierarchy observer to the internal accessibility bridge.
- Add custom-widget and dormant-construction regression tests and update docs.
- Report semantic subtree removals and arrivals through plain nodes,
preserving identities and avoiding duplicate embedded-scene notifications.
- Add regression coverage for direction changes, reparenting, focus,
selection, event ordering and mirrored-layout hit testing.
- Extend macOS password-mode tests to verify retained element identity,
protected text access and native transition notifications.
- Qualify the event-loop wake-up call to resolve MSVC's Window ambiguity.
- Document accessibility notifications for plain-node subtree moves.
- Cache effective direction and skip unchanged subtree propagation.
- Add logical alignment, box edges, and relative positioning.
- Mirror layouts and controls while preserving logical node order.
- Apply inherited base direction to UI text and editing.
- Keep logical-edge storage lazy and compact.
- Add regression tests, an isolated WebView demo, and RTL documentation.
- Document RichText bidi limitations and plan paragraph-level support.
- Replace full editor values with bounded 1024-code-point previews.
- Reuse previews when document identity and revision are unchanged.
- Use proportional column widths and borrowed model strings.
- Add explicit full-value copying with loading-state safeguards.
- Safely defer worker-originated refreshes to the main thread.
- Add regression tests and Inspector allocation benchmarks.
- Document preview behavior and text revision assumptions.
- Build text indexes using a bulk line-length read under one lock scope,
reusing storage and returning the document revision.
- Avoid requesting unused text offsets in the Accessibility Inspector.
- Add cold first-edit/focus benchmarks and regression tests for unqueried
edits, Inspector updates, Unicode lengths, and buffer reuse.
- Document the remaining O(lines) initialization cost and measurements.
Reduce million-line cold edits from ~29.2 ms to ~5.7 ms without changing
allocation counts, class sizes, or dormant-path work.
- Replace global line caches with lazy, per-manager document indexes
- Update affected line lengths without allocations on ordinary edits
- Preserve indexes across muted edits and recover ambiguous nested changes
- Count semantic children without materializing node identities
- Add a 2 ms AT-SPI dispatch budget while preserving backlog wakeups
- Add opt-in AT-SPI/UIA profiling and active performance benchmarks
- Cover shared documents, lifecycle, Unicode and nested-edit regressions
- Document measurements, scalability limits and native-host follow-ups
- Refresh virtual children when column visibility or main column changes
- Notify semantic parents when widget roles change tree exposure
- Report expansion state changes for queried tree rows
- Use scalar native queries without building full node metadata
- Add Linux CI coverage for deferred policy changes
- Add hierarchy, expansion-event and UIA scalar-query regressions
- Fix multiline control-statement braces and update architecture docs
Text:
- Ranged text access in code points (length, range, line bounds) that
never copies more than requested, plus a text revision (document UUID
and modification) so UIA and macOS cache their UTF-16 copies.
- Report document edits as exact change records, up to four per widget
and frame, then collapse to one whole-text change. Resets, loads and
document swaps report a whole-text change.
- The removed text is available only while an edit is being notified
(TextDocument::getNotifiedRemovedText()); DocumentContentChange stays
{ range, text }, so queued records (LSP) copy nothing extra.
- Suppressed text changes are keyed by node ref, so a document shared
with another window still reports its edits.
- UITextView reports NameChanged; UITextInput overrides onTextChanged()
to report ValueChanged.
Relations, live regions and shortcuts:
- aria-labelledby, aria-describedby and aria-live (API and CSS). Ids a
client has read are remembered; renaming, adding or removing their
widgets notifies the dependents. setId() copies the old id only when
it is remembered.
- Live regions and UISceneNode::announceForAccessibility(). A newer
change from the same widget replaces its pending announcement, and
hidden sources are dropped.
- Menu item shortcuts (std::string text and key) exposed as AT-SPI key
bindings, the UIA accelerator key and macOS command characters.
- ScrollTo for anything inside a scroll view
(UIScrollView::scrollIntoView()). Icon-only buttons are named from
their tooltip.
Dormant path (no client attached):
- Widget notifications are inline: one relaxed atomic load of a
process-wide count of active root scenes, no call.
- Scene destruction and scene or host changes never create a manager.
Without a client, notify() queues nothing and widget deletion does
not register parents to announce it. While only the root has an
identity, deletion skips the ancestor walk unless a remembered
relation target may be affected.
- When the last client disconnects, model-view sources release their
queried rows. Persistent handles count their registrations, so
handles held elsewhere survive (PersistentModelIndex::release()).
- Spin boxes no longer hide their input explicitly, and
AccessibilityProperties shrinks from 144 to 136 bytes.
Backends:
- AT-SPI: object:announcement, word and line granularity, ranged
reads, SetTextContents reported as its minimal diff. Watch
disconnects per client (NameOwnerChanged arg0 plus a non-blocking
NameHasOwner check) instead of for every bus name. Without a scene
thread pool, initialize on a detached thread that shares ownership,
so teardown never waits on D-Bus timeouts.
- UIA: notification events (resolved at runtime, Windows 10 1709+),
text ranges served from the revision cache, and a per-frame provider
drain that skips the lock when nothing is queued.
- macOS: announcements, a UTF-16 text cache per revision, and command
key attributes.
- EEPP_ACCESSIBILITY_TRACE=1 logs client activity transitions.
Fixes:
- UICodeEditor::isType() no longer dispatches to a subclass's
getType(), so a UITextEdit is a code editor again.
- ecode: dropped files open only in document editors, not in other
code editors such as a commit message box.
Tools, tests and docs:
- Widget inspector: role names, shortcut, relation and live rows, and
an accessibility audit (unnamed controls, broken relations).
- Accessibility example: live status, an icon-only button, and edit and
reset actions that test_atspi.py checks end to end.
- Regression tests for text records, relations, live regions,
disconnect and reconnect, scene lifecycle and id changes.
- Add the AccessibilityDormant benchmark (C++ new/new[] counted on the
measuring thread) and benchmark_accessibility_dormant.py, which fails
on missing measurements and compares against other builds with
--compare LABEL=PATH.
- Document the dormant-path rules, what survives a disconnection, and
the remaining platform limitations.
macOS:
- Check each notification against a hash set of (event, element,
related element) instead of scanning the whole pending queue. The
ordered queue still drives delivery, so a burst of distinct events
(e.g. 10,000 label changes) stays linear. Created/Destroyed are never
de-duplicated.
- Track the pending focus change by its index instead of scanning for it.
Core:
- SceneManager::update: also bound the loop by the live scene count, so
a scene removed during the pass can't cause an out-of-range read.
- Accessibility setters (role, label, description, hidden) return early
on unchanged values. Re-applying CSS no longer allocates the
properties block or notifies clients.
API cleanup:
- Remove AccessibilityRelation, AccessibilityRelationInfo and
AccessibilityNodeInfo::relations. Nothing populated them. Remove the
inspector row and the macOS linked-elements attribute that read them.
AT-SPI GetRelationSet still answers with an empty set.
- Drop unused includes in uiroot.cpp and accessibility.hpp.
Docs:
- Architecture guide: UIA and macOS queue events until update(); AT-SPI
emits right away except for per-frame text diffs. Describe each
backend's per-frame batching, including the macOS de-dup.
Tests:
- Add UnchangedAccessibilityPropertiesStaySilent.
CI fixes for the previous commit:
- Windows: announce one BoundsChanged on the window root when the scene
resizes, so UIA raises LayoutInvalidated again. Widgets still do not
report their own bounds changes.
- macOS: collapse identical pending notifications per element within one
update, so one text selection change posts a single
AXSelectedTextChanged. Created/Destroyed keep every occurrence, in order.
- MinGW: link the UIA test harness with -municode and uuid, which fixes
the nightly MinGW build.
Backends:
- Split the AT-SPI backend into a private header plus connection, events,
tree/marshalling and method files. Break the 600-line D-Bus dispatcher
into one handler per interface.
- Split the UIA backend into a private header plus core, element provider
and text range files.
- Share hasState/hasAction in accessibilitybackend.hpp instead of three
per-backend copies.
CI and docs:
- Run test_atspi.py --multi-window on Linux: closing a secondary window
while an AT-SPI client stays connected.
- Add docs/articles/uiaccessibilityarchitecture.md (layer map, files,
invariants, per-backend layout, common tasks, test matrix) and link it
from the articles index and the accessibility guide.
- Add a unit test for the single layout notification on scene resize.
Teardown and identity:
- Detach native backends before scene children are destroyed and clear
mRoot afterwards, fixing a use-after-free when an AT-SPI client is
connected while a window closes.
- Invalidate a deleted widget's subtree once, top-down, before OnClose
handlers run (NODE_FLAG_DESTROYING). Descendants never walk into
destroyed parents, and deleted identities cannot be re-registered.
- Closing scenes expose no accessibility manager through either accessor.
- Deleting widgets no client has seen allocates no identity and emits no
events.
- Ignore events from widgets not yet attached to a scene.
Events:
- Keep the order of state and structural events; coalesce only adjacent
query-again hints.
- Stop emitting per-widget BoundsChanged; bounds are queried on demand.
- Report the Focused state and keyboard focus only on the projected focus
owner, through a shared helper used by both paths.
Model views:
- Lazy child count and child queries, cached sibling indexes, selection
and hit testing without enumerating rows.
- Row identities follow inserted and removed rows; removed rows are
pruned from native wrapper caches.
- Expose the active cell editor as the child of the row or cell it edits.
AT-SPI:
- Correct state bits (EXPANDABLE/EXPANDED, SENSITIVE, CHECKABLE,
SELECTABLE, READ_ONLY, SINGLE/MULTI_LINE, SELECTABLE_TEXT) and the
password-text and panel roles.
- Emit text-changed insert/delete, caret-moved and selection-changed
events, diffed once per frame. Support line granularity,
SetCaretOffset and selection editing.
- Track clients by bus name so the active flag clears on disconnect.
- Fix the I/O thread busy-spin and stranded dispatch backlog, flush
queued signals, and type-check Properties.Set values.
UIA:
- Raise focus only for the focused element.
- Defer UiaDisconnectProvider out of widget destruction.
- Keep providers alive for in-flight cross-thread requests.
- Reject unknown interfaces without a UI-thread round trip.
- Require window-specific client activity.
macOS:
- Avoid repeated full node info builds.
- Use sibling indexes and lazy child access.
- Fix label/value deduplication for list items.
Tests and docs:
- Add regression tests for focus ownership, teardown, event ordering,
lazy model identities, nested scenes and cell editors.
- Extend the AT-SPI validation scripts and document the current
performance characteristics and limitations.
- Prevent dense-map iterator invalidation during widget deletion.
- Invalidate widget and model references when scene ownership changes.
- Expose nested-scene controls through the host accessibility manager.
- Preserve identities when reparenting within the same host.
- Synchronize AT-SPI state access with asynchronous initialization.
- Wake the shared SDL event loop without retaining window-owned Input pointers.
- Add lifecycle regression tests and hidden multi-window AT-SPI validation.
- Ensure test-bus failures cannot skip child-process cleanup.
- Reimplement previews using nested UISceneNodes with isolated styles
and resources.
- Preserve live XML/CSS editing, unsaved buffer previews, external file
watching, and project resource loading.
- Use UIApplication for initialization, font resolution, and offscreen
rendering support.
- Enable Breeze by default, add a menu toggle, and replace
--use-app-theme with --disable-app-theme.
- Automatically preserve declared project viewport dimensions, with
an optional override to use the preview split size.
- Support embedded HTML documents inside WebView XML elements.
- Fix fading window shadows and shutdown with visible modal dialogs.
- Update documentation and add regression coverage.