🎙️ Lecture 22: 【進階實戰專案】螢幕簾幕自動恢復:進程持久化與猴子補丁 (Screen Curtain Restore)

NOTE作者資訊 (Author Info)

🎧 錄音室開場:保護視障隱私的最後一道防線!


🎯 專案五目標:螢幕簾幕自動恢復 (Screen Curtain Restore)

當使用者開啟暫時性螢幕簾幕後,如果重開 NVDA(或系統錯誤重啟),外掛會自動在重啟前記錄狀態;並在 NVDA 啟動時自動恢復簾幕,且在恢復後自動清理標記。


🧠 大導演的思維課:跨進程狀態傳遞的 Prompt 設計

當我們重啟 NVDA 時,Python 的整個虛擬機進程會被銷毀並重新建立。這意味著,所有的記憶體變數都會歸零!我們不能用簡單的 self.curtainEnabled = True 來記錄。

我們必須設計一個「跨進程持久化(存入硬碟)」的提示詞架構:

📋 步驟一:拆解你的「跨進程記憶需求」

  1. 觸發時機(重啟前):當 NVDA 準備要關閉重啟時。
    • 行話翻譯:必須接管 NVDA 核心負責關閉的 core.triggerNVDAExit 與崩潰重啟的 core.restartUnsafely 函數。由於這是核心系統,我們不能直接改程式,必須指示 AI 採用 Monkeypatching(動態修改/猴子補丁) 技術!
  2. 狀態儲存:如果重啟時簾幕是開啟的,在硬碟寫入一個暫存檔。
    • 行話翻譯:在 NVDAState.ReadPaths.configDir(使用者設定路徑)下,寫入一個名為 screen_curtain_restart_active 的空檔案作為標記。
  3. 啟動恢復與自我清理(重啟後):當 NVDA 重新載入外掛時。
    • 行話翻譯:在全域外掛的 __init__ 中,檢查該檔案是否存在。若存在,立刻刪除該檔案以確保安全,並呼叫 screenCurtain.screenCurtain.enable(persist=False) 自動重新開啟簾幕。

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

請將以下 Prompt 貼給你的 AI:

「你現在是精通 NVDA 核心生命週期與跨進程狀態設計的頂級工程師。請幫我開發一個全域外掛(GlobalPlugin),檔案名為 screen_curtain_restore.py

1. 開發需求描述: - 重啟前攔截 (Monkeypatching):請動態替換(接管) core.triggerNVDAExitcore.restartUnsafely。在這兩個函數執行前,檢查 screenCurtain.screenCurtain.enabled 是否為 True。若是,請在 globalvars.appArgs.configDir(設定目錄)下建立一個名為 screen_curtain_restart_active 的暫存標記檔案。 - 重啟後恢復與清理:在 GlobalPlugin.__init__ 中,檢查該標記檔案是否存在。如果存在: - 請立刻刪除該檔案,以防異常殘留。 - 呼叫 screenCurtain.screenCurtain.enable(persist=False) 開啟簾幕。 - 開發者 Scratchpad 相容性防護(重要):為防止在 Scratchpad 模式下測試拋出找不到附加元件元數據的錯誤,請對 addonHandler.initTranslation() 加上 try-except 保護,防止 AddonError 導致外掛無法載入。

2. 規範限制: - 程式碼必須有非常詳細且給一般人看得懂的中文註釋,逐行解釋猴子補丁與標記檔讀寫的邏輯,讓不懂程式碼的人也能了解。

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


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

AI 產出的 screen_curtain_restore.py 結構如下,我們來讀懂這段高級魔法:

import globalPluginHandler
import addonHandler
import core
import screenCurtain
import os
import globalvars

# 1. 開發者 Scratchpad 相容性防禦
# 如果在未打包的暫存區測試,這行會拋錯,所以必須用 try-except 包裹起來保護
try:
    addonHandler.initTranslation()
except Exception:
    pass

# 定義我們在硬碟中存放「隱私便條紙」的實體路徑
RESTART_MARKER_FILE = os.path.join(globalvars.appArgs.configDir, "screen_curtain_restart_active")

# 2. 備份 NVDA 核心原本的重啟函數
original_triggerNVDAExit = core.triggerNVDAExit
original_restartUnsafely = core.restartUnsafely

# 3. 寫一個我們自己的「攔截版」重啟函數
def patched_triggerNVDAExit(newNVDA=None):
    # 如果偵測到使用者是要「重新啟動」而非關機 (newNVDA 不為 None)
    # 且當前螢幕簾幕是「開啟的」
    if newNVDA and screenCurtain.screenCurtain.enabled:
        # 在硬碟中寫下一張「隱私便條紙」(建立暫存標記檔)
        try:
            with open(RESTART_MARKER_FILE, "w") as f:
                f.write("active")
        except Exception:
            pass
    # 最後,把控制權交還給原本的重啟函數,讓 NVDA 正常重啟
    original_triggerNVDAExit(newNVDA)

