Skip to content

Add LINE version detection and ChatWindow mode support#52

Merged
keyang556 merged 1 commit intomainfrom
claude/amazing-cray
Apr 12, 2026
Merged

Add LINE version detection and ChatWindow mode support#52
keyang556 merged 1 commit intomainfrom
claude/amazing-cray

Conversation

@keyang556
Copy link
Copy Markdown
Owner

@keyang556 keyang556 commented Apr 12, 2026

Summary

  • Read LINE version and language from local INI files (LINE.ini, installLang.ini) at addon startup for debugging and diagnostic logging
  • Classify window type (AllInOneWindow vs standalone ChatWindow) to properly handle UI layout differences
  • Fix sidebar heuristics in ChatWindow mode where there is no chat list sidebar
  • Add diagnostic shortcut (NVDA+Shift+V) to report LINE version, language, and window type

Changes

New Functions

  • _getLineDataDir() — Locate LINE data directory
  • _readLineVersion() — Extract version from LINE.ini
  • _readLineLanguage() — Extract language from installLang.ini
  • _classifyLineWindow() — Detect AllInOneWindow vs ChatWindow mode with caching
  • _isChatWindowMode() — Helper to check if current window is ChatWindow

Improved Logic

  • _isInChatListContext() — Skip sidebar checks in ChatWindow mode
  • _findChatListFromWindow() — Return early if in ChatWindow (no sidebar to search)
  • _queryAndSpeakUIAFocus() — Treat all ListItems as message items in ChatWindow mode

AppModule Updates

  • Cache LINE version and language on init, log to debug output
  • New script: script_reportLineInfo() (NVDA+Shift+V) to announce version, language, and window type

Test Plan

  • Verify syntax with Python AST parser
  • Test version reading from LINE.ini (returns "26.1.0.3865")
  • Test language reading from installLang.ini (returns "zh-TW")
  • Confirm window classification detects AllInOneWindow (width ≥600px, multiple panes)
  • Test ChatWindow detection for standalone chat windows
  • Verify NVDA+Shift+V reports version and window type
  • Ensure sidebar heuristics are skipped in ChatWindow mode

🤖 Generated with Claude Code

Greptile Summary

此 PR 為 LINE Desktop NVDA 附加元件新增 LINE 版本與語言的診斷性讀取(從本地 INI 檔案)、視窗類型分類(AllInOneWindow 與 ChatWindow),以及 NVDA+Shift+V 診斷快捷鍵。ChatWindow 模式下側邊欄相關啟發式判斷也會提前返回或改用更合適的邏輯。

主要變更摘要:

  • 新增 _getLineDataDir()_readLineVersion()_readLineLanguage() 以讀取 LINE 安裝資訊作為診斷用途
  • 新增 _classifyLineWindow() 以寬度啟發式 + UIA 窗格計數判斷視窗類型,並快取 2 秒
  • _isInChatListContext_findChatListFromWindow_queryAndSpeakUIAFocus 中加入 ChatWindow 模式早期返回路徑
  • AppModule.__init__ 快取版本與語言,並新增 script_reportLineInfo(NVDA+Shift+V)

尚待處理的問題: 多個前次審查所提問題目前仍存在於程式碼中(walker 未使用、TreeScope.Children 深度不足、寬 ChatWindow 回退誤判、INI 區段名稱大小寫)。此外本次審查發現:當 UIAHandler.handlerNoneElementFromHandle 回傳 None 時,寬視窗的 windowType 會維持 "unknown" 而非回退到 "allinone",導致 copy-first 讀取功能在邊緣情況下失效。

Confidence Score: 2/5

PR 的核心 ChatWindow 分類邏輯存在已知與新發現的準確性問題,視窗分類在特定情況下會退化,主要功能尚未穩定。

多個來自前次審查的 P1 問題(INI 區段大小寫、UIA 搜尋深度不足、寬 ChatWindow 回退誤判)仍存在於程式碼中未獲解決,加上本次新發現的 handler/rootEl 為 None 時 windowType 維持 unknown 的邏輯缺口,使視窗分類在邊緣情況下無法正確觸發 ChatWindow 路徑。

