Skip to content
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
53dd3bb
Merge branch 'master' into integrateCPPJieba
CrazySteve0605 Aug 9, 2025
eba63ab
Merge branch 'master' into integrateCPPJieba
CrazySteve0605 Aug 15, 2025
b0ac081
add `WordSegment` module
CrazySteve0605 Aug 18, 2025
9f62f04
update `textUtils/__init__.py`
CrazySteve0605 Aug 18, 2025
81f2040
update `textInfos/offsets.py`
CrazySteve0605 Aug 18, 2025
da64cd8
update `displayModel.py`
CrazySteve0605 Aug 18, 2025
557f404
Pre-commit auto-fix
pre-commit-ci[bot] Aug 18, 2025
f72d348
update type annotations
CrazySteve0605 Aug 18, 2025
19cad8a
Merge branch 'master' into integrateCPPJieba
CrazySteve0605 Aug 20, 2025
adc22fb
add wrapper for word manager
CrazySteve0605 Aug 20, 2025
4adac07
update the word segmentation structure
CrazySteve0605 Aug 20, 2025
407d4b2
Merge branch 'integrateCPPJieba' into wordNavigationForChineseText
CrazySteve0605 Aug 20, 2025
0d40f0a
Pre-commit auto-fix
pre-commit-ci[bot] Aug 20, 2025
676fc42
add copyright header
CrazySteve0605 Aug 21, 2025
ddd48e8
add type annotations
CrazySteve0605 Aug 21, 2025
3c65868
update log
CrazySteve0605 Aug 21, 2025
d69e8b7
add trailing commas in multi-line constructs
CrazySteve0605 Aug 21, 2025
8244a76
make wordSegment module to make file structure clearer
CrazySteve0605 Aug 21, 2025
3f54d62
add initialization logic to wordSeg module
CrazySteve0605 Aug 21, 2025
38b4bea
Pre-commit auto-fix
pre-commit-ci[bot] Aug 21, 2025
eeb96aa
use multithreading for cppjieba's initialization
CrazySteve0605 Aug 23, 2025
3ba56f0
add configuration for word navigation
CrazySteve0605 Aug 23, 2025
356c11c
Pre-commit auto-fix
pre-commit-ci[bot] Aug 23, 2025
4a680ea
make "Auto" the default option for word navigation
CrazySteve0605 Aug 24, 2025
97b6db7
update for pyright checks
CrazySteve0605 Aug 24, 2025
a4edc9e
Merge branch 'integrateCPPJieba' into wordNavigationForChineseText
CrazySteve0605 Aug 28, 2025
3b2d835
resolve deprecation
CrazySteve0605 Aug 28, 2025
9e6a2e1
Pre-commit auto-fix
pre-commit-ci[bot] Aug 28, 2025
c1fb4b8
Merge branch 'integrateCPPJieba' into wordNavigationForChineseText
CrazySteve0605 Sep 4, 2025
a1113d8
add `segmentedText` method
CrazySteve0605 Sep 4, 2025
abeb147
Merge branch 'integrateCPPJieba' into wordNavigationForChineseText
CrazySteve0605 Sep 7, 2025
b848e1b
update `wordSegStrategy.py`
CrazySteve0605 Sep 7, 2025
3bfbe59
update module importing order and type annotations
CrazySteve0605 Sep 7, 2025
f5087cc
Merge branch 'integrateCPPJieba' into wordNavigationForChineseText
CrazySteve0605 Sep 9, 2025
3a0badc
update `wordSegStrategy.py`
CrazySteve0605 Sep 9, 2025
cf3e115
Pre-commit auto-fix
pre-commit-ci[bot] Sep 9, 2025
984b6eb
Merge branch 'master' into integrateCPPJieba
CrazySteve0605 Sep 12, 2025
2b1d4b3
Merge branch 'integrateCPPJieba' into wordNavigationForChineseText
CrazySteve0605 Sep 12, 2025
97eb6dd
handle punctuation spacing
CrazySteve0605 Sep 13, 2025
bac3210
Pre-commit auto-fix
pre-commit-ci[bot] Sep 13, 2025
a8955a3
Revert "update module importing order and type annotations"
CrazySteve0605 Sep 21, 2025
7ee08d0
Merge branch 'integrateCPPJieba' into wordNavigationForChineseText
CrazySteve0605 Sep 21, 2025
90660ba
update `wordSegStrategy.py`
CrazySteve0605 Sep 21, 2025
9537999
revert copyright header of `configSpec.py`
CrazySteve0605 Sep 21, 2025
dc23346
Update source/core.py
CrazySteve0605 Sep 21, 2025
5562e70
Merge branch 'master' into integrateCPPJieba
CrazySteve0605 Sep 25, 2025
38ec7ff
Merge branch 'integrateCPPJieba' into wordNavigationForChineseText
CrazySteve0605 Sep 25, 2025
ccf07f9
correct method naming
CrazySteve0605 Sep 25, 2025
250e700
update UI text for Uniscribe
CrazySteve0605 Sep 25, 2025
53b3870
make `cppjieba` only available when NVDA's language is set to Chinese
CrazySteve0605 Sep 25, 2025
fec70a9
Merge branch 'master' into integrateCPPJieba
CrazySteve0605 Sep 26, 2025
69617c4
Merge branch 'integrateCPPJieba' into wordNavigationForChineseText
CrazySteve0605 Sep 26, 2025
111a24d
update `wordSegSegmenter.py` to handle offsets at the end of the string
CrazySteve0605 Sep 26, 2025
43bfe03
make initialization of word segmenters conditional on language
CrazySteve0605 Sep 27, 2025
2eec029
add unittest cases for `WordSegmenter`
CrazySteve0605 Sep 27, 2025
f769457
Pre-commit auto-fix
pre-commit-ci[bot] Sep 27, 2025
9479029
fixup
CrazySteve0605 Sep 27, 2025
9834b68
extract punctuation from `wordSegStrategy.py` to `wordSegUtils.py`
CrazySteve0605 Sep 27, 2025
b69d466
fix up
CrazySteve0605 Sep 27, 2025
6f586fd
update changelog
CrazySteve0605 Sep 27, 2025
face4bd
Merge branch 'try-chineseWordSegmentation-staging' into wordNavigatio…
michaelDCurran Sep 29, 2025
b40d709
revert `Initialize Word Segmenters for Unused Languages:` checkbox an…
CrazySteve0605 Sep 29, 2025
653e808
Pre-commit auto-fix
pre-commit-ci[bot] Sep 29, 2025
552b42b
fixup unittests
CrazySteve0605 Sep 30, 2025
5e0e3fd
simplify the logic for 'Auto' option in Word Segmentation Standard se…
CrazySteve0605 Sep 30, 2025
c3a8562
Pre-commit auto-fix
pre-commit-ci[bot] Sep 30, 2025
0940a73
fixup
CrazySteve0605 Sep 30, 2025
80b0472
Pre-commit auto-fix
pre-commit-ci[bot] Sep 30, 2025
085ba2f
Merge branch 'try-chineseWordSegmentation-staging' into wordNavigatio…
michaelDCurran Oct 9, 2025
d55d077
Pre-commit auto-fix
pre-commit-ci[bot] Oct 9, 2025
2083095
fixup
CrazySteve0605 Oct 25, 2025
d32549f
make word segmentation module reinitialized after settings are saved
CrazySteve0605 Oct 25, 2025
b8ace76
Pre-commit auto-fix
pre-commit-ci[bot] Oct 25, 2025
042b778
Merge branch 'try-chineseWordSegmentation-staging' into wordNavigatio…
michaelDCurran Oct 27, 2025
db90fff
remove duplicate importing lines
CrazySteve0605 Oct 28, 2025
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
5 changes: 5 additions & 0 deletions nvdaHelper/cppjieba/sconscript
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ sourceFiles = [
"cppjieba.def",
]

env.AppendUnique(
CCFLAGS=['/wd4819'],
CXXFLAGS=['/wd4819'],
)

cppjiebaLib = env.SharedLibrary(target="cppjieba", source=sourceFiles)

if not os.path.exists(outDir.Dir("dicts").get_abspath()) or not os.listdir(outDir.Dir("dicts").get_abspath()): # insure dicts installation happens only once and avoid a scons' warning
Expand Down
10 changes: 6 additions & 4 deletions source/IAccessibleHandler/__init__.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2006-2022 NV Access Limited, Łukasz Golonka, Leonard de Ruijter
# Copyright (C) 2006-2025 NV Access Limited, Łukasz Golonka, Leonard de Ruijter
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

import typing

from winBindings import user32

# F401 imported but unused. RelationType should be exposed from IAccessibleHandler, in future __all__
# should be used to export it.
from .types import RelationType # noqa: F401
Expand Down Expand Up @@ -552,7 +554,7 @@ def winEventToNVDAEvent( # noqa: C901
)
return None
# Make sure this window does not have a ghost window if possible
if NVDAObjects.window.GhostWindowFromHungWindow and NVDAObjects.window.GhostWindowFromHungWindow(window):
if user32._GhostWindowFromHungWindow is not None and user32._GhostWindowFromHungWindow(window):
if isMSAADebugLoggingEnabled():
log.debug(
f"Ghosted hung window. Dropping winEvent {getWinEventLogInfo(window, objectID, childID, eventID)}",
Expand Down Expand Up @@ -784,9 +786,9 @@ def processDesktopSwitchWinEvent(window, objectID, childID):
log.debug(
f"Processing desktopSwitch winEvent: {getWinEventLogInfo(window, objectID, childID)}",
)
hDesk = windll.user32.OpenInputDesktop(0, False, 0)
hDesk = user32.OpenInputDesktop(0, False, 0)
if hDesk != 0:
windll.user32.CloseDesktop(hDesk)
user32.CloseDesktop(hDesk)
core.callLater(200, _handleUserDesktop)
else:
# When hDesk == 0, the active desktop has changed.
Expand Down
7 changes: 4 additions & 3 deletions source/IAccessibleHandler/internalWinEventHandler.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2020 NV Access Limited
# Copyright (C) 2020-2025 NV Access Limited
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

"""
Provides a non-threaded (limited by GIL) Windows Event Hook and processing.
"""

from ctypes import WINFUNCTYPE, c_int
from ctypes import c_int

from typing import Dict, Callable

import core
from winBindings.user32 import WINEVENTPROC
import winUser
from .utils import getWinEventLogInfo, isMSAADebugLoggingEnabled

Expand Down Expand Up @@ -185,7 +186,7 @@ def winEventCallback(handle, eventID, window, objectID, childID, threadID, times


# Register internal object event with IAccessible
cWinEventCallback = WINFUNCTYPE(None, c_int, c_int, c_int, c_int, c_int, c_int, c_int)(winEventCallback)
cWinEventCallback = WINEVENTPROC(winEventCallback)
# A list to store handles received from setWinEventHook, for use with unHookWinEvent
winEventHookIDs = []

Expand Down
6 changes: 3 additions & 3 deletions source/JABHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
POINTER,
byref,
cdll,
windll,
CFUNCTYPE,
WinError,
create_string_buffer,
Expand All @@ -30,6 +29,7 @@
from ctypes.wintypes import BOOL, HWND, WCHAR
import time
from winBindings.kernel32 import FreeLibrary
from winBindings import user32
import queueHandler
from logHandler import log
import winUser
Expand Down Expand Up @@ -1146,10 +1146,10 @@ def initialize():
):
enableBridge()
# Accept wm_copydata and any wm_user messages from other processes even if running with higher privileges
if not windll.user32.ChangeWindowMessageFilter(winUser.WM_COPYDATA, winUser.MSGFLT.ALLOW):
if not user32.ChangeWindowMessageFilter(winUser.WM_COPYDATA, winUser.MSGFLT.ALLOW):
raise WinError()
for msg in range(winUser.WM_USER + 1, 0xFFFF):
if not windll.user32.ChangeWindowMessageFilter(msg, winUser.MSGFLT.ALLOW):
if not user32.ChangeWindowMessageFilter(msg, winUser.MSGFLT.ALLOW):
raise WinError()
bridgeDll.Windows_run()
# Register java events
Expand Down
5 changes: 3 additions & 2 deletions source/NVDAHelper/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
wstring_at,
)