# 4. 猴子補丁:用我們寫的攔截版,替換掉官方原本的函數
core.triggerNVDAExit = patched_triggerNVDAExit


class GlobalPlugin(globalPluginHandler.GlobalPlugin):

    def __init__(self):
        super().__init__()
        # 5. 重啟完成!外掛被載入,立刻去硬碟檢查有沒有「隱私便條紙」
        if os.path.exists(RESTART_MARKER_FILE):
            # 發現便條紙!為維護安全,立刻刪除檔案(撕毀便條紙),避免下次開機誤判
            try:
                os.remove(RESTART_MARKER_FILE)
            except Exception:
                pass

            # 6. 安全插隊:啟用螢幕簾幕(暫時性模式)
            screenCurtain.screenCurtain.enable(persist=False)

💡 核心代碼導讀:


🔍 零基礎讀碼指南:白話拆解 screen_curtain_restore.py 原始碼

如果你是完全沒有寫過程式的新手,這段代碼展示了如何優雅地使用「保險箱標記」與「動態改裝(猴子補丁)」來解決跨重啟的記憶問題。

📦 1. 備料與安全防線 (第 5 至 18 行:importtry-except)

📝 2. 秘密便條紙的藏匿處 (第 25 行:MARKER_PATH)

🐒 3. 貍貓換太子:接管核心重啟函數 (第 27 至 61 行)

這是著名的 猴子補丁 (Monkeypatching) 技術,像是給原廠車改裝零件:

🧹 4. 重開機後的自動記憶恢復與銷毀 (第 64 至 89 行:GlobalPlugin)

當 NVDA 重新啟動,會初始化我們的外掛類別 GlobalPlugin


🧪 測試與驗收

  1. 將檔案存為 screen_curtain_restore.py 放入 globalPlugins
  2. 啟動螢幕簾幕。接著按下 NVDA+Q 開啟重啟對話框,選擇「重新啟動」。
  3. NVDA 重啟後,檢查螢幕是否立刻自動變黑,隱私防護罩完美運作。大功告成!

👉 回課程大綱:學習地圖

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

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

❓ 挑戰 1:螢幕幕簾 (Screen Curtain) 在作業系統防窺與資安保護中的物理繪除 👉 點擊展開專家解析答案

【實戰情境問題】:NVDA 的「螢幕幕簾」功能開啟後,螢幕會變得一片漆黑,但視障使用者仍然能正常操作電腦。這在作業系統圖形渲染層(GDI / Desktop Window Manager, DWM)是如何透過覆蓋黑色全螢幕視窗實施物理防窺的?

【鑑別點說明】:深剖 Windows GDI/DWM 繪圖層、全螢幕 Topmost 視窗與螢幕幕簾安全防護。

【專家級解答與深度剖析】: NVDA 螢幕幕簾透過 Win32 API 建立一個位於最高層級(`WS_EX_TOPMOST`)的黑色的全螢幕無邊框視窗,覆蓋所有顯示器。此外,為了防止黑客透過截圖軟體或外接顯示器竊視,先進的幕簾機制會調用 Win32 `SetWindowDisplayAffinity(WDA_EXCLUDEFROMCAPTURE)`,直接在 Windows 組合管理員 (DWM) 層面將桌面繪圖輸出抹黑。這確保了身旁旁觀者完全看不到螢幕上的敏感個人資料與密碼。

❓ 挑戰 2:螢幕幕簾卡死與緊急復原 (Screen Curtain Crash Recovery) 護城河 👉 點擊展開專家解析答案

【實戰情境問題】:如果 NVDA 在螢幕幕簾開啟狀態下突然發生 Python 崩潰(Crash),導致螢幕一片漆黑且語音報讀中斷(使用者陷入雙重黑暗與無聲困境),請設計一套外掛層級的「緊急解鎖護城河 (Emergency Uncurtain Guard)」。

【鑑別點說明】:考驗學員對極限故障安全(Fail-Safe Design)與無障礙安全防線的設計能力。

【專家級解答與深度剖析】: 這是無障礙軟體最嚴重的災難情境。防禦護城河設計:1. **Watchdog 獨立狗守護進程**:啟動一個極輕量的 C++/Python 看門狗進程,定期接收 NVDA 的心跳訊號 (Heartbeat);2. **心跳中斷自動銷毀**:若看門狗連續 3 秒未收到心跳(代表 NVDA 崩潰),看門狗立刻強行銷毀幕簾黑幕視窗;3. **緊急硬體熱鍵**:監聽全域硬體按鍵組合(如 `Ctrl + Alt + Shift + Esc`),直接由作業系統層面物理性解除幕簾,保障使用者絕不被困在黑屏中。