addon/appModules/line.py:特別關注 _classifyLineWindow() 中的 handler/rootEl None 路徑處理(第 135–153 行)

Important Files Changed

Filename Overview
addon/appModules/line.py 新增 LINE 版本/語言偵測、ChatWindow 模式分類與診斷快捷鍵;視窗分類的 UIA None 路徑處理缺失,寬視窗可能被誤標為 "unknown"

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[_classifyLineWindow 被呼叫] --> B{快取命中?}
    B -- 是 --> C[返回快取結果]
    B -- 否 --> D[GetWindowRect 取得 winWidth]
    D --> E{winWidth >= 600?}
    E -- 否 --> F[windowType = chat]
    E -- 是 --> G{UIAHandler.handler 非 None?}
    G -- 否 --> H[windowType 維持 unknown]
    G -- 是 --> I[ElementFromHandle 取得 rootEl]
    I --> J{rootEl 非 None?}
    J -- 否 --> K[windowType 維持 unknown]
    J -- 是 --> L[FindAll Pane 取得 paneCount]
    L --> M{paneCount >= 2?}
    M -- 是 --> N[windowType = allinone]
    M -- 否 --> O{winWidth >= 700?}
    O -- 是 --> P[windowType = allinone]
    O -- 否 --> Q[windowType = chat]
    H --> R[快取結果]
    K --> R
    F --> R
    N --> R
    P --> R
    Q --> R
Loading

Fix All in Claude Code Fix All in Codex

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

Comment:
**`handler``rootEl``None` 時寬視窗被誤分類為 `"unknown"`**`UIAHandler.handler``None`(UIA 尚未就緒)或 `ElementFromHandle` 返回 `None` 時,對應的 `if` 分支直接被跳過,不會觸發 `except Exception`,因此 `windowType` 維持初始值 `"unknown"`。

對於寬度 ≥ 600px 的視窗,開發者的意圖是回退到 `"allinone"`(如 `except` 分支的注釋所示)。但 `handler is None``rootEl is None` 這兩個路徑繞過了 `except`,直接讓 `windowType = "unknown"` 被快取。後續 `_isChatWindowMode()` 回傳 `False`,導致 `_queryAndSpeakUIAFocus` 中 ChatWindow 的訊息項目回退到位置像素判斷,`isMessageItem` 永遠為 `False`,copy-first 讀取功能失效。

建議明確處理 `None` 路徑:

```python
handler = UIAHandler.handler
if handler:
    rootCond = handler.clientObject.CreatePropertyCondition(
        30003, 50033  # ControlType == Pane
    )
    rootEl = handler.clientObject.ElementFromHandle(hwnd)
    if rootEl:
        panes = rootEl.FindAll(2, rootCond)
        paneCount = panes.Length if panes else 0
        if paneCount >= 2:
            windowType = "allinone"
        else:
            windowType = "allinone" if winWidth >= 700 else "chat"
    else:
        windowType = "allinone" if winWidth >= 700 else "chat"
else:
    windowType = "allinone"
```

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

Reviews (7): Last reviewed commit: "Add LINE version detection and ChatWindo..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Read LINE version and language from local INI files at startup for
debugging. Classify the active window as AllInOneWindow (with sidebar)
or standalone ChatWindow (no sidebar) so sidebar heuristics are skipped
in ChatWindow mode, fixing misidentification of message list items.
Add NVDA+Shift+V shortcut to report LINE version and window type.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment on lines +60 to +62
parser = configparser.ConfigParser()
parser.read(iniPath, encoding="utf-8")
return parser.get("global", "last_updated_version", fallback=None)
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 INI 區段名稱大小寫可能不符

configparser.ConfigParser 預設對區段名稱(section)不做大小寫折疊(只對選項名稱做 str.lower)。如果 LINE.ini 實際上使用 [Global](首字大寫,Windows INI 慣例),parser.get("global", ...) 將找不到該區段並靜默回傳 None,導致版本號永遠顯示為「未知」。

建議在解析前統一降低區段名稱的大小寫,或遍歷所有區段找到正確的名稱:

