🎙️ Lecture 15: 【基礎專案】剪貼簿淨化大師:結合 NVDA API 與模擬鍵盤 (Clipboard Cleaner)

NOTE作者資訊 (Author Info)

🎧 錄音室開場:用 Vibe Coding 解決日常生活痛點!


🎯 專案二目標:剪貼簿淨化大師 (Clipboard Cleaner)

按下 NVDA+Shift+V 時,外掛會把剪貼簿裡的花俏內容「洗乾淨」變成純文字,並自動幫你按 Ctrl+V 貼上!


🧠 大導演的思維課:如何為「資料處理與鍵盤模擬」設計 Prompt

當我們的外掛涉及到「修改系統資料(如剪貼簿)」與「模擬使用者動作(如貼上)」時,需求設計必須非常謹慎。我們來進行三步驟拆解:

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

  1. 觸發時機:按下快捷鍵 NVDA+Shift+V 觸發。
  2. 資料來源與處理
    • 來源:讀取系統剪貼簿。
    • 處理:把裡面的格式(HTML/RTF)過濾掉,只留下純文字。
  3. 動作與回饋
    • 動作一:把過濾後的純文字再塞回剪貼簿。
    • 動作二:自動幫我按下鍵盤的 Ctrl+V
    • 回饋:用語音跟點字朗讀「已貼上純文字」。

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

為了避免 AI 使用不安全的方法,我們必須在 Prompt 中指定以下 NVDA 專用 API: * 「讀取與寫入剪貼簿」 ➔ 必須指定 NVDA 官方提供的 api.getClipData()api.copyToClip(),這能防止 Windows 剪貼簿鎖死崩潰。 * 「模擬鍵盤按鍵」 ➔ 指定使用 KeyboardInputGesture.fromName("control+v").send(),這是 NVDA 2026.1 最標準且安全的按鍵注入方式。


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

請將以下 Prompt 貼給你的 AI 工程師:

「你現在是專業的 NVDA 視障輔助軟體外掛工程師。請幫我寫一個全域外掛(GlobalPlugin),檔案名為 purePaste.py

1. 開發需求描述: - 類型:全域外掛(GlobalPlugin),請綁定快捷鍵 kb:NVDA+shift+v。 - 剪貼簿處理:請使用 NVDA 官方的 api.getClipData() 讀取剪貼簿。如果剪貼簿沒有文字,請呼叫 ui.message("剪貼簿是空的") 並結束。 - 格式淨化:將讀取出來的純文字,使用 api.copyToClip() 重新寫入剪貼簿,以清除 HTML 等格式資訊。 - 模擬貼上:請呼叫 keyboardHandler.KeyboardInputGesture.fromName("control+v").send() 模擬按下 Ctrl+V 完成貼上。 - 播報提示:最後以 ui.message("已淨化並貼上") 提示使用者。

2. 嚴格規範限制: - 嚴禁使用 Python 的 win32clipboard 庫,必須完全採用上述 NVDA 官方的 api 模組,以維持相容性。 - 程式碼必須採用標準縮排,且必須有給一般人看得懂的詳細中文註釋,逐行解釋其運作原理,讓不懂寫程式代碼的人也能完全了解。

請直接輸出完整程式碼。」


🛠️ 看懂 AI 寫的代碼:逐行白話拆解

AI 產出的 purePaste.py 程式碼結構如下,我們一起來讀懂它:

# 引入全域外掛範本
import globalPluginHandler
# 引入快捷鍵裝飾器
import scriptHandler
# 引入 NVDA 官方 API 控制庫(負責剪貼簿讀寫)
import api
# 引入語音/點字播報模組
import ui
# 引入處理鍵盤模擬的模組
import keyboardHandler

class GlobalPlugin(globalPluginHandler.GlobalPlugin):

    # 綁定快捷鍵為 NVDA+Shift+V
    @scriptHandler.script(
        description="Purifies clipboard to plain text and pastes it",
        gestures=["kb:NVDA+shift+v"]
    )
    def script_purePaste(self, gesture):
        # 1. 安全地從系統剪貼簿讀取文字內容
        text = api.getClipData()

        # 2. 檢查剪貼簿是不是空的
        if not text:
            # 如果是空的,報讀提示,並立刻結束函數
            ui.message("剪貼簿是空的")
            return

        # 3. 將純文字重新寫回剪貼簿(這會自動清除所有字型、顏色與表格格式)
        api.copyToClip(text)

        # 4. 建立一個「按下 Control+V」的鍵盤虛擬手勢,並發送出去(模擬貼上)
        keyboardHandler.KeyboardInputGesture.fromName("control+v").send()

        # 5. 發送完成提示,用語音和點字告訴使用者
        ui.message("已淨化並貼上")

💡 核心代碼導讀:


🧪 測試與驗收

  1. 將檔案存為 purePaste.py 放進 globalPlugins
  2. 按下 NVDA+Ctrl+F3 重啟。
  3. 去複製一段網頁上帶有超連結與顏色的字,按下 NVDA+Shift+V。字會被以純文字自動貼上,大功告成!

👉 下一堂課:Lecture 16 【中階專案】特定軟體專屬守衛:記事本焦點音效提示
👉 回課程大綱:學習地圖

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

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

❓ 挑戰 1:`ui.message()` vs `speech.speak()` 在 NVDA 播報佇列中的層級差異 👉 點擊展開專家解析答案

【實戰情境問題】:在編寫 NVDA 外掛時,呼叫 `ui.message("提示訊息")` 與直接呼叫 `speech.speakText("提示訊息")` 或 `braille.handler.message("點字訊息")` 有何本質上的無障礙架構差異?

【鑑別點說明】:深剖 NVDA 輸出層(Speech / Braille Output Manager)的抽象封裝與多模態輸出。

【專家級解答與深度剖析】: `speech.speakText()` 只會單純觸發語音合成器發聲,不會將訊息傳送到視障者的點字觸摸顯示器 (Braille Display) 上;而 `ui.message()` 是 NVDA 提供的高階通用 API,它會**同時向語音合成器與點字顯示器發送訊息**,並且會處理點字訊息的暫留顯示時間與語音佇列的優先級搶占(Preemption)。因此,撰寫符合規範的外掛應優先使用 `ui.message()` 以確保點字使用者享有同等權益。

❓ 挑戰 2:`api.getFocusObject()` 的非同步空值 (NoneType) 安全防禦 👉 點擊展開專家解析答案

【實戰情境問題】:當使用者在 Windows 系統中快速切換視窗(如按 `Alt + Tab`)時,外掛呼叫 `obj = api.getFocusObject()` 時經常會回傳 `None` 或無效物件。如果不做非同步判空保護直接存取 `obj.name`,會引發什麼災難?

【鑑別點說明】:測試學員對 Win32 視窗焦點切換期間的 race condition 與 Python 例外防禦機制。

【專家級解答與深度剖析】: 當作業系統正在銷毀舊視窗並建立新視窗的微秒級間隙中,NVDA 的焦點物件可能暫時處於未初始化狀態,`getFocusObject()` 會回傳 `None` 或已失效的 `DeadNVDAObject`。若代碼直接呼叫 `obj.name`,會觸發 `AttributeError: 'NoneType' object has no attribute 'name'`,導致 NVDA 外掛腳本崩潰在中途,使用者失去語音反饋。防禦作法:必須進行嚴格的判空與 `try-except` 包裹:`if obj and not obj.isDead:`。