Commit Graph
2327 Commits
Author SHA1 Message Date
Martín Lucas Golini 0e7e12ba76 fix(vfs): data safety, SSH policy and liveness fixes from the pre-merge review
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.
2026-10-15 20:33:13 -03:00
Martín Lucas Golini 8827f8de86 feat(window): report X11 drags released over their source window +
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)".
2026-10-15 16:17:40 -03:00
Martín Lucas Golini a52eaa3926 Merge branch 'develop' into feature/vfs 2026-10-15 16:16:42 -03:00
Martín Lucas Golini bec24db054 Fix flaky HTTP socket-budget wakeup regression test
Replace the 50 ms sleep and 200 ms deadline with a synchronized
condition-wait handshake and a generous transfer deadline.

Add a scoped budget-wait hook to keep fallback polling beyond the test
deadline. Use the budget change counter as the wait predicate so spurious
wakeups cannot hide a missing notification.

Confirmed the test fails when the idle-socket notification is removed.
2026-10-15 16:00:08 -03:00
Martín Lucas Golini 5268a83e07 Merge branch 'develop' into feature/vfs 2026-10-15 14:10:04 -03:00
Martín Lucas Golini f0065b65ea Merge branch 'develop' of github.com:SpartanJ/eepp into develop 2026-10-15 13:51:36 -03:00
Martín Lucas Golini ee8f560bde Add native Wayland file dragging for SDL2 and SDL3
- 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.
2026-10-15 10:58:58 -03:00
Martín Lucas Golini 00999e26c3 Queue HTTP connection waits, add a socket budget and cancel on shutdown
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.
2026-10-15 00:36:56 -03:00
Martín Lucas Golini a7ca505229 Add bounded HTTP/1.1 connection pooling for browser sessions
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
2026-10-14 21:05:40 -03:00
Martín Lucas Golini a152a5427d fix(ui): open model rows once on Enter; link efsw frameworks for eefm
- UITableCell: leave Enter to the model view instead of clicking the cell.
  With focus on the selected row's cell, single-click navigation opened
  the row twice: once from the cell click and once from the view's Enter
  handler. Affected UITreeView, and UITableView/UIListView since they
  follow focus-on-selection.
- Add a regression test for list, table and tree views in both click
  modes.
- Link CoreFoundation and CoreServices in eepp_module_vfs_add(): efsw's
  FSEvents watcher needs them, which broke the eefm link on macOS.
2026-10-14 20:05:48 -03:00
Martín Lucas Golini c148becc64 feat(eefm): add settings, terminal panel, system icons and in-place rename
UI:
- Add UISystemIconTheme: colored file, folder and place icons from the
  freedesktop icon theme, the Windows shell or macOS, loaded asynchronously
  with glyph fallbacks. Platform helpers live in src/eepp/ui/icons/.
- UITableView: rubber band selection (tableview::rubberband, styled in
  breeze.css), Shift range selection from an anchor, type-to-select by a
  configurable column, and focus following the selection.
- UISpinBox: keep partial text while typing; clamp and normalize on Enter or
  focus loss, and detach the input listeners on destruction.
- InputEvent: report the window position of file drops (SDL3).
- Image: detect SVG documents preceded by comments, a DOCTYPE or XML
  declarations.

eefm:
- Add a settings window (theme, fonts, rendering, files, terminal),
  persisted in config.cpp.
- Add a terminal panel (F4) whose shell follows the active pane while it
  waits at its prompt.
- Show platform icons for files and places, cached per MIME type.
- Rename in place, select created or renamed items, and copy files
  dropped from other applications into the pane or folder under the drop.
- Fix MIME descriptions and icons for exact names, multi-dot extensions and
  globs; cache unknown types negatively.