Suggested change
parser = configparser.ConfigParser()
parser.read(iniPath, encoding="utf-8")
return parser.get("global", "last_updated_version", fallback=None)
parser = configparser.ConfigParser()
parser.read(iniPath, encoding="utf-8")
# Try both common casings for the section name
for section in ("global", "Global", "GLOBAL"):
if parser.has_section(section):
return parser.get(section, "last_updated_version", fallback=None)
return None
Prompt To Fix With AI
This is a comment left during a code review.
Path: addon/appModules/line.py
Line: 60-62

Comment:
**INI 區段名稱大小寫可能不符**

`configparser.ConfigParser` 預設對**區段名稱(section)不做大小寫折疊**(只對選項名稱做 `str.lower`)。如果 LINE.ini 實際上使用 `[Global]`(首字大寫,Windows INI 慣例),`parser.get("global", ...)` 將找不到該區段並靜默回傳 `None`,導致版本號永遠顯示為「未知」。

建議在解析前統一降低區段名稱的大小寫,或遍歷所有區段找到正確的名稱:

```suggestion
		parser = configparser.ConfigParser()
		parser.read(iniPath, encoding="utf-8")
		# Try both common casings for the section name
		for section in ("global", "Global", "GLOBAL"):
			if parser.has_section(section):
				return parser.get(section, "last_updated_version", fallback=None)
		return None
```

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

Fix in Claude Code Fix in Codex

@keyang556
Copy link
Copy Markdown
Owner Author

@greptileai global是小寫

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 12, 2026

了解,LINE.ini 的 section 名稱確實是小寫 [global],所以 parser.get("global", "last_updated_version", ...) 是正確的寫法,沒有問題。

不過這與我標記的主要問題無關——第 137–138 行的重複 CreatePropertyCondition 呼叫才是需要修正的核心 bug:

# 目前(有 bug):
rootCond = handler.clientObject.CreatePropertyCondition(
rootCond = handler.clientObject.CreatePropertyCondition(  # ← 這行是多餘的
    30003, 50033
)

移除重複行後應為:

rootCond = handler.clientObject.CreatePropertyCondition(
    30003, 50033  # ControlType == Pane
)

這個重複行導致 UIA 窗格偵測永遠丟出例外,ChatWindow 的核心判斷邏輯因此完全失效。

@keyang556 keyang556 force-pushed the claude/amazing-cray branch from 8d4f5b4 to e0c0774 Compare April 12, 2026 08:29
@keyang556
Copy link
Copy Markdown
Owner Author

@greptileai 要怎麼修正

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 12, 2026

_classifyLineWindow 函式中,if handler: 後缺少 else 分支。當 UIAHandler.handlerNone 時,windowType 維持初始值 "unknown" 而不是回退到 "allinone"

修正方式是在 if handler: 後加上明確的 else

		if winWidth >= 600:
			# Likely AllInOneWindow, but could be a wide ChatWindow.
			# Check for the presence of multiple pane children (sidebar + chat)
			# via quick UIA check.
			try:
				handler = UIAHandler.handler
				if handler:
					rootCond = handler.clientObject.CreatePropertyCondition(
						30003, 50033  # ControlType == Pane
					)
					rootEl = handler.clientObject.ElementFromHandle(hwnd)
					if rootEl:
						panes = rootEl.FindAll(2, rootCond)  # TreeScope.Children=2
						paneCount = panes.Length if panes else 0
						if paneCount >= 2:
							windowType = "allinone"
						else:
							# Single pane or no panes — could be ChatWindow
							# at larger size
							windowType = "allinone" if winWidth >= 700 else "chat"
				else:
					# UIA handler not ready — fall back to width heuristic
					windowType = "allinone"
			except Exception:
				# Fallback: wide window is likely AllInOneWindow
				windowType = "allinone"
		else:
			windowType = "chat"

這樣一來,handlerNone 時的行為與 except 的回退邏輯一致,都回傳 "allinone"

@keyang556 keyang556 force-pushed the claude/amazing-cray branch from bef3360 to e0c0774 Compare April 12, 2026 08:56
Comment on lines +135 to +153
handler = UIAHandler.handler
if handler:
walker = handler.clientObject.RawViewWalker
rootCond = handler.clientObject.CreatePropertyCondition(
30003, 50033 # ControlType == Pane
)
rootEl = handler.clientObject.ElementFromHandle(hwnd)
if rootEl:
panes = rootEl.FindAll(2, rootCond) # TreeScope.Children=2
paneCount = panes.Length if panes else 0
if paneCount >= 2:
windowType = "allinone"
else:
# Single pane or no panes — could be ChatWindow
# at larger size
windowType = "allinone" if winWidth >= 700 else "chat"
except Exception:
# Fallback: wide window is likely AllInOneWindow
windowType = "allinone"
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 handlerrootElNone 時寬視窗被誤分類為 "unknown"

UIAHandler.handlerNone(UIA 尚未就緒)或 ElementFromHandle 返回 None 時,對應的 if 分支直接被跳過,不會觸發 except Exception,因此 windowType 維持初始值 "unknown"

對於寬度 ≥ 600px 的視窗,開發者的意圖是回退到 "allinone"(如 except 分支的注釋所示)。但 handler is NonerootEl is None 這兩個路徑繞過了 except,直接讓 windowType = "unknown" 被快取。後續 _isChatWindowMode() 回傳 False,導致 _queryAndSpeakUIAFocus 中 ChatWindow 的訊息項目回退到位置像素判斷,isMessageItem 永遠為 False,copy-first 讀取功能失效。

建議明確處理 None 路徑:

handler = UIAHandler.handler
if handler:
    rootCond = handler.clientObject.CreatePropertyCondition(
        30003, 50033  # ControlType == Pane
    )
    rootEl = handler.clientObject.ElementFromHandle(hwnd)
    if rootEl:
        panes = rootEl.FindAll(2, rootCond)
        paneCount = panes.Length if panes else 0
        if paneCount >= 2:
            windowType = "allinone"
        else:
            windowType = "allinone" if winWidth >= 700 else "chat"
    else:
        windowType = "allinone" if winWidth >= 700 else "chat"
else:
    windowType = "allinone"
Prompt To Fix With AI
This is a comment left during a code review.
Path: addon/appModules/line.py
Line: 135-153

Comment:
**`handler``rootEl``None` 時寬視窗被誤分類為 `"unknown"`**`UIAHandler.handler``None`(UIA 尚未就緒)或 `ElementFromHandle` 返回 `None` 時,對應的 `if` 分支直接被跳過,不會觸發 `except Exception`,因此 `windowType` 維持初始值 `"unknown"`。

對於寬度 ≥ 600px 的視窗,開發者的意圖是回退到 `"allinone"`(如 `except` 分支的注釋所示)。但 `handler is None``rootEl is None` 這兩個路徑繞過了 `except`,直接讓 `windowType = "unknown"` 被快取。後續 `_isChatWindowMode()` 回傳 `False`,導致 `_queryAndSpeakUIAFocus` 中 ChatWindow 的訊息項目回退到位置像素判斷,`isMessageItem` 永遠為 `False`,copy-first 讀取功能失效。

建議明確處理 `None` 路徑:

```python
handler = UIAHandler.handler
if handler:
    rootCond = handler.clientObject.CreatePropertyCondition(
        30003, 50033  # ControlType == Pane
    )
    rootEl = handler.clientObject.ElementFromHandle(hwnd)
    if rootEl:
        panes = rootEl.FindAll(2, rootCond)
        paneCount = panes.Length if panes else 0
        if paneCount >= 2:
            windowType = "allinone"
        else:
            windowType = "allinone" if winWidth >= 700 else "chat"
    else:
        windowType = "allinone" if winWidth >= 700 else "chat"
else:
    windowType = "allinone"
```

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

Fix in Claude Code Fix in Codex

@keyang556
Copy link
Copy Markdown
Owner Author

繼續修正有bug,暫時先這樣

@keyang556 keyang556 merged commit 40ab349 into main Apr 12, 2026
4 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant