🎙️ Lecture 14: 【入門專案】打造你的第一個全域外掛:系統情報員 (System Info Announcer)

NOTE作者資訊 (Author Info)

🎧 錄音室開場:Vibe Coding 實戰工坊正式開張!


🎯 專案一目標:系統情報員 (System Info Announcer)

當使用者按下快捷鍵 NVDA+Shift+I 時,外掛會自動朗讀出「目前的 NVDA 版本」以及「現在的時間」。


🧠 大大導演的思維課:三步驟需求拆解與 Prompt 翻譯法

當你有一個點子時,絕對不要直接對 AI 說:「幫我寫一個報時的外掛」。這樣含糊的指令會讓 AI 自由發揮,進而寫出充滿 Bug 的程式碼。你必須依照以下三步驟進行拆解:

📋 步驟一:拆解你的「無障礙需求」

你要把點子切成三個黃金問題: 1. 觸發時機(什麼時候發動?):我是要按快捷鍵?還是在開啟某個軟體時自動發動? * 本專案答案:按快捷鍵 NVDA+Shift+I。 2. 資訊來源(我要去哪裡抓資料?):我是要抓時間?抓剪貼簿?還是抓網頁上的按鈕? * 本專案答案:抓系統時間、抓 NVDA 版本。 3. 動作與回饋(AI 要做什麼?):我是要讓它發出聲音?還是點字顯示? * 本專案答案:用語音和點字同步讀出來。

💬 步驟二:翻譯成「AI 聽得懂的 NVDA 行話」

這一步是 Vibe Coding 的精髓!我們要用我們前面學到的專有名詞,來限定 AI 的程式碼框架: * 「按快捷鍵」 ➔ 翻譯成 globalPlugins(全域外掛),並指定使用最新的 @scriptHandler.script 裝飾器來綁定。 * 「抓 NVDA 版本」 ➔ 翻譯成 呼叫 NVDA 的 core.version。 * 「用語音和點字同步讀出來」 ➔ 翻譯成 嚴格使用 ui.message()

✍️ 步驟三:組裝你的「導演派工單 (Prompt)」

將步驟一與步驟二的結果,組裝成一份結構化的 Prompt 丟給 AI。


📝 實戰演練:大導演的 Prompt 派工單

請直接複製以下 Prompt 貼給你的 AI 工程師(Antigravity、Claude 或 ChatGPT):

「你現在是一位頂尖的 NVDA 視障輔助軟體外掛工程師。我需要你幫我開發一個名為『系統情報員』的全域外掛。請給我以下兩個檔案的內容:

  1. 開發需求描述:
  2. 類型:全域外掛(GlobalPlugin),請在 globalPlugins 目錄下建立 systemInfo.py
  3. 觸發方式:使用最新的 @scriptHandler.script 裝飾器,將快捷鍵綁定為 kb:NVDA+shift+i
  4. 功能邏輯:當按下快捷鍵時,先獲取 Python 的 datetime.now() 取得時間,再獲取 NVDA 核心的 core.version 取得版本。
  5. 使用者回饋:請使用 ui.message() 進行播報,確保語音與點字同步。

  6. 嚴格規範限制:

  7. 請提供 manifest.ini 宣告檔內容,name 設定為 systemInfo,最低支援版本 (minimumNVDAVersion) 必須設定為 2026.1。
  8. 程式碼中必須包含清晰的中文註解,解釋每一行程式碼的運作原理,讓不懂寫程式代碼的一般人也能了解。

請直接輸出這兩個檔案的完整程式碼,不需要額外解釋。」


🛠️ 開看懂 AI 寫的代碼與檔案歸位

當 AI 產生代碼後,我們該怎麼看懂它呢?這就是第四步! AI 給你的 systemInfo.py 代碼大致如下(我們來白話看懂它):

# 引入 NVDA 官方提供的全域外掛基礎模組
import globalPluginHandler
# 引入處理快捷鍵綁定的模組
import scriptHandler
# 引入語音與點字輸出的模組
import ui
# 引入 NVDA 核心資訊模組,用來抓版本
import core
# 引入 Python 內建的時間模組
from datetime import datetime

