Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
86 changes: 85 additions & 1 deletion addon/appModules/line.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import re
import time
import configparser
import winreg
from ._virtualWindow import VirtualWindow
import addonHandler

Expand Down Expand Up @@ -83,6 +84,69 @@ def _readLineLanguage():
return None


# ---------------------------------------------------------------------------
# Qt accessibility environment variable management
# ---------------------------------------------------------------------------

_QT_ACCESSIBILITY_ENV_NAME = "QT_ACCESSIBILITY"
_HWND_BROADCAST = 0xFFFF
_WM_SETTINGCHANGE = 0x001A
_SMTO_ABORTIFHUNG = 0x0002


def _isQtAccessibleSet():
"""Check if QT_ACCESSIBILITY=1 is set in user environment variables.

Returns True if the variable is set to '1', False otherwise.
"""
try:
with winreg.OpenKey(
winreg.HKEY_CURRENT_USER, "Environment", 0, winreg.KEY_READ
) as key:
value, _ = winreg.QueryValueEx(key, _QT_ACCESSIBILITY_ENV_NAME)
return str(value) == "1"
except FileNotFoundError:
return False
except Exception:
log.debugWarning("Failed to read QT_ACCESSIBILITY from registry", exc_info=True)
return False


def _setQtAccessible(enable=True):
"""Set or remove QT_ACCESSIBILITY in user environment variables.

Writes to HKCU\\Environment so the setting persists across reboots.
Broadcasts WM_SETTINGCHANGE so new processes pick up the change.
Returns True on success, False on failure.
"""
try:
with winreg.OpenKey(
winreg.HKEY_CURRENT_USER, "Environment", 0,
winreg.KEY_SET_VALUE | winreg.KEY_READ
) as key:
if enable:
winreg.SetValueEx(
key, _QT_ACCESSIBILITY_ENV_NAME, 0,
winreg.REG_SZ, "1"
)
log.info("QT_ACCESSIBILITY=1 set in user environment")
else:
try:
Comment on lines +119 to +134
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 HKCU\Environment 不存在時操作會靜默失敗

_setQtAccessible 使用 winreg.OpenKey 開啟已存在的機碼。若 HKCU\Environment 不存在(例如極度精簡的 Windows 環境或 Wine),函式會進入 except Exception 並回傳 False,使用者只會看到錯誤訊息,而不知道原因是 key 不存在。

建議改用 winreg.CreateKeyEx,它在 key 已存在時等同 OpenKey,在不存在時則自動建立:

with winreg.CreateKeyEx(
    winreg.HKEY_CURRENT_USER, "Environment", 0,
    winreg.KEY_SET_VALUE | winreg.KEY_READ
) as key:

這樣可同時處理 key 不存在與已存在的情況。此修改在 line.pylineDesktopHelper.py 兩處的 _setQtAccessible 都應套用。

Prompt To Fix With AI
This is a comment left during a code review.
Path: addon/appModules/line.py
Line: 119-134

Comment:
**`HKCU\Environment` 不存在時操作會靜默失敗**

`_setQtAccessible` 使用 `winreg.OpenKey` 開啟已存在的機碼。若 `HKCU\Environment` 不存在(例如極度精簡的 Windows 環境或 Wine),函式會進入 `except Exception` 並回傳 `False`,使用者只會看到錯誤訊息,而不知道原因是 key 不存在。

建議改用 `winreg.CreateKeyEx`,它在 key 已存在時等同 `OpenKey`,在不存在時則自動建立:

```python
with winreg.CreateKeyEx(
    winreg.HKEY_CURRENT_USER, "Environment", 0,
    winreg.KEY_SET_VALUE | winreg.KEY_READ
) as key:
```

這樣可同時處理 key 不存在與已存在的情況。此修改在 `line.py``lineDesktopHelper.py` 兩處的 `_setQtAccessible` 都應套用。

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex

winreg.DeleteValue(key, _QT_ACCESSIBILITY_ENV_NAME)
log.info("QT_ACCESSIBILITY removed from user environment")
except FileNotFoundError:
pass
# Broadcast environment change to all windows
ctypes.windll.user32.SendMessageTimeoutW(
_HWND_BROADCAST, _WM_SETTINGCHANGE, 0,
"Environment", _SMTO_ABORTIFHUNG, 5000, None
)
return True
except Exception:
log.warning("Failed to set QT_ACCESSIBILITY in registry", exc_info=True)
return False


