Settings → General → Accessibility → Long press to right-click opens
Parla's context menus by holding a finger or the primary mouse button.
It covers chats, messages, gallery items, image/video viewers, group
members, stickers and running Webxdc apps. GTK text inputs keep their
native editing menus. A short tap or scrolling does not open a menu.
The option takes effect immediately and is independent of the a11y
build option.
When no preference has been saved, it is enabled if XDG_CURRENT_DESKTOP
contains a colon-separated Phosh entry (for example Phosh:GNOME), or
XDG_SESSION_DESKTOP / DESKTOP_SESSION identifies phosh, ignoring case.
Other desktops default to disabled. An explicit choice is saved as
long_press_context_menu in the [General] section of Parla's settings
and overrides session detection; no GNOME/Phosh GSettings changes are needed.
GTK's gtk-long-press-time controls the hold delay, and moving the pointer
beyond GTK's threshold cancels the hold.
The same option applies to touch and mouse holds on every display; Parla does not change behavior based on touchscreen detection. Right-click menus remain available when long-press is disabled.
Code that adds a context menu uses ContextMenuGestures (widget, settings)
and connects its requested (x, y, long_press) signal once for both input
methods. Coordinates are relative to widget; return true after opening
the menu, or false to let child widgets handle the event. The helper owns
capture, gesture claiming and live preference updates. Resolve recycled
list/grid items at event time. Keyboard handlers can call the same menu
function with their own focus-based anchor.
How Parla is exposed to screen readers on each platform, and what the Windows/macOS bundles must ship for that to work at all.
GTK 4 keeps an internal accessibility tree (roles, labels, states) and exposes it through a platform backend chosen at runtime from the backends compiled into GTK:
- Linux: the AT-SPI backend, always compiled in, talks to the AT-SPI bus over D-Bus. Orca works with stock distro GTK; nothing special is needed in our packages.
- Windows: screen readers (NVDA, JAWS, Narrator) consume
UI Automation. GTK only speaks UI Automation through its
AccessKit backend (GTK >= 4.18, build option
-Daccesskit=enabled, implemented by theaccesskit-clibrary). A GTK built without it exposes nothing: the window is an empty rectangle to a screen reader, no matter what the app does. - macOS: same situation — VoiceOver is only reachable through the AccessKit backend (bridged to NSAccessibility).
The backend is selected automatically: in a win32-only GTK build with
AccessKit compiled in, AccessKit is the only real backend and is used
by default; no GTK_A11Y environment variable is needed.
GTK_A11Y=help prints the backends a given build carries, which is
the quickest way to check a bundle.
MSYS2's stock gtk4 package is built without the AccessKit
backend (and its accesskit-c package lags behind the version GTK's
meson build requires), so a bundle made purely from stock MSYS2
packages is unreadable by NVDA/JAWS. Therefore:
- CI rebuilds the MSYS2
gtk4package with-Daccesskit=enabled, building a matchingaccesskit-cfirst when the MSYS2 repo version is too old:scripts/windows/gtk4-accesskit.sh. The script reuses MSYS2's own PKGBUILDs (same patches, same options) and only adds the accessibility bits; built packages are cached per gtk4 release so the rebuild cost is paid once per version bump. scripts/windows/bundle.shrefuses to produce a zip whoselibgtk-4-1.dlllacks the AccessKit backend, and checks that thelibaccesskitDLL made it intobin/(the ntldd closure picks it up like any other GTK dependency). SetREQUIRE_A11Y=0only for local throwaway builds against a stock GTK.- If MSYS2 ever enables AccessKit in its own gtk4 package, the rebuild script detects that and becomes a no-op; it can then be deleted.
Homebrew's gtk4 bottle has the same gap (and Homebrew carries no accesskit-c formula at all), so:
- CI rebuilds the same GTK release Homebrew installed from the
upstream tarball with the formula's macOS meson arguments plus
-Daccesskit=enabled, building accesskit-c first:scripts/macos/gtk4-accesskit.sh. Onlylibgtk-4*.dylibis replaced — in both$(brew --prefix)/liband the gtk4 keg, because libadwaita and the gtk modules reference GTK through the keg's opt path. Headers and pkg-config files are identical for the same release, so the app build is untouched. The built dylibs are cached per gtk4 version and runner flavor. scripts/macos/bundle.shrefuses to produce an app bundle whoselibgtk-4.1.dyliblacks the AccessKit backend, and checks that thelibaccesskitdylib made it intoFrameworks/(the Mach-O closure picks it up like any other dependency). SetREQUIRE_A11Y=0only for local throwaway builds against a stock GTK.- If Homebrew ever ships gtk4 with AccessKit, the rebuild script detects that and becomes a no-op; it can then be deleted.
To smoke-test: turn on VoiceOver (Cmd+F5), start Parla, and move focus with Tab / VO-arrows; controls should be announced with names and roles.
Nothing special is required, but for the record: the deb uses distro
GTK; the Flatpak uses the GNOME runtime's GTK and Flatpak proxies the
accessibility bus by default (the manifest must never pass
--no-a11y-bus); the AppImage bundles a distro GTK whose AT-SPI
backend talks to the host a11y bus over D-Bus.
NVDA is free: https://www.nvaccess.org/download/ (a portable version exists, no install needed).
- Start NVDA, then start Parla.
- Tab / Shift+Tab through the window: every focused control should be announced with a sensible name and role ("Search, edit", "Send, button", ...).
- Arrow through the chat list and messages; NVDA should read rows as they gain focus.
GTK_A11Y=help parla.exefrom a console lists the compiled-in backends; "accesskit" must appear for any of the above to work.
Accessibility is a Meson option, a11y, enabled by default. Passing
-Da11y=false at meson setup time:
- starts GTK with
GTK_A11Y=none, so no AT-SPI or AccessKit backend is loaded and nothing is registered on the accessibility bus; - compiles out Parla's own accessibility extras, which live behind
#if A11Yin the Vala sources (message-row summaries, the hand-applied selected state on chat rows, explicit accessible labels).
The Sailfish port (-Dsailfish=true) always sets GTK_A11Y=none
because Sailfish OS has no accessibility bus, independently of the
a11y option. CI builds the Linux tree both with and without a11y
so both halves of every #if A11Y keep compiling.
The keyboard selection steps explain how to enter selection with Space or the message menu, navigate with Up/Down, toggle checkboxes with Space, and reach the selection actions with Ctrl+Tab.
For a selection regression check:
- Follow those steps with several messages. Confirm the initially selected checkbox receives focus, Up/Down announces each message and its checked state, and Space toggles only the focused message. At either end, Up/Down must stay inside the list without changing selection.
- Repeat after filtering the conversation, and with enough messages to require scrolling. Navigation must follow the displayed message order.
- Deselect every message: Delete and Forward must become insensitive, and Ctrl+Tab must still reach Cancel. Select messages again and cancel with both Escape and the Cancel button; focus must not remain on a hidden checkbox or action bar. Repeat with an unaccepted contact request.
- Check that Space on selectable text, playback buttons, markdown task checkboxes, and popover buttons retains its own behavior. Modified Space must not start selection. Enter must not reply while selection is active.
A working backend only exposes what widgets declare, so:
- Anything that only exists for assistive technology (explicit
accessible labels,
update_state/update_propertycalls, the helpers that compute them) goes inside#if A11Y...#endifso a-Da11y=falsebuild stays free of it. Plaintooltip_textand keyboard-focus handling stay unconditional: they serve everyone. - Icon-only buttons need an accessible name: set
tooltip_text(GTK falls back to it) or callupdate_property (Gtk.AccessibleProperty.LABEL, "...", -1). - Composite rows (chat list, message rows) should carry a label
summarizing their content, otherwise screen readers announce an
unnamed list item. Message rows get theirs from
MessageRow.accessible_summarythroughListItem.accessible_label. - Message rows are focusable list items: Up/Down walk the messages,
Enter runs the double-click action configured in Settings (Reply by default),
Tab moves into a row's selectable
text, Menu / Shift+F10 open the message menu.
ConversationView.on_focus_widget_changeddecides where Tab enters the list (GTK would pick the first, oldest row and scroll the conversation to the top). - The chat-list rows answer the same Menu / Shift+F10 to open the chat
context menu, anchored to the focused row, in full and compact sidebar
modes and the archived list (
Window.focused_chat_row). - Purely decorative images/widgets should not be announced; give them
Gtk.AccessibleRole.PRESENTATION. - Build menus from
PopoverButtons in a plainGtk.Popover, not from aGLib.Menumodel. GTK'sGtkModelButtonnames itself through a presentational inner label, which the AccessKit backend hands to AccessKit unresolved, so NVDA announces every item of aGtkPopoverMenuas empty (#57). The AT-SPI backend resolves the name itself, which is why Orca never showed the problem. Actions and their accelerators stay aswin.*actions;PopoverButtontakes the shortcut hint and restates it asKEY_SHORTCUTS. - Keep the chat list quiet while the user types: the open chat's row does
not echo the draft being written into it, and
load_chatsleaves the rows alone when nothing displayed changed. Rebuilding rows on every draft save made screen readers read the row out on each pause. - Never bind Ctrl+Tab or Ctrl+Shift+Tab application-wide. GTK4 reserves them for moving the focus out of a widget that consumes plain Tab (text views walk out on their own; the chat list has a handler that jumps back to the contact search entry, or the account menu button when that entry is hidden in the compact sidebar, and forward to the conversation toolbar's first button). Ctrl+Page Up / Ctrl+Page Down only move focus through chat rows; Enter opens the focused chat. This keeps unread chats unread while a screen-reader user browses the list. The focused row has an accent outline, separate from the highlight for the chat already open.
- Set the double-click action to Reply. Open an accepted conversation, focus the message list, and use Up/Down to focus a message. Return and keypad Enter should start a reply to that message and focus the composer, announced as "Type a reply". Repeat while navigating matches with conversation search to check the reply target.
- Cancel the reply with Escape. Tab into a message's link or playback button and press Enter: it should activate that control, without starting a reply. Enter in selectable message text must retain GTK's behavior.
- Modified Enter (Shift, Ctrl, Alt, or the platform primary modifier) must not start a reply from a message row. Caps Lock and Num Lock must not prevent plain Return or keypad Enter from replying.
- Open the message menu with Menu / Shift+F10 and choose Select. The selection checkboxes should appear; Enter must not start a reply while selecting messages. It must also do nothing on a contact request's row until the request is accepted.
- Repeat Return and keypad Enter with every double-click setting: the two reactions and Show Contact Details must behave like double-clicking the same message. Show Message Menu must open the menu at the focused row, including after scrolling or filtering. Do Nothing must leave focus and the composer unchanged. Reactions must not move focus into the composer.