# 建立我們的外掛類別,繼承自官方的全域外掛範本
class GlobalPlugin(globalPluginHandler.GlobalPlugin):

    # 使用裝飾器,告訴 NVDA 當使用者按下 NVDA+Shift+I 時,執行底下的函數
    @scriptHandler.script(
        description="Announces current time and NVDA version", # 快捷鍵的無障礙說明
        gestures=["kb:NVDA+shift+i"] # 綁定的實體按鍵
    )
    def script_announceInfo(self, gesture):
        # 1. 抓取現在的時間,並格式化為時與分
        currentTime = datetime.now().strftime("%H:%M")
        # 2. 抓取當前的 NVDA 版本號
        nvdaVersion = core.version
        # 3. 組合我們要讀給使用者聽的中文句子
        message = f"現在時間是 {currentTime},當前 NVDA 版本為 {nvdaVersion}"
        # 4. 呼叫官方 API,讓語音與點字同步報讀
        ui.message(message)

💡 程式碼解密:


🧪 測試與驗收

  1. 在電腦上建立 systemInfoAddon 資料夾。
  2. 將 AI 給的 manifest.ini 存檔放入。
  3. 建立 globalPlugins 資料夾,並將上述 systemInfo.py 放入。
  4. 將整個資料夾放進 NVDA 的 scratchpad 中。
  5. 按下 NVDA+Ctrl+F3 重新啟動 NVDA,然後按下 NVDA+Shift+I 測試!

👉 下一堂課:Lecture 15 【基礎專案】剪貼簿淨化大師:結合 NVDA API 與模擬鍵盤
👉 回課程大綱:學習地圖

🔥 專家級深度思考與實戰挑戰 (Expert Challenge)

以下題目專為尋求真正硬核觀念與專家級實戰能力的學員設計。題目無法從講義表面抄寫解答,請嘗試獨立思考後再點擊展開解析。

❓ 挑戰 1:NVDA 外掛目錄構造 (`manifest.ini` + `globalPlugins`) 的載入機制 👉 點擊展開專家解析答案

【實戰情境問題】:當 NVDA 啟動時,它是如何掃描並載入 `globalPlugins` 目錄下的 Python 模組的?為什麼外掛中的 Python 檔案命名為 `__init__.py` 或 `myPlugin.py` 會產生不同的模組導出行為?

【鑑別點說明】:測試學員對 NVDA 外掛架構、Python import 機制與外掛生命週期 (Life Cycle) 的理解。

【專家級解答與深度剖析】: NVDA 在啟動時會讀取所有已安裝外掛的 `manifest.ini` 驗證相容版本,隨後遍歷 `globalPlugins` 目錄。若 `globalPlugins/myAddon/` 下存在 `__init__.py`,Python 會將該目錄視為一個完整的模組包 (Package),並自動實例化其中的 `GlobalPlugin` 類別;若只是單個 `myPlugin.py` 檔案,NVDA 則會直接載入該單一模組。外掛類別必須繼承 `globalPluginHandler.GlobalPlugin`,NVDA 才會在全域環境中監聽其快速鍵與事件。

❓ 挑戰 2:NVDA 外掛的生命週期管理與 `script` 快速鍵綁定 👉 點擊展開專家解析答案

【實戰情境問題】:在 NVDA 外掛中,如何使用 `@script` 裝飾器將鍵盤組合鍵(如 `NVDA + Shift + Z`)綁定到特定函數?當外掛被停用或 NVDA 關閉時,為什麼必須在 `terminate()` 方法中清理資源?

【鑑別點說明】:考驗學員對 NVDA 快速鍵手勢 (Gestures) 映射與記憶體/執行緒清理的實戰經驗。

【專家級解答與深度剖析】: NVDA 透過 `scriptHandler.script` 裝飾器與 `script_` 前綴函數來識別快速鍵腳本,並在類別內部定義 `__gestures__ = {"kb:nvda+shift+z": "myScript"}` 建立手勢映射。當使用者卸載、停用外掛或重啟 NVDA 時,NVDA 會調用外掛的 `terminate()` 析構方法。若外掛在背景開闢了未關閉的子執行緒、全域 Win32 鉤子 (Hook) 或定時器,而沒有在 `terminate()` 中顯式停止,會導致 NVDA 進程無法正常退出,鎖死 Python DLL 記憶體。