# Window type classification —
# LINE has two main window modes:
# "AllInOneWindow" — sidebar (chat list) + chat area in one window
Expand Down Expand Up @@ -3481,11 +3545,13 @@ def __init__(self, *args, **kwargs):
# Read and cache LINE installation info
self._lineVersion = _readLineVersion()
self._lineLanguage = _readLineLanguage()
self._qtAccessibleSet = _isQtAccessibleSet()
log.info(
f"LINE AppModule loaded for process: {self.processID}, "
f"exe: {self.appName}, "
f"lineVersion: {self._lineVersion}, "
f"lineLanguage: {self._lineLanguage}"
f"lineLanguage: {self._lineLanguage}, "
f"qtAccessible: {self._qtAccessibleSet}"
)

def chooseNVDAObjectOverlayClasses(self, obj, clsList):
Expand Down Expand Up @@ -6010,10 +6076,28 @@ def script_reportLineInfo(self, gesture):
parts.append(_("視窗類型: {type}").format(
type=typeNames.get(winType, winType)
))
qtA11y = _isQtAccessibleSet()
parts.append(
_("Qt 無障礙: 已啟用") if qtA11y else _("Qt 無障礙: 未啟用")
)
msg = ", ".join(parts)
ui.message(msg)
log.info(f"LINE info: {msg}")

def script_toggleQtAccessible(self, gesture):
"""Toggle QT_ACCESSIBILITY=1 user environment variable."""
currentlySet = _isQtAccessibleSet()
if currentlySet:
if _setQtAccessible(False):
ui.message(_("已移除 QT_ACCESSIBILITY 環境變數,重啟 LINE 後生效"))
else:
ui.message(_("移除 QT_ACCESSIBILITY 環境變數失敗"))
else:
if _setQtAccessible(True):
ui.message(_("已設定 QT_ACCESSIBILITY=1,重啟 LINE 後生效"))
else:
ui.message(_("設定 QT_ACCESSIBILITY 環境變數失敗"))
Comment on lines +6087 to +6099
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 缺少 @script 裝飾器,快捷鍵無法運作

script_toggleQtAccessible 雖然遵循 NVDA 的 script_* 命名慣例,但完全缺少 @script 裝飾器與 gesture 宣告。如此一來,NVDA 不會將任何鍵盤手勢綁定到此函式,使用者無論按下 NVDA+Shift+A 或其他任何組合鍵,都不會有任何反應。

PR 說明明確列出「Keyboard shortcut: NVDA+Shift+A to toggle the setting」,但搜尋整個程式碼庫確認此手勢並未被宣告於任何地方。

對照同一檔案內其他腳本(如 script_reportLineInfo),正確的寫法應為:

Suggested change
def script_toggleQtAccessible(self, gesture):
"""Toggle QT_ACCESSIBILITY=1 user environment variable."""
currentlySet = _isQtAccessibleSet()
if currentlySet:
if _setQtAccessible(False):
ui.message(_("已移除 QT_ACCESSIBILITY 環境變數,重啟 LINE 後生效"))
else:
ui.message(_("移除 QT_ACCESSIBILITY 環境變數失敗"))
else:
if _setQtAccessible(True):
ui.message(_("已設定 QT_ACCESSIBILITY=1,重啟 LINE 後生效"))
else:
ui.message(_("設定 QT_ACCESSIBILITY 環境變數失敗"))
@script(
# Translators: Description of a script to toggle Qt accessibility env var
description=_("切換 Qt 無障礙環境變數"),
gesture="kb:NVDA+shift+a",
category="LINE Desktop",
)
def script_toggleQtAccessible(self, gesture):
"""Toggle QT_ACCESSIBILITY=1 user environment variable."""
currentlySet = _isQtAccessibleSet()
if currentlySet:
if _setQtAccessible(False):
ui.message(_("已移除 QT_ACCESSIBILITY 環境變數,重啟 LINE 後生效"))
else:
ui.message(_("移除 QT_ACCESSIBILITY 環境變數失敗"))
else:
if _setQtAccessible(True):
ui.message(_("已設定 QT_ACCESSIBILITY=1,重啟 LINE 後生效"))
else:
ui.message(_("設定 QT_ACCESSIBILITY 環境變數失敗"))

補上裝飾器後,也建議在選單項目標籤中加入快捷鍵提示(+ "\tNVDA+Shift+A"),與其他選單項目保持一致。

Prompt To Fix With AI
This is a comment left during a code review.
Path: addon/appModules/line.py
Line: 6087-6099

Comment:
**缺少 `@script` 裝飾器,快捷鍵無法運作**

`script_toggleQtAccessible` 雖然遵循 NVDA 的 `script_*` 命名慣例,但完全缺少 `@script` 裝飾器與 `gesture` 宣告。如此一來,NVDA 不會將任何鍵盤手勢綁定到此函式,使用者無論按下 `NVDA+Shift+A` 或其他任何組合鍵,都不會有任何反應。

PR 說明明確列出「**Keyboard shortcut: NVDA+Shift+A to toggle the setting**」,但搜尋整個程式碼庫確認此手勢並未被宣告於任何地方。

對照同一檔案內其他腳本(如 `script_reportLineInfo`),正確的寫法應為:

```suggestion
	@script(
		# Translators: Description of a script to toggle Qt accessibility env var
		description=_("切換 Qt 無障礙環境變數"),
		gesture="kb:NVDA+shift+a",
		category="LINE Desktop",
	)
	def script_toggleQtAccessible(self, gesture):
		"""Toggle QT_ACCESSIBILITY=1 user environment variable."""
		currentlySet = _isQtAccessibleSet()
		if currentlySet:
			if _setQtAccessible(False):
				ui.message(_("已移除 QT_ACCESSIBILITY 環境變數,重啟 LINE 後生效"))
			else:
				ui.message(_("移除 QT_ACCESSIBILITY 環境變數失敗"))
		else:
			if _setQtAccessible(True):
				ui.message(_("已設定 QT_ACCESSIBILITY=1,重啟 LINE 後生效"))
			else:
				ui.message(_("設定 QT_ACCESSIBILITY 環境變數失敗"))
```

補上裝飾器後,也建議在選單項目標籤中加入快捷鍵提示(`+ "\tNVDA+Shift+A"`),與其他選單項目保持一致。

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex


def _pollFileDialog(self):
"""Poll to detect when the file dialog closes, then resume addon.

Expand Down
88 changes: 88 additions & 0 deletions addon/globalPlugins/lineDesktopHelper.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,65 @@
from logHandler import log
import gui
import wx
import winreg
import ctypes
import addonHandler

addonHandler.initTranslation()


# ---------------------------------------------------------------------------
# Qt accessibility environment variable helpers (duplicated from line.py
# so the global plugin can toggle the setting even when LINE is not running)
# ---------------------------------------------------------------------------

_QT_ACCESSIBILITY_ENV_NAME = "QT_ACCESSIBILITY"
_HWND_BROADCAST = 0xFFFF
_WM_SETTINGCHANGE = 0x001A
_SMTO_ABORTIFHUNG = 0x0002


def _isQtAccessibleSet():
"""Check if QT_ACCESSIBILITY=1 is set in user environment variables."""
try:
with winreg.OpenKey(
winreg.HKEY_CURRENT_USER, "Environment", 0, winreg.KEY_READ
) as key:
value, _ = winreg.QueryValueEx(key, _QT_ACCESSIBILITY_ENV_NAME)
return str(value) == "1"
except FileNotFoundError:
return False
except Exception:
return False
Comment on lines +29 to +40
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 未記錄非預期例外

lineDesktopHelper.py_isQtAccessibleSet 在最後的 except Exception 中靜默忽略所有非 FileNotFoundError 的例外,沒有任何日誌紀錄,使除錯更加困難。相較之下,line.py 的同名函式有記錄 debugWarning

建議對齊兩個實作的行為:

Suggested change
def _isQtAccessibleSet():
"""Check if QT_ACCESSIBILITY=1 is set in user environment variables."""
try:
with winreg.OpenKey(
winreg.HKEY_CURRENT_USER, "Environment", 0, winreg.KEY_READ
) as key:
value, _ = winreg.QueryValueEx(key, _QT_ACCESSIBILITY_ENV_NAME)
return str(value) == "1"
except FileNotFoundError:
return False
except Exception:
return False
except Exception:
log.debugWarning("Failed to read QT_ACCESSIBILITY from registry", exc_info=True)
return False
Prompt To Fix With AI
This is a comment left during a code review.
Path: addon/globalPlugins/lineDesktopHelper.py
Line: 29-40

Comment:
**未記錄非預期例外**

`lineDesktopHelper.py``_isQtAccessibleSet` 在最後的 `except Exception` 中靜默忽略所有非 `FileNotFoundError` 的例外,沒有任何日誌紀錄,使除錯更加困難。相較之下,`line.py` 的同名函式有記錄 `debugWarning`。

建議對齊兩個實作的行為:

```suggestion
	except Exception:
		log.debugWarning("Failed to read QT_ACCESSIBILITY from registry", exc_info=True)
		return False
```

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex



def _setQtAccessible(enable=True):
"""Set or remove QT_ACCESSIBILITY in user environment variables."""
try:
with winreg.OpenKey(
winreg.HKEY_CURRENT_USER, "Environment", 0,
winreg.KEY_SET_VALUE | winreg.KEY_READ
) as key:
if enable:
winreg.SetValueEx(
key, _QT_ACCESSIBILITY_ENV_NAME, 0,
winreg.REG_SZ, "1"
)
else:
try:
winreg.DeleteValue(key, _QT_ACCESSIBILITY_ENV_NAME)
except FileNotFoundError:
pass
ctypes.windll.user32.SendMessageTimeoutW(
_HWND_BROADCAST, _WM_SETTINGCHANGE, 0,
"Environment", _SMTO_ABORTIFHUNG, 5000, None
)
return True
except Exception:
Comment on lines +18 to +65
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 輔助函式重複定義

_isQtAccessibleSet_setQtAccessible 及所有常數(_QT_ACCESSIBILITY_ENV_NAME_HWND_BROADCAST 等)都與 line.py 中的定義完全相同,程式碼注釋也坦承是「duplicated from line.py」。這造成維護負擔:若日後需修改邏輯(例如改用 CreateKeyEx 處理 key 不存在的邊緣案例),必須同步更新兩個地方。

建議將共用邏輯提取至 addon/appModules/_qtAccessibility.py(或 addon/lib/qtAccessibility.py),讓兩個模組都能 import 相同的實作,同時保留 GlobalPlugin 在 LINE 未執行時仍可作業的能力。

Prompt To Fix With AI
This is a comment left during a code review.
Path: addon/globalPlugins/lineDesktopHelper.py
Line: 18-65

Comment:
**輔助函式重複定義**

`_isQtAccessibleSet``_setQtAccessible` 及所有常數(`_QT_ACCESSIBILITY_ENV_NAME``_HWND_BROADCAST` 等)都與 `line.py` 中的定義完全相同,程式碼注釋也坦承是「duplicated from line.py」。這造成維護負擔:若日後需修改邏輯(例如改用 `CreateKeyEx` 處理 key 不存在的邊緣案例),必須同步更新兩個地方。

建議將共用邏輯提取至 `addon/appModules/_qtAccessibility.py`(或 `addon/lib/qtAccessibility.py`),讓兩個模組都能 import 相同的實作,同時保留 GlobalPlugin 在 LINE 未執行時仍可作業的能力。

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex

log.warning("Failed to set QT_ACCESSIBILITY in registry", exc_info=True)
return False


def _getLineAppModule():
"""Find and return the LINE appModule instance, or None."""
for app in appModuleHandler.runningTable.values():
Expand Down Expand Up @@ -144,6 +198,15 @@ def _createToolsMenu(self):
_("跳到通話視窗(&F)") + "\tNVDA+Windows+F",
)

self._lineSubMenu.AppendSeparator()

# ── Settings ──
self._qtAccessibleItem = self._lineSubMenu.Append(
wx.ID_ANY,
# Translators: Menu item for toggling Qt accessibility env var
_("切換 Qt 無障礙環境變數(&Q)"),
)
Comment thread
greptile-apps[bot] marked this conversation as resolved.

# Bind events
gui.mainFrame.sysTrayIcon.Bind(
wx.EVT_MENU, self._onAllChats, self._allChatsItem
Expand Down Expand Up @@ -187,6 +250,9 @@ def _createToolsMenu(self):
gui.mainFrame.sysTrayIcon.Bind(
wx.EVT_MENU, self._onFocusCallWindow, self._focusCallItem
)
gui.mainFrame.sysTrayIcon.Bind(
wx.EVT_MENU, self._onToggleQtAccessible, self._qtAccessibleItem
)

# Add the submenu to NVDA's Tools menu
self._toolsMenu = gui.mainFrame.sysTrayIcon.toolsMenu
Expand Down Expand Up @@ -429,6 +495,28 @@ def _announceCallWindow():
log.warning(f"LINE focusCallWindow error: {e}", exc_info=True)
ui.message(_("跳到通話視窗功能錯誤: {error}").format(error=e))

def _onToggleQtAccessible(self, evt):
wx.CallAfter(self._doToggleQtAccessible)

def _doToggleQtAccessible(self):
import ui
lineApp = _getLineAppModule()
if lineApp and hasattr(lineApp, 'script_toggleQtAccessible'):
lineApp.script_toggleQtAccessible(None)
return
# LINE not running — toggle the env var directly
currentlySet = _isQtAccessibleSet()
if currentlySet:
if _setQtAccessible(False):
ui.message(_("已移除 QT_ACCESSIBILITY 環境變數,重啟 LINE 後生效"))
else:
ui.message(_("移除 QT_ACCESSIBILITY 環境變數失敗"))
else:
if _setQtAccessible(True):
ui.message(_("已設定 QT_ACCESSIBILITY=1,重啟 LINE 後生效"))
else:
ui.message(_("設定 QT_ACCESSIBILITY 環境變數失敗"))

def terminate(self, *args, **kwargs):
self._removeToolsMenu()
super().terminate(*args, **kwargs)
Expand Down
Loading