eterm:
- Apply UI font, size, hinting and anti-aliasing settings live.
2026-10-14 19:34:02 -03:00
Martín Lucas Golini 6e38945211 Add native file dragging on X11 and Windows
- 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.
2026-10-14 17:24:56 -03:00
Martín Lucas Golini 110a9d46f0 Fix numeric cleanup stripping significant trailing zeros
Only trim zeros from fractional digits, preserving integers and
scientific notation. Fixes the universal locator calculator displaying
14 instead of 140 for 86504-86364.
2026-10-14 14:48:35 -03:00
Martín Lucas Golini 88675a7c84 feat(vfs): add async URI-based VFS module with SFTP and the eefm file manager
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
2026-10-13 14:58:11 -03:00
Martín Lucas Golini 5833344902 refactor(scene): unify current scene and window context binding
Move Context and makeCurrent() from UISceneNode to SceneNode, allowing
scene drawing, updates, and input/IME dispatch to use the same scoped
binding without UI-specific branches.

Replace SceneManager's cached UI scene and scoped scene pointers with
one persistent SceneNode binding. Add setCurrentSceneNode() and make
getUISceneNode() return nullptr when the current scene is not a UI scene,
removing implicit fallback searches.

Establish the default binding during initialization and select a
surviving application scene when the current native window closes.
Use scoped binding in UICodeEditor::showFindReplace() and migrate
existing callers of the UI-specific setter.

Add regression coverage for nested and moved contexts, strict UI lookup,
cross-window restoration, scene removal, find/replace, and window closing.
2026-10-13 01:29:35 -03:00
Martín Lucas Golini d1ea03f420 fix(ui): clear stale dirty queues when widgets change scenes
Remove pending layout, style and style-state entries from the old
scene during rebinding without invalidating shared accessibility
identities. Requeue dirty layouts in the destination scene while
preserving their invalidation reasons.

Fix a use-after-free exposed by TabFocus.WidgetCreatorDefaultsAreIntentional
after deleting a WebView.

Add regression coverage for WebView deletion, subtree and reverse
rebinding, and coalesced layout work.
2026-10-13 00:17:21 -03:00
Martín Lucas Golini c59920c72c feat(a11y): expose basic HTML accessibility semantics
- 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
2026-10-12 23:33:46 -03:00
Martín Lucas Golini edf0ab26f0 Fix build warnings. 2026-10-12 11:06:50 -03:00
Martín Lucas Golini 1aa874d766 feat(ui): introduce intentional tab-focus defaults
- Make generic widgets non-tabbable and opt interactive controls in
- Keep composite controls and model views from exposing duplicate stops
- Fix reverse traversal and list-row focus navigation
- Complete keyboard activation and popup navigation for affected controls
- Preserve explicit focus policies and ecode search navigation callbacks
- Align accessibility focus semantics with effective keyboard targets
- Add tab-navigation regressions and document custom-widget migration
2026-10-12 02:38:25 -03:00
Martín Lucas Golini a06826ee72 refactor(a11y): expose virtual widget semantics and encapsulate hierarchy bridge
- 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.
2026-10-12 01:16:35 -03:00
Martín Lucas Golini 2a2bbea989 refactor(a11y): move scene-rebinding cleanup into UIWidget
- Add a protected UINode rebinding hook with a no-op default.
- Move widget identity invalidation into the UIWidget override.
- Remove the UINode self-downcast and AccessibilityManager dependency.
- Preserve child-first cleanup before updating the local scene pointer.
- Extend regression coverage to verify rebinding and invalidation order
2026-10-11 23:50:01 -03:00
Martín Lucas Golini 87d50eb177 perf(a11y): reduce dormant overhead and decouple scene notifications
- Move process-wide client activity tracking into Accessibility
- Remove UI dependencies from Node through an optional hierarchy observer
- Inline dormant manager checks and avoid redundant subtree cleanup
- Preserve invalidation of identities retained by explicit semantic queries
- Skip idle AT-SPI atomic exchanges and async-resource queue locking
- Avoid empty-set style lookups and redundant dispatcher lookups
- Add lifecycle, multi-scene activity and async-delivery regression tests
- Update accessibility architecture documentation
2026-10-11 18:48:53 -03:00
Martín Lucas Golini 866851c061 fix(a11y): track plain-node subtree moves and fix MSVC build
- 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.
2026-10-11 15:12:31 -03:00
Martín Lucas Golini f380cd7630 Merge branch 'develop' into feature/a11y 2026-10-11 13:08:33 -03:00
Martín Lucas Golini 15f76e4872 Fix MSVC build. 2026-10-11 13:08:12 -03:00
Martín Lucas Golini b79b4444d6 Merge branch 'develop' into feature/a11y 2026-10-11 12:59:47 -03:00
Martín Lucas Golini ccb03a3b8e Add inherited RTL direction and logical UI layout support
- 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.
2026-10-11 12:29:16 -03:00
Martín Lucas Golini 64e13cc80b perf(ui): bound accessibility Inspector value refreshes
- 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.
2026-10-11 03:05:18 -03:00
Martín Lucas Golini a154e1c9f9 perf(a11y): reduce cold text indexing overhead
- 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.
2026-10-11 00:58:03 -03:00
Martín Lucas Golini e8e0a26727 perf(a11y): incrementally index text and bound AT-SPI dispatch
- 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
2026-10-10 23:59:58 -03:00
Martín Lucas Golini df541930bf Merge remote-tracking branch 'origin/develop' into develop 2026-10-10 23:56:39 -03:00
Martín Lucas Golini 340d879677 Fix shaped text vertex colors, glyph blur and RTL caret mapping
Text sized its per-vertex color array with getTotalVertices(), which
assumed one quad per non-space character. Shaped text does not follow
that rule (clusters, ligatures, marks, zero-sized glyphs and fallback
fonts change the glyph count), so the color array could be shorter than
the geometry. The draw then read past the end of the array for the last,
visually rightmost glyphs: they disappeared or showed up as stray glyphs
in mixed Latin/Hebrew/Arabic strings.

- Size fill and outline colors from the generated geometry and rebuild
  them whenever the vertex counts change. Remove getTotalVertices().
- Never read past the color array in draw(), and draw the outline pass
  with its own vertex count instead of the fill count.
- Share one shaped glyph walker (forEachShapedGlyphQuad) between both
  setFillColor() overloads and keep it in sync with
  ensureGeometryUpdate(): same layout parameters and the same fill
  visibility test. The per-character overload advanced its vertex index
  only when a color was present, which misaligned the remaining colors.

TextLayout snapped complex-script glyph positions to whole pixels, but
simple-script glyphs used the pen position as-is. After a Hebrew or
Arabic run the pen is fractional (HarfBuzz 26.6 advances), so the Latin
glyphs that followed were drawn at sub-pixel positions and blurred. Round
simple-script positions as well. Pure Latin text is unchanged.

Caret mapping:
- Text::findCharacterPos() and findCharacterFromPos() now use the text's
  own base direction instead of TextDirection::Unspecified, so caret
  positions and hit testing agree with rendering.
- In a right-to-left paragraph, place the end-of-text caret at the left
  end of the last line instead of after the rightmost glyph.

Add TextLayout::getFirstStrongDirection(), which returns the direction of
the first strong bidi character (UAX #9 P2/P3), for upcoming
right-to-left layout support.

Update the textedit Arabic and Hebrew reference images: only punctuation
that follows RTL runs moves, now aligned to whole pixels.
2026-10-10 22:03:24 -03:00
Martín Lucas Golini c27425887f Fix accessibility dispatch safety, selection reporting and tree updates
- Defer policy changes during native dispatch and guard re-entrant updates.
- Correct container hit testing and semantic subtree identities and indices.
- Avoid duplicate indexed-reparent events and skip no-op sibling moves.
- Keep child insertion linear and avoid registering unseen nodes unnecessarily.
- Add lightweight role/state queries and reduce node-info overhead.
- Implement AT-SPI selection queries, actions and event-maintained snapshots.
- Release AT-SPI session-bus resources and unify event-loop wakeups.
- Fix Windows harness executable discovery and expand regression coverage.
- Document lifecycle safeguards and selection snapshot requirements.
2026-10-10 20:52:02 -03:00
Martín Lucas Golini d4b751bf83 Fixed six findings, with regression coverage and documentation updates:
- Windows selection teardown crash and cross-window text-range identity.
- Manager invalidation during setId() callbacks.
- Unused UIA/macOS text-edit allocations.
- Hidden-subtree event leaks, preserving label relations.
- Disabled virtual-row states and actions.
2026-10-10 14:48:37 -03:00
Martín Lucas Golini 67660d4c13 Add accessible text, relations and live regions; keep dormant a11y free
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.
2026-10-10 02:01:52 -03:00
Martín Lucas Golini 1dd0870187 Harden accessibility review fixes and make macOS de-dup linear
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.
2026-10-09 10:47:43 -03:00
Martín Lucas Golini d27eae5927 Harden accessibility lifecycle, events and native backends
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.
2026-10-09 00:06:36 -03:00
Martín Lucas Golini 51b1059996 Fix accessibility ownership and native backend lifecycle
- 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.
2026-10-08 21:20:14 -03:00
Martín Lucas Golini 5a4fb936f0 Merge branch 'develop' into feature/a11y 2026-10-08 10:04:08 -03:00
Martín Lucas Golini 9d8ec467cf Modernize UI Editor with isolated previews and UIApplication
- 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.
2026-10-07 14:02:56 -03:00
Martín Lucas Golini 8a3fbb6719 Replace select() with poll()/WSAPoll() in SocketSelector
SocketSelector used fd_set + select() on every platform. On POSIX,
descriptors >= FD_SETSIZE (1024) were rejected and only logged, so a
UI inspector listener or client opened that high was never serviced.
On Windows, the selector held at most 64 sockets (Winsock's default
FD_SETSIZE).

SocketSelector:
- Use a shared pollfd implementation on every platform: poll() on
  POSIX and WSAPoll() on Windows. This removes both FD_SETSIZE limits.
  Store descriptors in a SmallVector<pollfd, 16>, which covers the
  inspector's peak without heap allocations.
- Report POLLIN | POLLERR | POLLHUP as ready to match select()'s read
  readiness, so a peer disconnect still triggers the receive() that
  discovers it. Windows reports a graceful shutdown as POLLHUP.
- Treat negative timeouts as an immediate check instead of an infinite
  wait.
- Clear revents when poll() fails, so isReady() never reports readiness
  left over from an earlier wait.
- An empty selector sleeps for a finite timeout and returns false;
  with an infinite timeout it returns immediately. WSAPoll() rejects
  empty sets, and nothing could ever become ready anyway.
- Remove sockets by swapping with the last entry instead of shifting.
- Raise the Winsock target to Vista (0x0600), the minimum for WSAPoll().

UI inspector:
- Stop watching the listener while the 16-client limit is reached. A
  pending connection kept it readable, so every wait() returned
  immediately and the network thread spun.

Tests:
- Add SocketSelector tests for listener and data readiness, removal,
  duplicate adds, disconnect readiness, negative timeouts and empty
  selectors, plus a POSIX run with every descriptor above FD_SETSIZE.
- Add HighFileDescriptorReservation, shared with
  Http.tcpConnectTimeoutHandlesFdAboveFdSetSize. It raises the soft
  RLIMIT_NOFILE when needed, so the high-fd tests run under common
  defaults (1024 on Linux, 256 on macOS) instead of being skipped.
- Fix UIHTML.AttributeSelectorsMatchWidgetIds on Windows: it compared
  c_str() of a temporary that was destroyed before the check.
- Fix UIHTML.StylesheetsDoNotPaintRawTextNodes on Windows: read the
  frame before display(), because Windows drivers don't preserve the
  back buffer after a buffer swap.
2026-10-07 01:07:03 -03:00
Martín Lucas Golini be17af3da0 Fix CSS selector backtracking, union style dependencies and bind libeepp calls directly
Selector matching (Selectors Level 4 §17.3):
- Retry other candidates for descendant and general-sibling combinators
  instead of rejecting after the first failed path. Checkpoints use a
  fixed array of one entry per checkpoint-saving rule (16 on the stack,
  32/64 in out-of-line helpers, heap beyond), with failure pruning so
  exhausted searches stop early.
- Handle <html> as a style-sheet parent boundary whose siblings stay
  visible: failed parent steps at an <html> with element siblings retry
  instead of rejecting, and a ~ candidate that is such an <html> resumes
  its search without a checkpoint.
- Reject the subject before sizing storage for long selectors.

Style dependencies:
- getRelatedElements() now returns the union of tracked elements over
  every matching path, so state changes on any eligible ancestor or
  sibling restyle the subject. Strategies are chosen per selector:
  immediate exit, fixed-prefix walk, single candidate scan (with a
  shared suffix for ~ candidates), an ancestor-path DP, and a memoized
  collector. All are linear and allocation-free for common selectors.

Other fixes:
- Exclude raw text nodes from matched author styles; their widget
  backgrounds painted over glyphs drawn by the parent rich text.
- Report widget ids from getPropertyString() so [id=...] selectors match.
- Add StyleSheetSelectorRule::hasAnyPseudoClasses() and
  UIStyle::getRelatedWidgets() (inline, ABI-compatible additions).

Build:
- Link libeepp with -Wl,-Bsymbolic-functions on Linux, BSD and Haiku so
  internal calls skip the PLT (11,314 -> 619 PLT entries). Measured:
  Markdown load -25%, selector matching -11%, attribute matching -14%.

Tests and benchmarks:
- Regression tests for backtracking, <html> boundaries, subscription
  union and cleanup, operation-count bounds, long selectors and id
  attribute selectors, plus a differential test against an exhaustive
  reference matcher.
- Add Benchmark.CSSSelectorCombinators and Benchmark.CSSStateDependencies
  and create all benchmark windows hidden.
2026-10-07 00:03:22 -03:00
Martín Lucas Golini d49fe3fdc8 Add date and time pickers with calendar popups and HTML mappings
- Add CalendarDate, TimeOfDay, LocalDateTime and DateRange with Gregorian
  arithmetic, locale-aware formatting/parsing, ISO serialization and Unix
  timestamp conversions with explicit UTC offsets.
- Add UIDateTimeEdit, UIDatePicker, UITimePicker, UIDateTimePicker and
  standalone UICalendar, with XML/CSS registration and data binding.
- Support segmented keyboard editing, numeric entry, ISO paste, configurable
  steps, bounds, empty values and independent seconds/milliseconds display.
  Default time display to hours and minutes while preserving stored precision.
- Add day/month/year calendar navigation and a combined date-time popup with
  time steppers, live configuration updates and scoped event connections.
- Add Breeze and fallback theme styles with CSS-defined SVG icons, dp sizing,
  font-aware calendar dimensions and muted read-only/disabled controls.
- Map HTML date, time and datetime-local inputs to native controls with strict
  value sanitization, min/max/step/required/readonly/disabled handling,
  constraint validation and normalized form serialization.
- Provide private light HTML control defaults and intrinsic temporal sizing;
  prevent author CSS from styling anonymous implementation parts.
- Share popup placement and visibility helpers across pickers, dropdowns and
  trigger/cursor menus while retaining cascading-menu placement rules.
- Fix fractional grid wrapping and input cursor bubbling over child buttons;
  notify and refresh styles when editing permission changes.
- Reduce CSS registration allocations with string_view parameters and
  heterogeneous name lookup; inline frequently used node and CSS getters.
- Align CodeEditor default font sizing with text widgets without overriding
  application preferences.
- Add HTML form margin defaults and update the affected rendering baseline.
- Upgrade Windows ecode nightly builds to Ubuntu 24.04 and newer MinGW.
- Add picker documentation, an example and regression coverage for date math,
  locales, editing, popup placement/lifetime, HTML controls and pixel density
2026-10-06 01:13:44 -03:00
Martín Lucas Golini e9f074b1d2 Fix baseline alignment when rendering mixed rich-text fonts + Fix image diff button hit-testing and native pixel sizing
- Compensate for the difference between font ascent used by RichText layout
  and the character-size baseline used by Text rendering. Cache the natural
  baseline in each text span while preserving background and selection boxes.

- Add pixel-level coverage for proportional and monospace fonts at matching
  and different sizes, with default and explicit line heights. Refresh the
  affected rendering reference images.

- Keep diff view select buttons last in the child list so lazily created
  image viewers cannot intercept their clicks.

- Display source images and generated diffs at native pixel dimensions,
  independent of UI density, scaling down only to fit the viewport. Avoid
  rounding twice at fractional densities.

- Add regression coverage for mode switching and sizing at densities
  1, 1.5, and 2. Use portable temporary paths.
2026-10-05 00:57:39 -03:00
Martín Lucas Golini 98e9213f4d Add local Markdown navigation and persistent ecode previews and fix terminal selection bounds and vertical auto-scroll
- Follow local Markdown links relative to each document, including
  relative file:// paths, with configurable handling and async loading.
- Add validated subtree navigation interceptors and custom link flows.
- Introduce UIScrollableMarkdownView with XML support and scroll reset
  after successful navigation.
- Add opt-in Back/Forward history, localized context menu actions, and
  mod+[ / mod+] shortcuts; enable history in ecode previews.
- Restore preview tabs between sessions and preserve live editor binding
  and unsaved source content when navigating back.
- Refresh the README around eepp's GUI framework and document the new APIs.
- Remove the fixed Markdown code font size and add regression coverage
- Clamp selection coordinates independently so horizontal dragging preserves
  the selected row. Use the mouse event's Y coordinate against the terminal's
  vertical bounds, including padding, and retain unclamped coordinates during
  periodic updates outside the window.
2026-10-05 00:12:07 -03:00
Martín Lucas Golini af273c08a4 Avoid redundant layout invalidation from inline fragment sizing
Track when the formatting owner assigns fragment bounds and suppress
layout feedback from text-node and inline-span size changes. Preserve
size events and invalidation from callback-driven text or style changes.

Add regression coverage for content reuse and table sizing after size
callbacks. Preserve class sizes and existing member offsets.

Adjust browser toolbar padding and remove the URL bar's right margin.
2026-10-01 01:41:18 -03:00
Martín Lucas Golini e4801afab9 Reduce rich-text rebuilds and normalize browser addresses
- Reuse unchanged inline content and fragment lookups.
- Share plain-text min/max intrinsic measurements without copying
  existing shaped layout state.
- Preserve content invalidation during active measurements.
- Default URL bar and CLI addresses to HTTPS while preserving
  explicit URLs and local file paths.
- Fix Windows header macro conflicts and eproc signal declarations.
- Add regression coverage for reuse, rewrapping, and invalidation
2026-10-01 00:45:25 -03:00
Martín Lucas Golini ddc9112f13 Reduce redundant HTML table layout work. Add browser shortcuts and keybinding inspection.
- Skip dirty-descendant scans for leaf layouts during document construction.
- Treat table-assigned cell sizes as computed output, avoiding repeated measurement and intrinsic-size invalidation.
- Defer descendant changes until table traversal completes, preserving content updates and propagating completed intrinsic contributions.
- Add regressions for dirty-root coalescing, cell measurement, nested table reflow, and content changes during size events.
- Register tab, address-bar, reload, and history navigation commands in ui_html
- Route toolbar actions through the registered commands
- Add read-only ui.keybindings queries for scenes and supported widgets
- Extend the inspector CLI, documentation, and regression tests
2026-09-30 23:53:18 -03:00
Martín Lucas Golini 2613ec0921 Fix remaining DPI inconsistencies and expand regression coverage
Correct checkbox and radio autosizing, submenu padding, scrollbar skin
sizing, and nested menu-bar popup coordinates.

Preserve resolved stroke widths through CSS serialization and transitions,
including HTML's special px scaling.

Add high-density regressions for these fixes and the preceding DPI cleanup
commits, covering padding and margin setters, widget defaults, dialog
dimensions, table padding, editor spacing, and diff gutters.
2026-09-30 12:19:40 -03:00
Martín Lucas Golini d04a6ad279 Fix cursor jump when clicking a scrolled editor split
Preserve the viewport when restoring a pane's selection on mouse focus,
so click hit testing uses the visible text. Keep scrolling to the restored
cursor for keyboard and programmatic focus changes.

Refs SpartanJ/ecode#958
2026-09-30 11:18:58 -03:00