from winBindings import user32
import winBindings.oleaut32
import winBindings.kernel32
import winBindings.advapi32
Expand Down Expand Up @@ -774,11 +775,11 @@ def terminate(self):
def initialize() -> None:
global _remoteLib, _remoteLoaderX86, _remoteLoaderAMD64, _remoteLoaderARM64
global lastLanguageID, lastLayoutString
hkl = c_ulong(windll.User32.GetKeyboardLayout(0)).value
hkl = user32.GetKeyboardLayout(0)
lastLanguageID = winUser.LOWORD(hkl)
KL_NAMELENGTH = 9
buf = create_unicode_buffer(KL_NAMELENGTH)
res = windll.User32.GetKeyboardLayoutNameW(buf)
res = user32.GetKeyboardLayoutName(buf)
if res:
lastLayoutString = buf.value
for name, func in [
Expand Down
5 changes: 3 additions & 2 deletions source/NVDAObjects/IAccessible/MSHTML.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# NVDAObjects/MSHTML.py
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2006-2015 NV Access Limited, Aleksey Sadovoy
# Copyright (C) 2006-2025 NV Access Limited, Aleksey Sadovoy
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

Expand All @@ -11,6 +11,7 @@
import ctypes
import ctypes.wintypes
import contextlib
from winBindings import user32
import winUser
import oleacc
import UIAHandler
Expand Down Expand Up @@ -520,7 +521,7 @@ def kwargsFromSuper(cls, kwargs, relation=None):
elif isinstance(relation, tuple):
windowHandle = kwargs.get("windowHandle")
p = ctypes.wintypes.POINT(x=relation[0], y=relation[1])
ctypes.windll.user32.ScreenToClient(windowHandle, ctypes.byref(p))
user32.ScreenToClient(windowHandle, ctypes.byref(p))
# #3494: MSHTML's internal coordinates are always at a hardcoded DPI (usually 96) no matter the system DPI or zoom level.
xFactor, yFactor = getZoomFactorsFromHTMLDocument(HTMLNode.document)
try:
Expand Down
9 changes: 7 additions & 2 deletions source/NVDAObjects/IAccessible/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2006-2024 NV Access Limited, Babbage B.V., Cyrille Bougot
# Copyright (C) 2006-2025 NV Access Limited, Babbage B.V., Cyrille Bougot
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

Expand Down Expand Up @@ -40,6 +40,7 @@
import IAccessibleHandler
import oleacc
import JABHandler
from winBindings import user32
import winBindings.ole32
import winUser
import globalVars # noqa: F401
Expand Down Expand Up @@ -715,6 +716,10 @@ def findOverlayClasses(self, clsList):
from . import webKit

webKit.findExtraOverlayClasses(self, clsList)
elif windowClassName == "wxWindowNR":
from . import wx as wxObjects

wxObjects.findExtraOverlayClasses(self, clsList)
elif windowClassName.startswith("Chrome_"):
from . import chromium

Expand Down Expand Up @@ -805,7 +810,7 @@ def __init__( # noqa: C901
log.debugWarning("Resorting to WindowFromPoint on accLocation")
try:
left, top, width, height = IAccessibleObject.accLocation(0)
windowHandle = winUser.user32.WindowFromPoint(winUser.POINT(left, top))
windowHandle = user32.WindowFromPoint(winUser.POINT(left, top))
except COMError as e:
log.debugWarning("accLocation failed: %s" % e)
if not windowHandle:
Expand Down
10 changes: 5 additions & 5 deletions source/NVDAObjects/IAccessible/winword.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2006-2023 NV Access, Cyrille Bougot and other NVDA Contributors
# Copyright (C) 2006-2025 NV Access, Cyrille Bougot and other NVDA Contributors
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

Expand All @@ -8,7 +8,7 @@
import operator
import uuid
from logHandler import log
import winUser
from winBindings import user32
import speech
import controlTypes
import config
Expand Down Expand Up @@ -586,8 +586,8 @@ def event_gainFocus(self):
document = next((x for x in self.children if isinstance(x, WordDocument)), None)
if document:
curThreadID = ctypes.windll.kernel32.GetCurrentThreadId()
winUser.user32.AttachThreadInput(curThreadID, document.windowThreadID, True)
winUser.user32.SetFocus(document.windowHandle)
winUser.user32.AttachThreadInput(curThreadID, document.windowThreadID, False)
user32.AttachThreadInput(curThreadID, document.windowThreadID, True)
user32.SetFocus(document.windowHandle)
user32.AttachThreadInput(curThreadID, document.windowThreadID, False)
if not document.WinwordWindowObject.active:
document.WinwordWindowObject.activate()
66 changes: 66 additions & 0 deletions source/NVDAObjects/IAccessible/wx.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2025 NV Access Limited, Leonard de Ruijter
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

"""Improvements for wxWidgets objects."""

import api
import eventHandler
import winUser

from . import IAccessible, getNVDAObjectFromEvent
from logHandler import log
import windowUtils
from .. import NVDAObject


def findExtraOverlayClasses(obj: IAccessible, clsList: list[NVDAObject]):
if obj.name == "wxWebView":
clsList.insert(0, WxWebView)


class WxWebView(IAccessible):
def event_gainFocus(self) -> None:
super().event_gainFocus()
firstChild = self.firstChild
if not firstChild:
return
match firstChild.windowClassName:
case "Shell Embedding":
# This is an IE webview.
try:
obj = getNVDAObjectFromEvent(
windowUtils.findDescendantWindow(
firstChild.windowHandle,
className="Internet Explorer_Server",
),
winUser.OBJID_CLIENT,
winUser.CHILDID_SELF,
)
obj.setFocus()
except LookupError:
log.warning("Could not find Internet Explorer_Server in wxWebView")
case "Chrome_WidgetWin_0":
# This is a Edge WebView2 control.
# First focus might fail when the inner window is not yet created.
while not eventHandler.isPendingEvents("gainFocus"):
# Wait for the window and refocus when it is there.
try:
windowUtils.findDescendantWindow(
firstChild.windowHandle,
className="Chrome_RenderWidgetHostHWND",
)
except LookupError:
api.processPendingEvents()
continue
else:
# Suppress IAccessible focus event handling for the refocus below.
# This prevents duplicate or unwanted focus events of the Web View parent control,
# since we're actually interested in the focus event from the inner document that focus is propagated to.
self.shouldAllowIAccessibleFocusEvent = False
self.setFocus()
break

case _:
log.warning(f"Unexpected inner control in wxWebView: {firstChild.windowClassName}")
29 changes: 9 additions & 20 deletions source/NVDAObjects/window/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# A part of NonVisual Desktop Access (NVDA)
# Copyright (C) 2006-2023 NV Access Limited, Babbage B.V., Bill Dengler, Cyrille Bougot
# Copyright (C) 2006-2025 NV Access Limited, Babbage B.V., Bill Dengler, Cyrille Bougot
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.

import re
import ctypes
import ctypes.wintypes
from winBindings import user32
from winBindings.user32 import _GhostWindowFromHungWindow
import winKernel
import winUser
from logHandler import log # noqa: F401
Expand All @@ -23,16 +25,11 @@
re_WindowsForms = re.compile(r"^WindowsForms[0-9]*\.(.*)\.app\..*$")
re_ATL = re.compile(r"^ATL:(.*)$")

try:
GhostWindowFromHungWindow = ctypes.windll.user32.GhostWindowFromHungWindow
except AttributeError:
GhostWindowFromHungWindow = None


def isUsableWindow(windowHandle):
if not ctypes.windll.user32.IsWindowVisible(windowHandle):
if not user32.IsWindowVisible(windowHandle):
return False
if GhostWindowFromHungWindow and ctypes.windll.user32.GhostWindowFromHungWindow(windowHandle):
if _GhostWindowFromHungWindow is not None and _GhostWindowFromHungWindow(windowHandle):
return False
return True

Expand Down Expand Up @@ -60,14 +57,6 @@ def __del__(self):
winKernel.closeHandle(self.processHandle)


# We want to work with physical points.
try:
# Windows >= Vista
_windowFromPoint = ctypes.windll.user32.WindowFromPhysicalPoint
except AttributeError:
_windowFromPoint = ctypes.windll.user32.WindowFromPoint


class Window(NVDAObject):
"""
An NVDAObject for a window
Expand All @@ -91,7 +80,7 @@ def getPossibleAPIClasses(cls, kwargs, relation=None):
if windowClassName == "#32769":
return
# If this window has a ghost window its too dangerous to try any higher APIs
if GhostWindowFromHungWindow and GhostWindowFromHungWindow(windowHandle):
if _GhostWindowFromHungWindow is not None and _GhostWindowFromHungWindow(windowHandle):
return
if windowClassName == "EXCEL7" and (relation == "focus" or isinstance(relation, tuple)):
from . import excel
Expand Down Expand Up @@ -169,7 +158,7 @@ def kwargsFromSuper(cls, kwargs, relation=None):
if threadInfo.hwndFocus:
windowHandle = threadInfo.hwndFocus
elif isinstance(relation, tuple):
windowHandle = _windowFromPoint(ctypes.wintypes.POINT(relation[0], relation[1]))
windowHandle = user32.WindowFromPhysicalPoint(ctypes.wintypes.POINT(relation[0], relation[1]))
if not windowHandle:
return False
kwargs["windowHandle"] = windowHandle
Expand Down Expand Up @@ -207,7 +196,7 @@ def _get_windowControlID(self):

def _get_location(self):
r = ctypes.wintypes.RECT()
ctypes.windll.user32.GetWindowRect(self.windowHandle, ctypes.byref(r))
user32.GetWindowRect(self.windowHandle, ctypes.byref(r))
return RectLTWH.fromCompatibleType(r)

def _get_displayText(self):
Expand Down Expand Up @@ -310,7 +299,7 @@ def _get_extendedWindowStyle(self):

def _get_isWindowUnicode(self):
if not hasattr(self, "_isWindowUnicode"):
self._isWindowUnicode = bool(ctypes.windll.user32.IsWindowUnicode(self.windowHandle))
self._isWindowUnicode = bool(user32.IsWindowUnicode(self.windowHandle))
return self._isWindowUnicode

def correctAPIForRelation(self, obj, relation=None):
Expand Down
Loading
Loading