Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b6fd89a
Added Web navigation to core
kefaslungu Jan 3, 2026
b4e9bd4
Pre-commit auto-fix
pre-commit-ci[bot] Jan 3, 2026
62eb77f
Added relevant user documentation and changes
kefaslungu Jan 3, 2026
daf47b4
Added name
kefaslungu Jan 3, 2026
8fe2f73
Merge branch 'webmode' of https://github.com/kefaslungu/nvda into web…
kefaslungu Jan 3, 2026
82043fa
Move web touch navigation into BrowseModeTreeInterceptor with dynamic…
kefaslungu Feb 19, 2026
6030aba
Merge branch 'master' into webmode
kefaslungu Feb 19, 2026
de05928
Pre-commit auto-fix
pre-commit-ci[bot] Feb 19, 2026
266c625
Addressed reviewer's comments
kefaslungu Feb 23, 2026
6a93eed
Merge branch 'webmode' of https://github.com/kefaslungu/nvda into web…
kefaslungu Feb 23, 2026
a68cdf3
Pre-commit auto-fix
pre-commit-ci[bot] Feb 23, 2026
d670765
Added documentation and minor fixes
kefaslungu Feb 27, 2026
3707430
Merge branch 'webmode' of https://github.com/kefaslungu/nvda into web…
kefaslungu Feb 27, 2026
4052b06
Fix type hint where necessary
kefaslungu Feb 27, 2026
152190a
Changed from webmode to browsemode
kefaslungu Mar 2, 2026
1fffa9e
Made edits to user docs and changes.md
kefaslungu Mar 2, 2026
c5ce667
rename web→browse, type hints, deprecation
kefaslungu Mar 2, 2026
5703f06
sentence split onto two lines in user docs and remove unnecessary imp…
kefaslungu Mar 4, 2026
7649ef3
Added documentation explaining browse mode in touch is only active wh…
kefaslungu Mar 5, 2026
3028100
updated docs and changes
kefaslungu Mar 9, 2026
53830c4
- Type annotate _enabledBrowseElements; Sphinx docstring
kefaslungu Mar 9, 2026
8af69b1
Merge upstream/master into webmode
kefaslungu Mar 9, 2026
8a9b3f2
changing the enum values to shorter strings, making _curTouchMode sto…
kefaslungu Mar 10, 2026
1c2a459
Fixed reviewers comments
kefaslungu Mar 10, 2026
3745f52
renamed AVAILABLE_TOUCH_MODES to availableTouchModes
kefaslungu Mar 11, 2026
f11f808
docs: Explicitly states the element type is remembered per document
kefaslungu Mar 24, 2026
f612318
translator comment now says browse mode touch navigation element types
kefaslungu Mar 24, 2026
6a7ba8e
clarifying comment added above the setting
kefaslungu Mar 24, 2026
9e237ef
addQuickNav docstring converted fully to Sphinx style
kefaslungu Mar 24, 2026
74614a2
Apply suggestions from code review
SaschaCowley Mar 30, 2026
ac048ae
Fixed reviewers suggestions
kefaslungu Mar 31, 2026
5690479
Merge branch 'webmode' of https://github.com/kefaslungu/nvda into web…
kefaslungu Mar 31, 2026
f0067e6
Update user_docs/en/userGuide.md
SaschaCowley Mar 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 103 additions & 1 deletion source/globalCommands.py
Comment thread
kefaslungu marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
# Copyright (C) 2006-2025 NV Access Limited, Peter Vágner, Aleksey Sadovoy, Rui Batista, Joseph Lee,
# Leonard de Ruijter, Derek Riemer, Babbage B.V., Davy Kager, Ethan Holliger, Łukasz Golonka, Accessolutions,
# Julien Cochuyt, Jakub Lukowicz, Bill Dengler, Cyrille Bougot, Rob Meredith, Luke Davis,
# Burman's Computer and Education Ltd, Cary-rowen.
# Burman's Computer and Education Ltd, Cary-rowen, Kefas Lungu.
Comment thread
kefaslungu marked this conversation as resolved.
Outdated

import itertools
from typing import (
Expand Down Expand Up @@ -4662,6 +4662,108 @@ def script_touch_rightClick(self, gesture):
winUser.setCursorPos(x, y)
self.script_rightMouseClick(gesture)

webElements = (
"default",
"link",
"button",
"form field",
"heading",
"frame",
"table",
"list",
"graphic",
"landmark",
Comment thread
kefaslungu marked this conversation as resolved.
Outdated
)

webBrowseMode = 0

browseModeCommands = (
(
browseMode.BrowseModeTreeInterceptor.script_nextLink,
browseMode.BrowseModeTreeInterceptor.script_previousLink,
),
(
browseMode.BrowseModeTreeInterceptor.script_nextButton,
browseMode.BrowseModeTreeInterceptor.script_previousButton,
),
(
browseMode.BrowseModeTreeInterceptor.script_nextFormField,
browseMode.BrowseModeTreeInterceptor.script_previousFormField,
),
(
browseMode.BrowseModeTreeInterceptor.script_nextHeading,
browseMode.BrowseModeTreeInterceptor.script_previousHeading,
),
(
browseMode.BrowseModeTreeInterceptor.script_nextFrame,
browseMode.BrowseModeTreeInterceptor.script_previousFrame,
),
(
browseMode.BrowseModeTreeInterceptor.script_nextTable,
browseMode.BrowseModeTreeInterceptor.script_previousTable,
),
(
browseMode.BrowseModeTreeInterceptor.script_nextList,
browseMode.BrowseModeTreeInterceptor.script_previousList,
),
(
browseMode.BrowseModeTreeInterceptor.script_nextGraphic,
browseMode.BrowseModeTreeInterceptor.script_previousGraphic,
),
(
browseMode.BrowseModeTreeInterceptor.script_nextLandmark,
browseMode.BrowseModeTreeInterceptor.script_previousLandmark,
),
)
Comment thread
kefaslungu marked this conversation as resolved.
Outdated

@scriptHandler.script(
description="Select next web navigation element",
gesture="ts(Web):flickDown",
)
def script_nextWebElement(self, gesture):
ti = api.getNavigatorObject().treeInterceptor
if not isinstance(ti, browseMode.BrowseModeTreeInterceptor):
return

self.webBrowseMode = (self.webBrowseMode + 1) % len(self.webElements)
ui.message(self.webElements[self.webBrowseMode])

@scriptHandler.script(
description="Select previous web navigation element",
gesture="ts(Web):flickUp",
)
def script_prevWebElement(self, gesture):
ti = api.getNavigatorObject().treeInterceptor
if not isinstance(ti, browseMode.BrowseModeTreeInterceptor):
return

self.webBrowseMode = (self.webBrowseMode - 1) % len(self.webElements)
ui.message(self.webElements[self.webBrowseMode])

@scriptHandler.script(gesture="ts(Web):flickRight")
def script_nextSelectedElement(self, gesture):
ti = api.getNavigatorObject().treeInterceptor
if not isinstance(ti, browseMode.BrowseModeTreeInterceptor):
return

if self.webBrowseMode == 0:
self.script_navigatorObject_nextInFlow(gesture)
else:
nextScript, _ = self.browseModeCommands[self.webBrowseMode - 1]
nextScript(ti, gesture)

@scriptHandler.script(gesture="ts(Web):flickLeft")
def script_prevSelectedElement(self, gesture):
ti = api.getNavigatorObject().treeInterceptor
if not isinstance(ti, browseMode.BrowseModeTreeInterceptor):
return

if self.webBrowseMode == 0:
self.script_navigatorObject_previousInFlow(gesture)
else:
_, prevScript = self.browseModeCommands[self.webBrowseMode - 1]
prevScript(ti, gesture)
Comment thread
kefaslungu marked this conversation as resolved.
Outdated

@script(
# Translators: Describes the command to open the Configuration Profiles dialog.
description=_("Shows the NVDA Configuration Profiles dialog"),
Expand Down
28 changes: 27 additions & 1 deletion source/touchHandler.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# A part of NonVisual Desktop Access (NVDA)
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
# Copyright (C) 2012-2025 NV Access Limited, Joseph Lee, Babbage B.V.
# Copyright (C) 2012-2025 NV Access Limited, Joseph Lee, Babbage B.V., Kefas Lungu
Comment thread
kefaslungu marked this conversation as resolved.
Outdated

"""handles touchscreen interaction.
Used to provide input gestures for touchscreens, touch modes and other support facilities.
Expand Down Expand Up @@ -43,6 +43,7 @@
import core
import systemUtils
from utils import _deprecate
from treeInterceptorHandler import post_browseModeStateChange

__getattr__ = _deprecate.handleDeprecations(
_deprecate.MovedSymbol(
Expand Down Expand Up @@ -95,6 +96,31 @@
POINTER_MESSAGE_FLAG_CANCELED = 0x400


def _browseModeStateChange(browseMode=False, interceptor=None, **kwargs):
Comment thread
kefaslungu marked this conversation as resolved.
Outdated
if not handler:
return

webModeName = "web"
Comment thread
kefaslungu marked this conversation as resolved.
Outdated

if browseMode:
# Entering browse mode
if webModeName not in availableTouchModes:
availableTouchModes.append(webModeName)
Comment thread
kefaslungu marked this conversation as resolved.
Outdated

handler._curTouchMode = webModeName

Comment thread
seanbudd marked this conversation as resolved.
else:
# Leaving browse mode
if webModeName in availableTouchModes:
availableTouchModes.remove(webModeName)

if handler._curTouchMode == webModeName:
handler._curTouchMode = "object"


post_browseModeStateChange.register(_browseModeStateChange)
Comment thread
kefaslungu marked this conversation as resolved.
Outdated


class POINTER_INFO(Structure):
_fields_ = [
("pointerType", DWORD),
Expand Down
1 change: 1 addition & 0 deletions user_docs/en/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
* A new command, assigned to `NVDA+x`, has been introduced to repeat the last information spoken by NVDA; pressing it twice shows it in a browseable message. (#625, @CyrilleB79)
* Added an unassigned command to toggle keyboard layout. (#19211, @CyrilleB79)
* Added an unassigned Quick Navigation Command for jumping to next/previous slider in browse mode. (#17005, @hdzrvcc0X74)
* Added touch based navigation of web elements in browse mode, allowing touch screen users to move between links, headings, form fields, landmarks and other structural elements. (#3424, #19414, @kefaslungu)

### Changes

Expand Down
31 changes: 29 additions & 2 deletions user_docs/en/userGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -599,13 +599,13 @@ Therefore, gestures such as 2-finger flick up and 4-finger flick left are all po
#### Touch Modes {#TouchModes}

As there are many more NVDA commands than possible touch gestures, NVDA has several touch modes you can switch between which make certain subsets of commands available.
The two modes are text mode and object mode.
The three modes are text mode, object mode and web mode.
Certain NVDA commands listed in this document may have a touch mode listed in brackets after the touch gesture.
For example, flick up (text mode) means that the command will be performed if you flick up, but only while in text mode.
If the command does not have a mode listed, it will work in any mode.

<!-- KC:beginInclude -->
To toggle touch modes, perform a 3-finger tap.
To switch between touch modes, perform a 3-finger tap.
<!-- KC:endInclude -->

#### Touch keyboard {#TouchKeyboard}
Expand Down Expand Up @@ -1050,6 +1050,33 @@ If you want to use these while still being able to use your cursor keys to read
To toggle single letter navigation on and off for the current document, press NVDA+shift+space.
<!-- KC:endInclude -->

#### Touch Navigation in Browse Mode {#BrowseModeTouch}

When using a touch enabled device, NVDA provides an additional touch navigation mode for browsing web content.

When browse mode is active in supported documents such as web pages, NVDA can expose a Web touch mode. This mode allows users to navigate structural elements of a document using touch gestures, similar to browse mode navigation with the keyboard.

In Web touch mode, flick gestures are used to move between common web elements such as links, buttons, headings, form fields, landmarks, and other document structures.

This feature is intended to provide touch users with efficient, structured navigation that mirrors existing browse mode functionality.

##### Touch gestures in Web mode
Comment thread
kefaslungu marked this conversation as resolved.
Outdated
Comment thread
kefaslungu marked this conversation as resolved.
Outdated

<!-- KC:beginInclude -->

| Gesture | Description |
| ----------- | ---------------------------------------------------- |
| Flick down | Switches to the next web navigation element type |
| Flick up | Switches to the previous web navigation element type |
| Flick right | Moves to the next element of the selected type |
| Flick left | Moves to the previous element of the selected type |
Comment thread
kefaslungu marked this conversation as resolved.
Outdated

<!-- KC:endInclude -->

When any element type is selected, flicking left or right moves through the document in reading order, while flicking up and down selects different element.
Comment thread
kefaslungu marked this conversation as resolved.
Outdated

Web touch mode is only available when browse mode is active and does not affect touch navigation outside of browse mode documents.

#### Text paragraph navigation command {#TextNavigationCommand}

You can jump to the next or previous text paragraph by pressing `p` or `shift+p`.
Expand Down