From 19f469bb8ee04754701b212a2edb43d9bf8e8267 Mon Sep 17 00:00:00 2001 From: yangeric Date: Wed, 5 Aug 2026 20:23:04 +0800 Subject: [PATCH 01/17] feat(i18n): add Traditional Chinese (zh-TW) localization - add `app/src/main/res/values-zh-rTW/strings.xml` with all 1055 strings, at full parity with `values/strings.xml` - add `gamefocus/src/main/res/values-zh-rTW/strings.xml` with all 62 strings, at full parity - register `zh-TW` in `app/src/main/res/xml/locales_config.xml` so the locale is offered in the per-app language picker on Android 13+ - mirror key order and comments from the English source files so future syncs stay reviewable as a diff - leave `app_name` untranslated, matching `values-de` Co-Authored-By: Claude Opus 5 (1M context) --- app/src/main/res/values-zh-rTW/strings.xml | 1209 +++++++++++++++++ app/src/main/res/xml/locales_config.xml | 1 + .../src/main/res/values-zh-rTW/strings.xml | 70 + 3 files changed, 1280 insertions(+) create mode 100644 app/src/main/res/values-zh-rTW/strings.xml create mode 100644 gamefocus/src/main/res/values-zh-rTW/strings.xml diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 000000000..0487dd48a --- /dev/null +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,1209 @@ + + Megingiard-zh + 遊戲聚焦啟動器 + 重新整理 + 搜尋應用程式與遊戲… + 找不到應用程式 + 正在等待螢幕投影授權\u2026 + Megingiard 必須在底部螢幕運行。\n請移動應用程式以繼續。 + 指向底部螢幕的箭頭 + 開始投影 + 上一個工具 + 下一個工具 + 停止投影 + 開始投影 + 凍結 + 解除凍結 + 螢幕截圖 + 鎖定視圖 + 解鎖視圖 + 觸控投影開啟 + 觸控投影關閉 + 凍結的串流圖像 + 螢幕投影活動中 + + + 關閉 + 支持此應用程式 + 開啟設定 + 開啟全域設定 + 拖曳以重新排序 + 工具 + 一般 + 設定檔與自動化 + 外觀 + 設定 + 返回 + 強調色 + 主題 + 深色 + 深色 OLED + Megingiard + Mjölnir 鋼鐵 + 英靈殿日落 + 北極光 + 復古磷光體 + 皇家阿斯嘉特 + 控制列置於底部 + 自動隱藏控制列 + 閒置幾秒後自動隱藏快速選單列 + 邊緣融合 + %1$d + 關閉 + 輕微 + 中等 + 強烈 + 最大 + FPS 限制 + %1$d FPS + + 自動切換設定檔 + 當主螢幕啟動已對應的應用程式時,自動切換 MacroPad 設定檔 + 從最近任務中排除 + 切換到其他程式時,在最近任務列表中隱藏此應用程式 + 無障礙服務 + 已啟用 + 未啟用(點擊以啟用) + 讓 Megingiard 能夠偵測當前運行的應用程式,以便自動切換設定檔。 + 前往設定 + 選擇強調色 + 自定義 + 推薦強調色 + 預設強調色 + 關閉 + 亮度 + 取消 + 應用 + + + 鍵盤設定 + 軌跡點 + 全選 + 剪下 + 複製 + 貼上 + Shift + 退格 + Enter + 鍵盤配置 + 收起鍵盤 + 鍵盤設定 + 鍵盤配置 + 字母排列方式 + QWERTZ + QWERTY + AZERTY + 軌跡點 + 在主行顯示軌跡點 + 按鍵重複 + 按住按鍵以重複輸入 + 全螢幕鍵盤 + 無底部間隙 — 鍵盤填滿整個螢幕 + 鍵盤觸控板 + 在鍵盤上方顯示觸控板 + + + + 滑鼠模式 + 使用滑鼠移動而非觸控控制 + 輕點以點擊 + 單指輕點 = 左鍵點擊 + 雙指輕點 + 雙指輕點 = 右鍵點擊 + + + + 記住視窗 + 投影重啟時恢復縮放與平移位置 + 記住鎖定狀態 + 投影重啟時恢復視圖鎖定狀態 + 記住觸控投影 + + 未配置設定檔。\n請開啟設定以建立配置。 + 開啟設定 + MacroPad 按鈕: %s + 軌跡點 + + + 編輯配置 + 編輯設定檔 + 儲存 + 新增按鈕 + 新增 + 刪除 + 標籤 + 動作 + 按鈕形狀 + 圓形 + 正方形 + 僅圖示 + 按鈕大小 + 1×1 + + 按鍵 + 修飾鍵 1 + 修飾鍵 2 + 按鈕 + 額外 1 + 額外 2 + 額外 3 + 2×1 + 1×2 + 2×2 + 軌跡點大小 + + + + 追蹤模式 + 虛擬實體滑鼠 + 虛擬觸控 (開發中) + 模擬標準滑鼠。拖曳會從系統游標目前位置相對移動。 + 模擬主螢幕上的絕對觸控輸入。游標位置會被記住,讓您可以從同一點進行多次滑動。(開發中) + 觸感回饋 + 關閉 + 輕微 + 中等 + 強烈 + 自定義 + 持續時間 + 振幅 + 測試振動 + 模擬裝置 + 鍵盤 + 手把 + 滑鼠 + 此配置已禁用鍵盤 + 此配置已禁用手把 + + + 左鍵 + 中鍵 + 右鍵 + M4 + M5 + + 按鈕位置 + 左/右鍵按鈕顯示位置 + 左側 + 右側 + 兩側 + 此配置已禁用滑鼠 + 此配置已禁用觸控 + 設定檔 %1$d + 重新命名 + 刪除設定檔 + 刪除此設定檔? + 刪除配置 + 切換配置啟用狀態 + 取消 + 刪除 + + + 設定檔設定 + 設定檔名稱 + 分配應用程式 + 選擇應用程式… + 清除關聯 + 搜尋應用程式… + 找不到應用程式 + (已分配) + 正在載入應用程式… + 必須在全域設定中啟動無障礙服務 + + + 選擇圖示 + 搜尋圖示… + 找不到圖示 + 填滿 + 當前選擇 + + + 配置 + 數據 + 還原預設設定檔 + 刪除所有設定檔並建立一個空白預設設定檔 + 這將刪除所有現有的設定檔與巨集,並建立一個空白設定檔。此操作無法撤銷。 + 還原 + 還原 (%1$d) + 取消 + 建立備份 + 將所有設定與 MacroPad 設定檔儲存為 .mgrd 備份檔 + 還原備份 + 套用設定並從內部每日備份或外部 .mgrd 檔案新增設定檔 + 分享設定檔 + 將單個 MacroPad 設定檔匯出為 .mgrd 檔案以便分享 + 匯入分享的設定檔 + 從 .mgrd 檔案新增 MacroPad 設定檔 + 新增到 Obtainium + 在 Obtainium 中預填儲存庫連結以進行自動更新 + megingiard_%s.mgrd + + + 匯出資訊 (選填) + 作者 + 描述 + 標籤 (逗號分隔) + 匯出 + 取消 + + + 匯入配置 + 作者 + 描述 + 標籤 + 包含內容 + 全域設定 + 投影設定 + 觸控板設定 + 鍵盤設定 + MacroPad (%1$d 個設定檔, %2$d 個巨集) + MacroPad 設定 + 設定 (全域 / 工具設定) + 作者: %1$s + 新的設定檔與巨集將新增至現有內容中。 + 新的設定檔將新增至現有內容中。應用程式設定將被覆蓋。 + 設定檔將新增至現有內容中。此檔案中的應用程式設定將被忽略。 + 分享設定檔 + 要分享的設定檔 + 設定檔匯入成功。 + 匯入 + 取消 + + + 配置匯出成功。 + 配置匯入成功。 + 錯誤 + 完成 + 發生非預期的錯誤。 + 確定 + 選擇按鈕圖示 + 套用圖示選擇 + 移除圖示 + + + 鍵盤按鍵 + 手把按鈕 + 滑鼠按鈕 + 滾輪 + 軌跡點 (相對滑鼠) + 背景預覽 + 下一個配置 + 上一個配置 + 設定檔切換器 + 投影 開始/停止 + 投影 凍結 + 投影 視窗 + 觸控投影 + 全螢幕滑鼠 + 全螢幕鍵盤 + + + 活動設定檔 + 新設定檔 + Pad 形狀 + 正方形 + 圓形 + Pad 大小: %d%% + 編輯配置… + 新配置 + 編輯 + 新建 + 複製自… + 空白 + 重新排序設定檔 + 重新排序配置 + + + 背景跟隨 + 背景投影跟隨觸控 + 將投影視窗中心對齊您在主螢幕上最後觸碰的位置。 + 平滑度 + 使用指數衰減平滑視窗移動 + 關閉 + 區塊 (Cutouts) + 未定義區塊。請在視窗編輯器中新增。 + + + 背景顯示 + 自動開始螢幕投影 (首次使用需授權) + 顯示預覽 + 調整顯示時顯示透明視窗 + 將主題套用於背景顯示 + 禁用時,所有按鈕將以中性灰色風格顯示 + 調暗 + + 預覽 + + + + 按鍵: %s + 手把: %s + 滑鼠: %s + 滾輪 + 軌跡點 + + + 語言 + 系統預設 + English + Deutsch + 日誌級別 + 交換面按鈕標籤 (A/B 與 X/Y) + 在手把選擇器中顯示任天堂風格標籤 (A↔B 且 X↔Y)。注入的鍵碼保持不變。 + + + 特權模式 + 提高相容性並增強特定功能。查看說明以獲取更多資訊。 + 關閉 + 正在連接… + 正在初始化… + 運行中 + 失敗 + 連接 + 斷開連接 + 測試連接 + 設定… + 測試自動切換 + 自動設定 + 重試自動設定 + 自動切換需要無障礙服務,請先在系統設定中啟用。 + 設定完成!Privd 特權模式已就緒。 + Daemon 有回應 — 連接正常。 + Daemon 無回應。連接可能已失效。 + + 顯示重新連接提示 + 如果與特權助手 daemon 連接失敗,在啟動時顯示對話視窗。 + 重新啟用特權模式 + 跳過 + 完成 + 開發者設定 + + + + + + 配置死區 + 左: %1$d %% · 右: %2$d %% + 手把死區 + 開啟 Thor 內建設定 → Thor Settings → Joystick calibration & Gamepad test → Joystick deadzone radius 查看各搖桿值,然後填入下方。 + 左搖桿 + 右搖桿 + 確定 + 取消 + + 正在錄製實體控制器… + 按下實體手把上的按鈕並移動搖桿。完成後點擊停止。 + 已按下: %1$s + 左搖桿: %1$s, %2$s + 右搖桿: %1$s, %2$s + 停止並儲存 + 取消 + + + 設定特權模式 + 下一步 + 上一步 + 完成 + 關閉嚮導 + 正在配對… + 正在初始化… + + 按照以下步驟啟用無線偵錯: + 1. 開啟設定 → 關於掌上型主機 + 2. 連續點擊「版本號碼」7 次直到出現「您現在已成為開發人員!」 + 3. 返回 → System → 開發人員選項 + 4. 啟用「無線偵錯」並將其開啟 + 5. 點擊「使用配對碼配對裝置」並保持該對話視窗開啟 + 開啟系統設定 + + 在無線偵錯設定中點擊「使用配對碼配對裝置」。輸入該對話視窗中顯示的配對連接埠與 6 位數配對碼。連線連接埠會自動偵測。 + 配對連接埠 (來自「配對裝置」對話視窗) + 配對碼 (6 位數) + 配對 + 配對失敗。請檢查配對連接埠與配對碼,然後重試。 + 自動填入(讀取螢幕) + 正在掃描螢幕… + 已自動填入配對資訊! + 未能在螢幕上偵測到配對資訊。請確認「配對裝置」對話框顯示於上螢幕。 + 請在系統設定中啟用無障礙服務,才能透過讀取螢幕文字自動填入。 + + Megingiard 現在將推送助手 daemon 執行檔到 /data/local/tmp 並啟動它。Daemon 以 ADB shell 使用者身份運行,直到您重啟設備為止。 + 推送並啟動 daemon + + 特權模式設定完成。未來應用程式啟動時將自動重新連接。 + + 閒置 + 正在與 ADB 配對… + 正在連接到 ADB… + 正在推送 daemon 檔案… + 正在啟動 daemon… + 正在驗證連接… + 完成 + + ADB 連接 + 推送 daemon 執行檔 + 啟動 daemon + 驗證連接 + + 無法連接到 Daemon。請執行設定嚮導。 + 配對失敗。請驗證系統配對對話視窗中的地址、連接埠與 6 位數代碼。 + 無法透過 mDNS 發現 ADB 無線偵錯端點。請確保無線偵錯已啟用。 + 無法連接到 ADB。如果您之前撤銷了配對,請重新配對。 + 需要進行 ADB 配對。請在無線偵錯設定中移除「Megingiard」後重新配對。 + 推送 daemon 檔案失敗。請檢查 /data/local/tmp 中的可用空間。 + 啟動 daemon 進程失敗。 + 提供驗證金鑰失敗。請重新執行設定嚮導。 + 特權模式已啟用並正在運行。 + 找不到配對憑據。請執行設定嚮導來配對此裝置。 + 無線偵錯未啟用。請在開發人員選項中開啟並重試。 + 無線偵錯已啟用。點擊連接以啟動特權模式。 + 正在連接到 daemon… + 全部 + 篩選 + 一般 + 自動化 + 外觀 + 數據 + 配置 + 特權模式 + 診斷 + 特權模式 + 診斷 + 展開區塊 + 收起區塊 + 名稱不能為空。 + 此名稱已存在。 + + 重試偵測 + 設定與放置說明 + 1. 在次螢幕重新開啟 Megingiard,然後點擊「重試」。 + 2. 建議:如果您的啟動器支援,請將 Megingiard 固定在底部螢幕以保持其位置。 + 3. 焦點模式:由於 Megingiard 僅使用觸控控制,請在 AYN 設定中將焦點模式設為「主螢幕」。 + 開始 + 停止 + 凍結 + 解除凍結 + 視窗 + 投影 + 螢幕截圖 + 螢幕截圖已儲存至 Pictures/Megingiard + 儲存螢幕截圖失敗 + + + 退出 Megingiard + 您確定要關閉應用程式嗎? + 關閉 + 取消 + + + 巨集 + 巨集: %s + + + 沒有可用的巨集 + 沒有可用的巨集 + 未知的巨集 + + + 巨集… + 新增巨集按鈕 + + + 網格關閉 + 矩形網格 + 徑向網格 + 更改網格 + 新增按鈕 + 解鎖按鈕 + 鎖定按鈕 + 更改背景 + + + 配置 + 按鈕 + 按鈕設定 + 配置設定 + 列表 + 巨集步驟 + 巨集設定 + + + 按鈕顏色 (未投影時) + 按鈕顏色 (投影時) + 強調 + 中性 + + + 背景圖片 + 設定按鈕面板的背景圖片 + 未選擇 + 自定義背景啟用中 + 已選擇新圖片 (未儲存) + 選擇圖片 + 更改圖片 + 移除背景圖片 + 作為遮罩使用 + 將背景圖片繪製在螢幕投影區塊上方。適用於具有透明度的 PNG。 + 預覽背景圖片 + 背景圖片預覽 + 裁切 + 裁切背景圖片 + 拖曳選取框以移動 • 拖曳角落以調整大小 + 背景 + 調暗 + 按鈕預設顏色 + 按鈕顏色 + 文字與圖示顏色 + 邊框顏色 + 淡出顏色 + 選擇文字與圖示顏色 + 選擇邊框顏色 + 選擇淡出顏色 + 中性風格 + 強調色 + 最近使用的顏色 + 尚無最近使用的顏色 + 配置預設值 + 不透明度 + 隱形按鈕 + 使用配置時讓按鈕隱形,編輯時仍然可見。 + + + + 配置設定說明 + 設定此配置的外觀、背景圖片與按鈕預設顏色。 + 設定此配置的外觀、名稱與按鈕預設顏色。 + 屬性 + 配置名稱 + 為此配置取一個不重複的名稱,方便在編輯器與快捷選單中辨識。 + 背景圖片 + 可選擇在按鈕後方顯示自定義圖片。支援 JPG、PNG 與 WebP 格式。 + 作為遮罩使用 + 將背景圖片覆蓋在螢幕投影區塊上方。非常適合具有透明度的自定義主機外殼或框架。 + 按鈕顏色 + 自訂預設顏色 + 設定此配置中所有按鈕的預設顏色(文字/圖示、外框與漸隱顏色)。個別按鈕可以各自覆寫這些預設值。 + 快速調色盤 + 點擊顏色旁邊的調色盤圖示,即可從標準的中性/強調色樣式,或最近選過的自訂顏色中挑選。 + + + 設定此配置的背景圖片設定、調暗、抓取與遮罩選項。 + 調暗 + 使用滑桿調暗背景圖片。範圍從 0% (不調暗) 到 90% (最大調暗),以保持按鈕清晰可見。 + 裁切與平移 + 使用拖曳平移與捏合縮放手勢裁切並縮放圖片,使其符合雙螢幕長寬比。 + + + 巨集 + 新增巨集 + 尚無巨集。點擊 + 建立一個。 + + %1$d 個步驟 • %2$s + %1$d 毫秒 + %1$d 毫秒 (隨機偏移 %2$d 毫秒) + 新巨集 + 刪除巨集 + 刪除此巨集?引用它的按鈕將停止運作。 + 此巨集被 %d 個按鈕使用。確定要刪除嗎? + 複製巨集 + + + 儲存 + 取消 + 步驟 + 錄製 + 錄製 + 尚無步驟。點擊 + 新增一個。 + 刪除步驟 + 從巨集中移除此步驟? + 錄製手把輸入 + 觸碰螢幕上的控制器。輸入將被錄製並即時轉發到上方螢幕。 + 完成 + 取消 + 左搖桿 + 右搖桿 + L3 + R3 + SE + HM + ST + 開始 + 開始手把錄製 + 此螢幕上將開啟觸控手把覆蓋層。您的觸碰將被錄製為巨集步驟,並透過虛擬手把即時轉發到上方螢幕。 + 不再顯示 + 手把錄製不可用 + 無法啟動虛擬手把。錄製未開始。 + + + 錄製觸控 + 螢幕投影將開始。請選擇您要錄製單次點擊還是連續手勢 (包含多指觸控)。 + 點擊 + 手勢 + 錄製手把輸入 + 錄製觸控步驟 + 測試運行 + 測試巨集 + 編輯巨集 + 循環巨集 + 兩次循環間的暫停時間 + 隨機化時序 + 時序偏移最大值 + 每次執行時,為每個步驟的設定啟動時間與持續時間增加一個隨機毫秒數 (最高至最大值)。 + 復原 + 重做 + 檢視 + 列表/編輯 + 時間軸 + 移動後續步驟 + 關閉 + 啟動偏移 Δ + 結束偏移 Δ + + 新增步驟 + 編輯步驟 + 手把按鈕 + 搖桿 + 搖桿路徑 + 方向鍵 + 觸控點擊 + 觸控手勢 + 點擊 + 手勢 + (%1$d 點) + %1$s (%2$d 點) + 手勢路徑: 已錄製 %1$d 個點。 + 錄製位置 (唯讀) + 已錄製的觸控手勢 (唯讀)。僅能編輯時序。 + 已錄製的類比搖桿路徑 (唯讀)。僅能編輯時序。 + 搖桿: %1$s + 採樣數: %1$d + 開始時間 (毫秒) + 持續時間 (毫秒) + %+d + 左搖桿 + 右搖桿 + 力度: %.1f + 方向 + %1$s / %2$s / %3$s + + + + + + + + + + %1$s 複本 + + %1$d 毫秒 + + 從 @%1$d 毫秒 ⟶ 持續 %2$d 毫秒 + + 更多選項 + + + + + + 設定檔 + 配置 + 新設定檔 + 新配置 + 編輯設定檔/配置 + 螢幕投影設定 + 螢幕投影 + 全域設定 + 關閉 Megingiard + 關閉 Megingiard + 您確定要關閉 Megingiard 嗎? + 關閉 + 關閉對話視窗 + 設定檔名稱 + 配置名稱 + 上一個配置 + 下一個配置 + 編輯視窗 + + + 點擊此處控制上方螢幕 + 拖曳移動 · 輕點點擊 + 鍵盤 + 手把 + 滑鼠 + 巨集 + 設定檔/配置 + 投影選單 + 設定檔 + 全螢幕工具 + 群組 + + + 儲存日誌報告 + 將最近的 logcat 輸出儲存為 .txt 檔案以便回報錯誤 + 日誌報告已儲存。 + 儲存日誌報告失敗。 + + + 還原備份 + 外部檔案… + 從儲存空間選擇 .mgrd 檔案 + %1$d 個設定檔 • %2$d 個配置 • %3$d 個巨集 + + + 複製到設定檔… + 複製到配置… + 選擇目標設定檔 + 選擇目標配置 + 重複按鈕 + 重複設定檔 + 重複配置 + 沒有其他可用的設定檔。 + 沒有其他可用的配置。 + + + 定義裁切區域 + 區塊: %1$s + 完成 + 取消 + 拖曳以移動工具列 + + + 新增區塊 + 刪除所選 + 刪除區塊 + 編輯裁切 + 單個視窗 + 多個區塊 + 完成 + 多個區塊需要特權模式 + 長寬比模式 + 比例: 已鎖定 + 自由 + 頂部 + 底部 + 平滑 + 切換所選區塊的平滑移動 + 平滑度 + %1$d%% + 輕微 + 中等 + 強烈 + 關閉 + 形狀: 圓形 + 形狀: 矩形 + 沒有足夠空間新增另一個區塊 + 最多允許 10 個區塊 + 重新命名區塊 + 區塊名稱 + + + 新增 + 設定 + 刪除 + 裁切 + 完成 + 取消 + 圓形 + 矩形 + 觸控投影 + 將此區塊內的觸控輸入轉發到主螢幕 + 區塊 + 區塊 %1$d + + + + 開啟說明 + 關閉說明 + + + MacroPad 編輯器 — 說明 + 此螢幕讓您建立並組織顯示在巨集板上的每個按鈕。變更會立即儲存 — 沒有單獨的儲存步驟。 + 設定檔 + 設定檔標籤 + 點擊標籤以切換活動設定檔。當前活動的標籤會醒目顯示。每個設定檔包含自己的配置與巨集組,因此您可以為不同遊戲建立獨立設定。 + 新增設定檔 + 點擊設定檔標題旁的加號 (+) 按鈕以建立全新空白設定檔。 + 設定檔選項 + 點擊選項 (三個點) 圖示可複製、重新排序或刪除設定檔。重新命名不在這裡,而是標題列上的那支鉛筆;分配應用程式也不在這裡,而是「巨集…」旁邊那顆按鈕。 + 配置 + 配置標籤 + 一個設定檔可以有多個配置。僅啟用的配置會出現在快速選單中以便快速切換。長按並拖曳配置標籤可以重新排序。 + 新增配置 + 點擊配置標題旁的加號 (+) 按鈕。配置會立刻以預設名稱建立並切換過去,要改名就點配置標題列上的鉛筆。 + 配置選項 + 點擊選項 (三個點) 圖示以複製配置、複製到其他設定檔、重新排序或刪除配置。 + 工具列 + 按鈕 + 在畫布上放置新的巨集按鈕。點擊後,在畫布解鎖狀態下您可以將其拖曳到任何位置。 + 背景 + 開啟背景圖片設定,可以選擇或移除背景圖片、從 SteamGridDB 抓取封面、裁切/縮放圖片,或切換遮罩模式。 + 巨集… + 開啟此設定檔的巨集列表,您可以在此建立、編輯與組織可重複使用的巨集序列。 + 網格 + 循環切換網格模式:關閉 → 矩形 → 徑向。當網格啟用時,按鈕在拖曳時會對齊網格位置,使精確佈局更加容易。 + 鎖定 / 解鎖 + 鎖定可防止意外移動按鈕。點擊鎖定圖示以再次允許重新定位。表格模式的按鈕依格子配置、沒有自由拖曳,因此該功能不會顯示。 + 畫布 + 拖曳按鈕 + 當畫布解鎖時,長按並拖曳任何按鈕即可自由重新定位。放開即可放置。 + 按鈕 + 編輯按鈕 + 點擊畫布下方列表中的任何按鈕以開啟編輯對話視窗。在此您可以更改其標籤、圖示、大小、形狀、顏色、觸感以及分配的巨集動作。 + 重新排序按鈕 + 拖曳列表項右側的控制把手以重新排序列表中的按鈕。 + + + 巨集 — 說明 + 巨集列表顯示屬於當前設定檔的所有可重複使用巨集。巨集是獨立的輸入步驟序列,可以分配給一或多個按鈕。 + 動作 + 新增巨集 + 建立一個新的空白巨集並立即開啟時間軸編輯器,以便您可以開始新增步驟。在編輯器頂端給它一個有意義的名稱。 + 巨集項目 + 點擊巨集列 + 開啟該巨集的時間軸編輯器,以便您查看或編輯其步驟。 + 選項選單 + 每列巨集都有一個選項選單 (三個點),可進行重新命名、重複、複製到設定檔或刪除巨集。刪除是永久性的。 + 拖曳把手 + 長按巨集列右側的拖曳把手並移動,以重新排序列表中的巨集。此處的順序僅供美觀參考,不影響執行。 + + + 巨集編輯器 — 說明 + 時間軸編輯器是用來建構巨集執行的確切輸入序列。步驟會依序從上到下執行。時序可以精確錄製或手動輸入。 + 頂端欄控制 + 巨集名稱欄位 + 點擊文字欄位以重新命名巨集。當您點擊儲存時名稱會被儲存。 + 取消 + 捨棄此巨集所有未儲存的變更並返回巨集列表。您在本次工作期間新增的步驟將會遺失。 + 儲存 + 將當前名稱與所有步驟儲存到設定檔。如果名稱欄位為空,按鈕將顯示為灰色不可用。 + 次要欄 + 復原 + 還原對步驟列表的上一次更改。當前編輯工作期間最多保留 50 個復原步驟。 + 重做 + 重新套用先前復原的更改。每當您進行新編輯時,重做歷史記錄就會被清除。 + 列表/編輯 + 以垂直捲動列表形式顯示所有步驟 — 這是審查與編輯個別步驟時序最易讀的檢視方式。 + 時間軸 + 將步驟水平佈置在時間軸上。適用於發現間隙、重疊以及檢視巨集時序的整體形狀。 + 移動後續步驟 + 控制編輯後的後續步驟如何移動:無 (不移動)、依啟動延遲 (隨啟動時間變更而移動) 或依總持續時間 (隨啟動時間或持續時間變更而移動)。 + 新增與測試步驟 + 新增步驟 + 開啟選擇器供您選擇輸入類型:按鍵、手把按鈕、滑鼠點擊、捲動、延遲等。 + 錄製觸控 + 錄製主螢幕上的實際點擊或手勢,並將其轉換為觸控步驟。如果投影串流尚未活動,系統會自動啟動。 + 錄製手把 + 捕捉實體控制器的即時按鍵動作並轉換為手把步驟。如果特權模式已連接,則使用實體手把錄製器以獲得更高的精確度。 + 測試運行 + 立即在裝置上執行目前的巨集步驟以便您驗證序列。測試運行期間禁用循環播放。 + 步驟選項 + 編輯步驟 + 點擊編輯 (鉛筆) 圖示或步驟列本身,以開啟該步驟的配置對話視窗。 + 刪除步驟 + 點擊刪除 (垃圾桶) 圖示以從巨集時間軸中移除該步驟。 + 循環與時序 + 循環開關 + 啟用後,巨集將持續重複直到停止。您可以使用開關下方出現的滑桿設定兩次循環間的暫停時間。 + 隨機化時序 + 每次執行時為每個步驟的時序新增一個微小的隨機偏移 (±範圍)。這使巨集感覺不那麼機械化,並有助於避免某些應用程式中的自動偵測模式。 + + + 螢幕投影設定 — 說明 + 這些設定控制投影的上方螢幕圖像在背景模式下的外觀 — 即當 Megingiard 在按鈕後方背景運行時。所有變更僅套用於當前活動的配置。 + 調暗 + 透過覆蓋一層黑色蒙版來降低投影圖像的亮度。0% 表示不調暗;90% 幾乎為全黑。使用眼睛圖示可在次螢幕上即時預覽效果而不儲存。 + 邊緣融合 + 使投影圖像的邊緣向內淡出,讓投影內容與背景平滑過渡而非生硬切斷。較高的值會產生更強的暈影效果。 + 將投影視窗中心對齊您在主螢幕上最後觸碰的位置。從下拉選單中選擇一個區塊以啟用,或選擇「關閉」以禁用。 + 區塊列表 + 列出為此配置定義的所有投影區塊。您可以使用編輯 (鉛筆) 圖示重新命名每個區塊,或調整其運動平滑度與觸控投影設定。 + 平滑度 + 套用運動平滑於區塊平移 (關閉、輕微、中等或強烈)。較高的值會使視窗移動在平移過程中更加平滑,但可能會增加極少量的延遲。 + 將投影區塊區域上的觸控輸入轉換回主螢幕。啟用後,區塊內的觸碰會觸發主螢幕上相應的動作。 + 預覽 + 在次螢幕上啟用該滑桿值的即時預覽。與次螢幕的控制項互動以確認或取消該值。這讓您無需離開當前頁面即可看到效果。 + + + 投影編輯器 — 說明 + 投影編輯器讓您在投影過程中,於次螢幕上新增、定位、調整大小與配置區塊。點擊螢幕上的區塊進行選擇,然後使用下方的工具列按鈕執行操作。表格版面會用版面背景圖取代投影畫面,沒有東西可編排,編輯器只會顯示提示。編排期間巨集按鈕會變暗且不反應,邊緣滑動也停用——請用完成或取消離開。離開編輯器後,每個區塊會帶著自己的模式顏色與圖示;編排期間會隱藏,也可以在設定中關閉。 + 全域動作 + 在次螢幕的空閒位置建立一個新的區塊。每個配置最多允許 10 個區塊。新區塊會被自動選中,並預設鎖定「底部」長寬比,因此調整大小時裁切會跟著重新配合。 + 開啟這個配置的背景設定:亮度與邊緣融合、觸控跟隨選擇,以及逐區塊清單,可在其中改名、設定平滑度,並選擇與工具列相同的區塊模式。 + 所選區塊 + 為所選區塊循環切換三種長寬比模式。「自由」— 無限制,可獨立調整大小。「頂部」— 鎖定長寬比以匹配主螢幕的裁切。「底部」— 鎖定長寬比以匹配次螢幕的區塊。 + 形狀 + 在矩形與圓形之間切換所選區塊的形狀。適用於遊戲小地圖或圓形儀表,圓形裁切外觀更佳。 + 開啟所選區塊的裁切選擇器,讓您在主螢幕拖曳框架以選擇投影到此區塊的區域。 + 從配置中永久移除所選區塊。如果被刪除的區塊啟用了觸控跟隨,該模式也會被停用。 + 完成 + 關閉編輯器。所有變更在你操作當下就已套用到配置,這顆只是離開編排模式。 + 把區塊還原成開啟編輯器當下的樣子。在「設定」面板改的亮度、邊緣融合、觸控跟隨已經存檔,不會被還原。 + + + 設定 — 說明 + 全域設定套用於整個應用程式並在會話間持久儲存. 使用頂端的區塊跳轉列可快速捲動到特定群組。 + 如果您的手握姿勢自然落在裝置底部附近,建議開啟此項,使快速選單更容易滑開而無需移動大拇指。 + 在幾秒後視覺上隱藏側邊快速選單列。即使控制列完全不可見,滑動手勢仍可正常感應,讓您隨時開啟快速選單。 + + 實現無縫遊戲體驗的關鍵。將巨集設定檔與特定 App 關聯,以便在啟動遊戲時立即切換自定義按鈕,無需手動選單導覽。 + 保持 Android 最近任務選單整潔。如果您在遊戲時完全透過側邊快速選單列控制 Megingiard,建議開啟此項。 + 自動切換設定檔所需。Android 權限要求此服務以偵測主螢幕上當前活動的遊戲。點擊此列進行配置。 + 更改所有應用程式選單、覆蓋層與說明教學的語言。目前提供英文、德文與繁體中文。 + 如果您偏好任天堂配置 (A 在右側,B 在底部) 而非預設的 Xbox 配置 (A 在底部,B 在右側),請啟用此項。注入的鍵碼將保持不變。 + 自定義活動按鈕與分頁的強調顏色。自定義強調色套用於深色與深色 OLED 主題;點擊顏色預覽以開啟自定義選色輪,或點擊調色盤圖示以選擇預設的主題強調顏色。 + 從 8 種獨特視覺主題中選擇,包括深色、深色 OLED、Megingiard、Mjölnir 鋼鐵、英靈殿日落、北極光、復古磷光體與皇家阿斯嘉特。 + 如果配置錯誤,可使用此項將所有設定還原為出廠預設值。這將刪除您所有自定義的設定檔、配置與巨集。 + 建立自定義設定檔與配置的完整備份。建議在執行應用程式更新或重置設定前進行備份。 + 從先前儲存的 .mgrd 檔案或每日自動備份中還原設定檔與配置。確認後將覆蓋當前配置。 + 將單個設定檔儲存到檔案中,以便與其他玩家分享配置與巨集,而無需洩露您的完整 App 配置。 + 從其他用戶新增分享的設定檔到您的收藏中。此操作是安全的,不會修改或覆蓋您現有的設定檔。 + 開啟 Obtainium (如果已安裝) 並預填 Megingiard 儲存庫連結。如果未安裝 Obtainium,則會在瀏覽器中開啟 Obtainium 的 GitHub 版本發佈頁面。 + 透過無線偵錯建立背景助手連接,以寫入一般應用程式無法訪問的系統輸入裝置。這將啟用:\n\n• 手把合併:結合虛擬與實體手把,使遊戲僅看到一個控制器 (提高相容性)。\n• 實體手把錄製:錄製來自實體控制器的巨集輸入。\n• 特權螢幕投影:無需在每次運行時提示螢幕擷取權限即可開始投影。\n• 螢幕截圖:在未開啟螢幕投影的情況下,直接從快速選單覆蓋層儲存主螢幕截圖。 + 確保類比搖桿錄製不會捕捉到漂移或雜訊。將這些百分比與 Thor 系統設定中的死區設定匹配以獲得最佳準確性。 + 啟用後,如果背景助手連接斷開,在 App 啟動時會顯示幫助重新連接的嚮導對話視窗,讓您能立即配對並恢復。 + 正常遊戲時請保持在 Warn 或 Info。僅在重現問題以收集詳細日誌以供錯誤報告時才設定為 Debug 或 Verbose。 + 匯出最新的系統日誌。在遇到錯誤或當機後立即產生此日誌,並將其附加到 GitHub 議題或開發者報告中。 + + 鍵盤設定 — 說明 + 為虛擬鍵盤配置配置與按鍵。 + 選擇您偏好的字母排列 (QWERTY、QWERTZ 或 AZERTY)。請注意,此配置僅影響字母 (ABC) 鍵盤配置模式。數字與符號配置層在不同區域格式間保持相同。 + 鍵盤開啟時,在鍵盤配置上方的區域顯示一個小型相對觸控板。支援點擊以按一下、雙指點擊 (按右鍵) 以及雙指捲動等手勢。游標速度與捲動速度靈敏度設定繼承自觸控板設定選單。 + + + + 快速選單 — 說明 + 快速選單讓您能快速訪問設定檔、配置與最常用的動作 — 且無需離開當前遊戲。從螢幕邊緣的快速選單列往內滑動即可開啟;點擊背景遮罩即可關閉。 + 投影控制 (頂端卡片) + 開始 / 停止投影 + 開始或停止螢幕投影擷取。投影進行時,「凍結」與「螢幕截圖」會加入該列,「螢幕投影」也才可按。 + 凍結 / 解除凍結 + 凍結投影圖像,使其顯示靜態畫面而非即時畫面。適用於您需要仔細閱讀遊戲場景時暫停畫面。僅在投影進行中才會出現。 + 在次螢幕上開啟視窗配置編輯器,以便您在投影覆蓋層中調整大小、移動或新增透明區塊視窗。未投影時仍在列上,但要開始投影後才可按。 + 螢幕截圖 + 擷取主螢幕截圖。僅在投影進行中、或特權模式已連接時才會出現。 + 設定檔、配置與動作 (底端卡片) + 點擊標籤以立即切換到不同的巨集設定檔。使用中的標籤會醒目顯示,開啟選單時也會被捲到中間;邊緣淡出表示該列還有內容。當該列放不下時,會出現 ··· 按鈕開啟完整清單。設定檔及其配置在「MacroPad 編輯器」中管理。 + 點擊標籤以切換到當前設定檔中的不同按鈕配置。該設定檔的所有配置都會列出;當該列放不下時,會出現 ··· 按鈕開啟完整清單。 + 開啟「MacroPad 編輯器」,您可以在此新增、移動與設定按鈕,管理設定檔與配置,以及建立或編輯巨集。 + 開啟全域設定螢幕以進行 App 範圍配置,例如主題、強調色、投影自動啟動與特權模式設定。 + 關閉 App + 正常關閉 Megingiard,停止螢幕投影與背景服務。會先詢問確認。 + 說明 (此螢幕) + 開啟此說明工作表,其中包含快速選單中每個控制項的解釋。 + + + 歡迎使用 Megingiard! + Megingiard 是 AYN Thor 雙螢幕掌機的助手應用程式。它讓您能配置自定義 MacroPad 設定檔、虛擬鍵盤對應、虛擬觸控板以及螢幕間的投影。 + 如何獲取幫助 + 尋找各個螢幕右上角的說明圖示 (?),以查看該功能的詳細指南與最佳實踐。 + 了解 + 不再顯示 + 重置教學 + 重置所有教學彈窗,以便下次造訪時再次顯示 + 教學現在將再次顯示 + 強制在造訪時再次顯示所有教學螢幕與歡迎彈窗,即使您先前已關閉它們。 + + + 建立巨集 + 巨集讓您能自動化手把輸入或螢幕點擊序列。 + 使用實體按鈕錄製 + 為了獲得最佳體驗,請在全域設定中啟用特權模式。這讓您能透過按下 Thor 裝置上的實體按鈕來錄製巨集。 + 虛擬手把備案 + 如果特權模式未活動,螢幕上將顯示虛擬觸控手把覆蓋層,讓您錄製控制器輸入。 + 手動編輯與時序 + 您可以使用下方的工具列手動新增步驟。執行此操作時,請嘗試思考您在遊戲中實際輸入指令時使用的時序:首先精確模仿那些物理時序與輸入,之後再進行優化 (例如為了更快的執行)。雖然系統會自動新增預設時序以建立邏輯序列,但它們幾乎總是必須根據遊戲的具體需求進行調整。 + 時序隨機化 + 您可以在列表視圖底部的設定區塊下啟用時序隨機化。這會為每次播放的步驟延遲增加微小的隨機偏移,模擬人類輸入的不一致性。對於線上遊戲,強烈建議開啟此項以防止防作弊偵測。請記住,巨集旨在使您的生活更輕鬆,而不應讓其他玩家的生活更艱難;本 App 與此功能不適用於線上作弊。 + 了解 + 不再顯示 + + + 播放歡迎嚮導與快速設定 + 重新執行分步引導教學與邊緣手勢功能導覽 + 開始 + 歡迎使用 + 邊緣手勢 + 主題風格 + 選擇您的主題風格 + 為 Megingiard 選擇視覺色彩與主題。您隨時可以在全域設定中重新調整。 + 上一個主題 + 下一個主題 + 無障礙服務 + 無障礙服務授權 + 無障礙服務是 Megingiard 的必要基礎。啟動遊戲時自動切換巨集配置、設定 Privd 特權模式等核心功能皆需此服務。 + Privd 特權模式 + Privd 特權模式 (強烈推薦!) + 解鎖超低延遲直接觸控注入、Trackpoint 高精度點按與實體搖桿錄製,無需 Root 或電腦。您可以使用一鍵自動設定快速開通。 + 開始自動設定 + 重新連線 Privd 特權模式 + Privd 特權模式已中斷(可能是裝置重新開機或無線除錯被關閉)。 + 自動重新連線 + 已全部設定完成! + 關閉 + 開發者選項 + 無線除錯 + ADB 無線配對 + Daemon 服務連線 + Wi-Fi 已關閉。必須開啟 Wi-Fi 才能進行無線除錯配對。 + 目前的系統語言 (%1$s) 暫不支援自動腳本導航,請改用手動配對。 + 手動設定 Privd 特權模式 + 1. 先暫時跳過此步驟並進行下一步。 + 2. 開啟全域設定 → Privd 特權模式進行配對。 + 3. 依照該處的手動配對說明啟用 Privd 特權模式。 + 完成設定 + 設定完成!準備好開始調校了嗎? + 您已完成歡迎導覽。隨時可從 全域設定 → 一般 重新執行此導覽。 + 第 %1$d 步,共 %2$d 步 + 下一步 + 上一步 + 略過 + 好,開始動手吧! + + 開啟選單 + 滑動螢幕邊緣的快速選單列指示器以開啟快速選單。您可以使用它快速切換設定檔、配置或設定。\n\n注意:如果您喜歡更簡潔的外觀,可以在全域設定 (外觀) 中配置控制列自動隱藏。此行為預設為禁用。 + 鍵盤 + 選單 + 滑鼠 + + + SteamGridDB API 金鑰 + 從 steamgriddb.com 抓取背景圖片所需。在該網站的「個人資料偏好設定」中產生一個金鑰。 + 在此輸入您的 API 金鑰 + 從 SteamGridDB 抓取 + 瀏覽本地圖片 + 需要 API 金鑰 + SteamGridDB 抓取功能需要 API 金鑰。請在您的 SteamGridDB 帳戶偏好設定中建立 API 金鑰,並新增至 Megingiard 的全域設定中。 + 前往設定 + 搜尋遊戲 + 例如: Half-Life 2 + 網格 (Grid) + 主圖 (Hero) + 標誌 (Logo) + 圖示 (Icon) + 搜尋 + 正在搜尋遊戲\u2026 + 正在載入圖片\u2026 + 找不到遊戲。 + 找不到此類型的圖片。 + 選擇遊戲: + 錯誤: %1$s + SteamGridDB 抓取器 + 正在下載圖片\u2026 + 連線已斷開 + 您目前處於離線狀態。請檢查您的網路連線並重試。 + 觸發頻率限制 + 已超過頻率限制。請稍後重試。 + 無法連接 SteamGridDB + 目前無法連接 SteamGridDB 或伺服器已離線。請稍後重試。 + 錯誤 + 發生非預期的錯誤。請重試。 + 確定 + + + 更換封面圖 + + + 觸控板設定 + 手勢與點擊設定 + 觸控(投影)畫面設定 + 收起觸控板 + 觸控板設定 + 強制使用傳統滑鼠模式 + 顯示滑鼠按鈕並禁用投影自動切換行為 + 輕點以點擊 + 單指輕點即為滑鼠左鍵點擊 + 雙指輕點 + 雙指輕點即為滑鼠右鍵點擊 + 三指輕點 + 三指輕點即為滑鼠中鍵點擊 + 三指按住圈選與拖曳 + 三指按住移動即可拖曳圈選文字或視窗 + 雙指捲動 + 雙指垂直拖曳以捲動螢幕 + 自然捲動 + 內容隨手指移動方向捲動 + 捲動速度 + 調整捲動靈敏度倍率 + 啟用滑鼠 4/5 按鈕 + 在頂端角落顯示實體返回 (M4) 與前進 (M5) 按鈕 + 游標速度 + 調整游標靈敏度倍率 + 觸感回饋 + 在點擊與手勢時產生輕微振動 + 觸控板說明 + 配置全螢幕虛擬觸控板的選項。 + 相對滑鼠模式使用手指移動量來移動游標,而絕對模式則直接投影觸控座標。 + 可透過單、雙或三指短促輕點來模擬左鍵、右鍵與中鍵點擊。 + 使用雙指垂直拖曳以發送滑鼠滾輪事件。 + 觸控板投影 + 在觸控板區域內投影上方顯示器 + 投影亮度遮罩 + + M4 + M5 + 相對滑鼠模式 + 絕對觸控模式 + 切換觸控板投影 + 單指短促輕點即執行滑鼠左鍵點擊。 + 雙指短促輕點即執行滑鼠右鍵點擊。 + 三指短促輕點即執行滑鼠中鍵點擊。 + 三指按住移動以啟動左鍵按住拖曳圈選。 + 在相對滑鼠區域的角落顯示 M4 (返回) 與 M5 (前進) 按鈕。 + 啟用絕對觸控投影與主顯示器的即時影像投影。 + 調整觸控板內投影螢幕的亮度覆蓋層。 + 調整相對滑鼠模式下的游標移動速度倍率。 + 啟用自然捲動方向:向下拖曳手指會將螢幕內容拉下。 + 調整雙指捲動的靈敏度/速度。 + 點擊視覺按鈕或執行點擊手勢時,啟用細微的實體觸感震動。 + + 觸控板設定 + 背景觸控板 + 啟用背景觸控板 + 將空白畫布區域作為相對滑鼠觸控板使用 + 觸控板設定 + 配置空白畫布區域的特定配置相對滑鼠觸控板行為。 + 禁用觸控投影? + 啟用背景觸控板會關閉此配置中所有區塊的觸控投影。 + 關閉觸控投影並啟用觸控板 + 禁用背景觸控板? + 啟用觸控投影將禁用此配置的背景觸控板。 + 禁用觸控板並啟用投影 + 與目前啟用的觸控投影不相容 + 與目前啟用的背景觸控板不相容 + 背景觸控板 + 游標速度 + 手勢與動作 + 捲動 + 觸感 + 包含圖片 + 背景圖片 (%1$d) + + + Megingiard 配套程式 + 已連線的用戶端 + 使用中的控制設定檔 + 停留中的遊戲/應用程式 + + 沒有使用中的設定檔 + 設定檔配置 + 已連結的設定檔 + 編輯配置 + 尚未為此應用程式設定任何設定檔。 + 建立設定檔 + 連結既有設定檔 + 沒有未指派的設定檔 + 狀態面板 + 特權 Daemon + 螢幕投影 + 無障礙服務 + 啟用中 + 未啟用 + 執行中 + 已停止 + 未知 + + 新增設定檔 + 預設 + + diff --git a/app/src/main/res/xml/locales_config.xml b/app/src/main/res/xml/locales_config.xml index e04caba19..13bf974ff 100644 --- a/app/src/main/res/xml/locales_config.xml +++ b/app/src/main/res/xml/locales_config.xml @@ -2,4 +2,5 @@ + diff --git a/gamefocus/src/main/res/values-zh-rTW/strings.xml b/gamefocus/src/main/res/values-zh-rTW/strings.xml new file mode 100644 index 000000000..94e7c4376 --- /dev/null +++ b/gamefocus/src/main/res/values-zh-rTW/strings.xml @@ -0,0 +1,70 @@ + + + Megingiard Game Focus + 遊戲聚焦啟動器 + 重新整理 + 搜尋應用程式與遊戲… + 找不到應用程式 + 更換封面圖 + 取消 + + 主螢幕 + 底部螢幕 + Android 遊戲 + Android 應用程式 + 所有應用程式 + 我的最愛 + 最近使用 + 加入我的最愛 + 從我的最愛移除 + 隱藏 + 取消隱藏 + 編輯 + 應用程式資訊 + 關閉 + 動作 + 尚未加入任何最愛應用程式 + 沒有最近使用的應用程式 + 找不到 Android 遊戲 + 找不到 Android 應用程式 + + 需要 API 金鑰 + SteamGridDB 抓取功能需要 API 金鑰。請在您的 SteamGridDB 帳戶偏好設定中建立 API 金鑰,並新增至 Megingiard 的全域設定中。 + 確定 + 搜尋 + 取消編輯 + 輸入搜尋關鍵字… + 無法預覽 + SteamGridDB 封面選項 + 正在下載封面圖… + 正在搜尋 SteamGridDB… + 正在取得遊戲封面… + 更改搜尋關鍵字 + 使用應用程式圖示 + + 遊戲庫 + 全部 + Android 應用程式 + Android 遊戲 + 按 R2 或 B 關閉 + 圖庫 + 遊戲庫 + 新增 ROM 資料夾 + 移除 ROM 資料夾 + 移除 ROM 資料夾 + 選擇要移除的 ROM 資料夾/系統: + 確認移除 + 確定要移除 ROM 系統「%1$s」嗎?已掃描到的遊戲將從啟動器中移除。 + + 已辨識系統 + 此資料夾已辨識為系統:%1$s。 + 選擇 RetroArch 核心: + 建議(%1$s) + 儲存 + 此系統使用原生模擬,不需要 RetroArch 核心。 + 關閉 + 取消 + 選擇 + 未知系統 + + From a1f42b7d9f40cdacefaea9efdf671e3f7466c969 Mon Sep 17 00:00:00 2001 From: yangeric Date: Wed, 5 Aug 2026 21:43:22 +0800 Subject: [PATCH 02/17] feat(privd): support Traditional Chinese in Auto Setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Required by the localization policy: every language Megingiard ships a translation for must also be able to complete privileged Auto Setup, which matches Settings entries by their text in the device language. - add `CHINESE_TW` with the Traditional Chinese Settings wording (版本號碼, 無線偵錯, USB 偵錯, 使用配對碼配對裝置, 允許), plus `CHINESE_HK` and `CHINESE_MO` sharing it, and register all three in `CONFIGS_BY_TAG` - resolve a bare `zh` locale through region and script subtags rather than the primary language alone, so only Traditional regions (TW/HK/MO/Hant) match - keep Simplified Chinese (zh-CN, zh-SG, zh-Hans) unsupported: its Settings entries read 版本号 / 无线调试, so falling back to the Traditional keywords would stall Auto Setup mid-flow instead of reporting the language as unsupported - cover the new resolution paths in `AutoSetupLanguageConfigTest`, including the Simplified-stays-null cases the existing suite already asserted Co-Authored-By: Claude Opus 5 (1M context) --- .../privd/AutoSetupLanguageConfig.kt | 55 ++++++++++++++++++- .../privd/AutoSetupLanguageConfigTest.kt | 40 ++++++++++++++ 2 files changed, 94 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt b/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt index ac643ad5e..4f5250429 100644 --- a/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt +++ b/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt @@ -132,6 +132,40 @@ data class AutoSetupLanguageConfig( val ENGLISH = ENGLISH_US + val CHINESE_TW = + AutoSetupLanguageConfig( + localeTag = "zh-TW", + languageCode = "zh", + buildNumberQueryAndKeyword = "版本號碼", + wirelessDebuggingQueryAndKeyword = "無線偵錯", + usbDebuggingQueryAndKeyword = "USB 偵錯", + pairDeviceKeywords = + listOf( + "使用配對碼配對裝置", + "配對碼配對", + "使用配對碼", + "裝置配對碼", + "配對碼", + ), + allowButtonKeywords = + listOf( + "允許", + "確定", + ), + ) + + val CHINESE_HK = CHINESE_TW.copy(localeTag = "zh-HK") + val CHINESE_MO = CHINESE_TW.copy(localeTag = "zh-MO") + + /** + * Traditional Chinese only. Simplified Chinese (zh-CN, zh-SG) uses different + * Settings wording ("版本号", "无线调试", …) and is deliberately not mapped here, + * so there is no alias for a bare `CHINESE`. + */ + val CHINESE_TRADITIONAL = CHINESE_TW + + private val TRADITIONAL_CHINESE_SUBTAGS = setOf("tw", "hk", "mo", "hant") + private val CONFIGS_BY_TAG: Map = listOf( GERMAN_DE, @@ -146,8 +180,21 @@ data class AutoSetupLanguageConfig( ENGLISH_GB, ENGLISH_CA, ENGLISH_AU, + CHINESE_TW, + CHINESE_HK, + CHINESE_MO, ).associateBy { it.localeTag.lowercase() } + /** + * Resolves a `zh` locale to the Traditional Chinese config only when the region or + * script subtag says so. Simplified Chinese must return null rather than fall back + * to Traditional keywords: the Settings entries it would search for do not exist on + * a Simplified device, so Auto Setup would stall instead of reporting the language + * as unsupported. + */ + private fun traditionalChineseOrNull(subtags: List): AutoSetupLanguageConfig? = + if (subtags.any { it in TRADITIONAL_CHINESE_SUBTAGS }) CHINESE_TW else null + /** * Selects appropriate [AutoSetupLanguageConfig] for given [Locale], or null if unsupported. */ @@ -164,6 +211,10 @@ data class AutoSetupLanguageConfig( "es" -> SPANISH_ES "fr" -> FRENCH_FR "en" -> ENGLISH_US + "zh" -> + traditionalChineseOrNull( + listOf(locale.country.lowercase(), locale.script.lowercase()), + ) else -> null } } @@ -184,12 +235,14 @@ data class AutoSetupLanguageConfig( return matchedByTag } - val primaryLang = cleanTag.split("-").firstOrNull() ?: "" + val subtags = cleanTag.split("-") + val primaryLang = subtags.firstOrNull() ?: "" return when (primaryLang) { "de" -> GERMAN_DE "es" -> SPANISH_ES "fr" -> FRENCH_FR "en" -> ENGLISH_US + "zh" -> traditionalChineseOrNull(subtags) else -> null } } diff --git a/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt b/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt index 2cc4f1473..96f7a7f92 100644 --- a/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt +++ b/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt @@ -1,6 +1,7 @@ package com.stormpanda.megingiard.privd import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull import org.junit.Test import java.util.Locale @@ -124,4 +125,43 @@ class AutoSetupLanguageConfigTest { org.junit.Assert.assertNotNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("fr-FR")) org.junit.Assert.assertNotNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("en-US")) } + + @Test + fun fromLocale_returnsRegionSpecificTraditionalChineseConfigs() { + val configZhTw = AutoSetupLanguageConfig.fromLocale(Locale.TRADITIONAL_CHINESE) + val configZhHk = AutoSetupLanguageConfig.fromLocale(Locale("zh", "HK")) + val configZhMo = AutoSetupLanguageConfig.fromLocale(Locale("zh", "MO")) + + assertEquals("zh-TW", configZhTw.localeTag) + assertEquals("zh", configZhTw.languageCode) + assertEquals("版本號碼", configZhTw.buildNumberQueryAndKeyword) + assertEquals("無線偵錯", configZhTw.wirelessDebuggingQueryAndKeyword) + assertEquals("USB 偵錯", configZhTw.usbDebuggingQueryAndKeyword) + + assertEquals("zh-HK", configZhHk.localeTag) + assertEquals("zh-MO", configZhMo.localeTag) + } + + @Test + fun fromLanguageTag_resolvesTraditionalChineseTags() { + assertEquals("zh-TW", AutoSetupLanguageConfig.fromLanguageTag("zh-TW").localeTag) + assertEquals("zh-HK", AutoSetupLanguageConfig.fromLanguageTag("zh-HK").localeTag) + + // Script subtag present but no region, and region present alongside a script subtag. + assertEquals("zh-TW", AutoSetupLanguageConfig.fromLanguageTag("zh-Hant").localeTag) + assertEquals("zh-TW", AutoSetupLanguageConfig.fromLanguageTag("zh-Hant-TW").localeTag) + } + + @Test + fun simplifiedChineseStaysUnsupportedAndDoesNotFallBackToTraditional() { + // Simplified Settings entries read "版本号" / "无线调试", so the Traditional keywords + // would never match. Reporting the language as unsupported is correct; silently + // handing back the Traditional config would stall Auto Setup mid-flow. + assertNull(AutoSetupLanguageConfig.fromLocaleOrNull(Locale.SIMPLIFIED_CHINESE)) + assertNull(AutoSetupLanguageConfig.fromLocaleOrNull(Locale("zh", "SG"))) + assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("zh-CN")) + assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("zh-Hans")) + assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("zh-Hans-CN")) + assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("zh")) + } } From 6fa4442c2c7cd6e4ca599204fdc070d6e095b23d Mon Sep 17 00:00:00 2001 From: yangeric Date: Wed, 5 Aug 2026 23:13:36 +0800 Subject: [PATCH 03/17] fix(privd): detect the Developer options screen in every supported locale MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auto Setup told the Wireless debugging sub-screen apart from Developer options through a Developer options title list hardcoded in the service, covering only English, German, Spanish and French. Both screens carry the wireless debugging label, so on any other system language the check reported the sub-screen while still on Developer options: the loop advanced to CLICK_PAIR_DIALOG and scrolled Developer options 25 times looking for a pairing row that only exists one screen deeper, then timed out. Verified on an AYN Thor with the system language set to zh-Hant-TW. - move the hardcoded titles into `AutoSetupLanguageConfig` as the default of a new `developerOptionsKeywords`, leaving the four existing locale configs untouched and their behaviour identical - give `CHINESE_TW` its own `開發人員選項`, since its title is not among the defaults - read the keywords from the config in `isWirelessDebuggingSubScreen`, so adding a locale no longer means editing the accessibility service - assert every config carries lowercase keywords, the matching lowercases the screen text Co-Authored-By: Claude Opus 5 (1M context) --- .../MegingiardAccessibilityService.kt | 14 ++++--- .../privd/AutoSetupLanguageConfig.kt | 22 ++++++++++ .../privd/AutoSetupLanguageConfigTest.kt | 40 +++++++++++++++++++ 3 files changed, 70 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/stormpanda/megingiard/services/MegingiardAccessibilityService.kt b/app/src/main/java/com/stormpanda/megingiard/services/MegingiardAccessibilityService.kt index 01e50baca..d0c02fbeb 100644 --- a/app/src/main/java/com/stormpanda/megingiard/services/MegingiardAccessibilityService.kt +++ b/app/src/main/java/com/stormpanda/megingiard/services/MegingiardAccessibilityService.kt @@ -201,7 +201,7 @@ class MegingiardAccessibilityService : AccessibilityService() { val isWirelessOn = isWirelessDebuggingActive(context) val hasLocalCreds = PrivdBootstrapper.hasCredentials(context) - val isSubScreen = isWirelessDebuggingSubScreen(rootNode, config.wirelessDebuggingQueryAndKeyword) + val isSubScreen = isWirelessDebuggingSubScreen(rootNode, config) if (isSubScreen) { if (isWirelessOn) { @@ -715,17 +715,19 @@ class MegingiardAccessibilityService : AccessibilityService() { private fun isWirelessDebuggingSubScreen( rootNode: AccessibilityNodeInfo, - wirelessDebuggingKeyword: String, + config: AutoSetupLanguageConfig, ): Boolean { val sb = StringBuilder() collectAllText(rootNode, sb) val allText = sb.toString().lowercase() - val devOptionsKeywords = - listOf("developer options", "entwickleroptionen", "opciones de desarrollador", "options pour les développeurs") - val isDevOptionsPresent = devOptionsKeywords.any { kw -> allText.contains(kw) } + // The Developer options screen also carries the Wireless debugging label — it is the row + // that leads into the sub-screen. Distinguishing the two therefore needs the Developer + // options title in the active system language, which lives in the language config so + // every supported locale stays in one place. + val isDevOptionsPresent = config.developerOptionsKeywords.any { kw -> allText.contains(kw.lowercase()) } - val isWirelessDebuggingPresent = allText.contains(wirelessDebuggingKeyword.lowercase()) + val isWirelessDebuggingPresent = allText.contains(config.wirelessDebuggingQueryAndKeyword.lowercase()) return isWirelessDebuggingPresent && !isDevOptionsPresent } diff --git a/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt b/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt index 4f5250429..4e37322fc 100644 --- a/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt +++ b/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt @@ -14,6 +14,10 @@ import java.util.Locale * @property wirelessDebuggingQueryAndKeyword Search query and UI node matching string for Wireless Debugging. * @property usbDebuggingQueryAndKeyword Search query and UI node matching string for USB Debugging. * @property pairDeviceKeywords List of UI text keywords used to identify the "Pair device with pairing code" row. + * @property developerOptionsKeywords Lowercased UI text keywords identifying the Developer options screen, + * used to tell it apart from the Wireless debugging sub-screen (both carry the wireless debugging label). + * Defaults to the titles of the previously supported locales, so a new locale whose title is not + * among them must supply its own — otherwise Auto Setup mistakes Developer options for the sub-screen. * @property allowButtonKeywords List of UI text keywords used to confirm network trust dialogs. */ data class AutoSetupLanguageConfig( @@ -23,9 +27,23 @@ data class AutoSetupLanguageConfig( val wirelessDebuggingQueryAndKeyword: String, val usbDebuggingQueryAndKeyword: String, val pairDeviceKeywords: List, + val developerOptionsKeywords: List = DEFAULT_DEVELOPER_OPTIONS_KEYWORDS, val allowButtonKeywords: List, ) { companion object { + /** + * Developer options titles for the locales that shipped before this field existed. + * Kept as the default so those configs stay unchanged; a locale whose title is not + * listed here must pass its own [developerOptionsKeywords]. + */ + private val DEFAULT_DEVELOPER_OPTIONS_KEYWORDS = + listOf( + "developer options", + "entwickleroptionen", + "opciones de desarrollador", + "options pour les développeurs", + ) + val GERMAN_DE = AutoSetupLanguageConfig( localeTag = "de-DE", @@ -147,6 +165,10 @@ data class AutoSetupLanguageConfig( "裝置配對碼", "配對碼", ), + developerOptionsKeywords = + listOf( + "開發人員選項", + ), allowButtonKeywords = listOf( "允許", diff --git a/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt b/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt index 96f7a7f92..21a73204c 100644 --- a/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt +++ b/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt @@ -126,6 +126,46 @@ class AutoSetupLanguageConfigTest { org.junit.Assert.assertNotNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("en-US")) } + @Test + fun everyConfigCarriesDeveloperOptionsKeywords() { + // Auto Setup tells the Wireless debugging sub-screen apart from Developer options by + // looking for the Developer options title, because both screens show the wireless + // debugging label. A locale missing this keyword reports the sub-screen while still on + // Developer options and then scrolls forever looking for the pairing row. + val configs = + listOf( + AutoSetupLanguageConfig.GERMAN_DE, + AutoSetupLanguageConfig.SPANISH_ES, + AutoSetupLanguageConfig.FRENCH_FR, + AutoSetupLanguageConfig.ENGLISH_US, + AutoSetupLanguageConfig.CHINESE_TW, + ) + + configs.forEach { config -> + val keywords = config.developerOptionsKeywords + org.junit.Assert.assertTrue( + "${config.localeTag} has no developerOptionsKeywords", + keywords.isNotEmpty(), + ) + keywords.forEach { keyword -> + org.junit.Assert.assertTrue( + "${config.localeTag} keyword '$keyword' must be lowercase — matching lowercases the screen text", + keyword == keyword.lowercase(), + ) + } + } + + // The locales that predate this field keep the shared default, so their behaviour is + // unchanged; Traditional Chinese overrides it because its title is not in that default. + org.junit.Assert.assertTrue( + AutoSetupLanguageConfig.ENGLISH_US.developerOptionsKeywords.contains("developer options"), + ) + org.junit.Assert.assertTrue( + AutoSetupLanguageConfig.GERMAN_DE.developerOptionsKeywords.contains("entwickleroptionen"), + ) + assertEquals(listOf("開發人員選項"), AutoSetupLanguageConfig.CHINESE_TW.developerOptionsKeywords) + } + @Test fun fromLocale_returnsRegionSpecificTraditionalChineseConfigs() { val configZhTw = AutoSetupLanguageConfig.fromLocale(Locale.TRADITIONAL_CHINESE) From 607c20247138b763edcbe45d00fca680a403c740 Mon Sep 17 00:00:00 2001 From: yangeric Date: Thu, 6 Aug 2026 22:10:04 +0800 Subject: [PATCH 04/17] feat(i18n): sync Traditional Chinese with the current upstream baseline - add the 23 keys upstream introduced since the previous branch base - retranslate 9 keys whose English source text changed upstream - drop settings_jump_privileged_mode and settings_section_privileged_mode, renamed upstream to settings_jump_input and settings_section_input - retranslate 29 entries that had been carried over from this fork's own strings, where upstream's English says something different: app_name no longer reads "Megingiard-zh", macropad_editor_new_profile_name no longer carries a %1$d that upstream never supplies, and the help texts no longer document behaviour that only exists in the fork Co-Authored-By: Claude Opus 5 (1M context) --- app/src/main/res/values-zh-rTW/strings.xml | 105 ++++++++++++--------- 1 file changed, 63 insertions(+), 42 deletions(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 0487dd48a..d3cc339d2 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1,5 +1,5 @@ - Megingiard-zh + Megingiard 遊戲聚焦啟動器 重新整理 搜尋應用程式與遊戲… @@ -189,7 +189,7 @@ 兩側 此配置已禁用滑鼠 此配置已禁用觸控 - 設定檔 %1$d + 新設定檔 重新命名 刪除設定檔 刪除此設定檔? @@ -299,7 +299,7 @@ 活動設定檔 - 新設定檔 + 新設定檔… Pad 形狀 正方形 圓形 @@ -357,12 +357,13 @@ 關閉 正在連接… 正在初始化… - 運行中 + 運行中 (v%1$d) + 已啟用 失敗 連接 斷開連接 測試連接 - 設定… + 設定 測試自動切換 自動設定 重試自動設定 @@ -383,7 +384,8 @@ 配置死區 - 左: %1$d %% · 右: %2$d %% + 為了讓巨集能完全一致地重現類比輸入,請將死區對齊 Thor 設定中的數值。 + 左搖桿:%1$d%% · 右搖桿:%2$d%% 手把死區 開啟 Thor 內建設定 → Thor Settings → Joystick calibration & Gamepad test → Joystick deadzone radius 查看各搖桿值,然後填入下方。 左搖桿 @@ -409,22 +411,36 @@ 正在初始化… 按照以下步驟啟用無線偵錯: + 若尚未啟用開發人員選項: 1. 開啟設定 → 關於掌上型主機 2. 連續點擊「版本號碼」7 次直到出現「您現在已成為開發人員!」 - 3. 返回 → System → 開發人員選項 - 4. 啟用「無線偵錯」並將其開啟 - 5. 點擊「使用配對碼配對裝置」並保持該對話視窗開啟 + 3. 返回 → 系統 → 開發人員選項 + 4. 啟用「USB 偵錯」並將其開啟 + 5. 啟用「無線偵錯」並將其開啟 + 若已啟用開發人員選項: + 1. 開啟設定 → 系統 → 開發人員選項 + 2. 啟用「USB 偵錯」並將其開啟 + 3. 啟用「無線偵錯」並將其開啟 開啟系統設定 - - 在無線偵錯設定中點擊「使用配對碼配對裝置」。輸入該對話視窗中顯示的配對連接埠與 6 位數配對碼。連線連接埠會自動偵測。 - 配對連接埠 (來自「配對裝置」對話視窗) + 請先在開發人員選項中同時開啟「USB 偵錯」與「無線偵錯」才能繼續。 + + 輸入無線偵錯選單中的連線連接埠,以及「使用配對碼配對裝置」對話視窗中的配對碼與配對連接埠。 + 輸入無線偵錯主選單中「IP 位址與連接埠」下方顯示的 5 位數連線連接埠。 + IP 位址與連接埠範例:192.168.1.42: + 在無線偵錯設定中點擊「使用配對碼配對裝置」,然後輸入 6 位數配對碼與 5 位數配對連接埠。 + 配對碼範例: + 配對 IP 位址與連接埠範例:192.168.1.42: + 取自無線偵錯選單 + 連線連接埠(「IP 位址與連接埠」下方的 5 位數) + 取自配對對話視窗 + 配對連接埠(「配對裝置」下方的 5 位數) 配對碼 (6 位數) 配對 - 配對失敗。請檢查配對連接埠與配對碼,然後重試。 + 配對失敗。請檢查各連接埠與配對碼,然後重試。 自動填入(讀取螢幕) 正在掃描螢幕… 已自動填入配對資訊! - 未能在螢幕上偵測到配對資訊。請確認「配對裝置」對話框顯示於上螢幕。 + 無法在螢幕上偵測到完整的配對資訊。請確認上螢幕已開啟無線偵錯選單或「配對裝置」對話視窗。 請在系統設定中啟用無障礙服務,才能透過讀取螢幕文字自動填入。 Megingiard 現在將推送助手 daemon 執行檔到 /data/local/tmp 並啟動它。Daemon 以 ADB shell 使用者身份運行,直到您重啟設備為止。 @@ -453,6 +469,7 @@ 推送 daemon 檔案失敗。請檢查 /data/local/tmp 中的可用空間。 啟動 daemon 進程失敗。 提供驗證金鑰失敗。請重新執行設定嚮導。 + Daemon 版本不符。請點擊「自動設定」或「連接」以更新 daemon。 特權模式已啟用並正在運行。 找不到配對憑據。請執行設定嚮導來配對此裝置。 無線偵錯未啟用。請在開發人員選項中開啟並重試。 @@ -465,9 +482,9 @@ 外觀 數據 配置 - 特權模式 + 輸入 診斷 - 特權模式 + 輸入 診斷 展開區塊 收起區塊 @@ -548,7 +565,7 @@ 背景圖片預覽 裁切 裁切背景圖片 - 拖曳選取框以移動 • 拖曳角落以調整大小 + 拖曳以平移 • 雙指縮放 背景 調暗 按鈕預設顏色 @@ -817,7 +834,7 @@ 關閉說明 - MacroPad 編輯器 — 說明 + 編輯設定檔/配置 — 說明 此螢幕讓您建立並組織顯示在巨集板上的每個按鈕。變更會立即儲存 — 沒有單獨的儲存步驟。 設定檔 設定檔標籤 @@ -825,14 +842,14 @@ 新增設定檔 點擊設定檔標題旁的加號 (+) 按鈕以建立全新空白設定檔。 設定檔選項 - 點擊選項 (三個點) 圖示可複製、重新排序或刪除設定檔。重新命名不在這裡,而是標題列上的那支鉛筆;分配應用程式也不在這裡,而是「巨集…」旁邊那顆按鈕。 + 點擊選項 (三個點) 圖示可重新命名、複製、重新排序或刪除設定檔。 配置 配置標籤 一個設定檔可以有多個配置。僅啟用的配置會出現在快速選單中以便快速切換。長按並拖曳配置標籤可以重新排序。 新增配置 - 點擊配置標題旁的加號 (+) 按鈕。配置會立刻以預設名稱建立並切換過去,要改名就點配置標題列上的鉛筆。 + 點擊「配置」標題旁的新增 (+) 按鈕,即可在目前設定檔中建立新配置。 配置選項 - 點擊選項 (三個點) 圖示以複製配置、複製到其他設定檔、重新排序或刪除配置。 + 點擊選項 (三個點) 圖示可重新命名、複製、複製到其他設定檔或刪除配置。 工具列 按鈕 在畫布上放置新的巨集按鈕。點擊後,在畫布解鎖狀態下您可以將其拖曳到任何位置。 @@ -843,7 +860,7 @@ 網格 循環切換網格模式:關閉 → 矩形 → 徑向。當網格啟用時,按鈕在拖曳時會對齊網格位置,使精確佈局更加容易。 鎖定 / 解鎖 - 鎖定可防止意外移動按鈕。點擊鎖定圖示以再次允許重新定位。表格模式的按鈕依格子配置、沒有自由拖曳,因此該功能不會顯示。 + 鎖定可防止意外移動按鈕。點擊鎖定圖示即可再次允許重新定位。 畫布 拖曳按鈕 當畫布解鎖時,長按並拖曳任何按鈕即可自由重新定位。放開即可放置。 @@ -926,10 +943,10 @@ 投影編輯器 — 說明 - 投影編輯器讓您在投影過程中,於次螢幕上新增、定位、調整大小與配置區塊。點擊螢幕上的區塊進行選擇,然後使用下方的工具列按鈕執行操作。表格版面會用版面背景圖取代投影畫面,沒有東西可編排,編輯器只會顯示提示。編排期間巨集按鈕會變暗且不反應,邊緣滑動也停用——請用完成或取消離開。離開編輯器後,每個區塊會帶著自己的模式顏色與圖示;編排期間會隱藏,也可以在設定中關閉。 + 投影編輯器讓您在投影過程中,於次螢幕上新增、定位、調整大小與設定視窗區塊。點擊螢幕上的區塊即可選取,然後使用下方的工具列按鈕進行操作。 全域動作 - 在次螢幕的空閒位置建立一個新的區塊。每個配置最多允許 10 個區塊。新區塊會被自動選中,並預設鎖定「底部」長寬比,因此調整大小時裁切會跟著重新配合。 - 開啟這個配置的背景設定:亮度與邊緣融合、觸控跟隨選擇,以及逐區塊清單,可在其中改名、設定平滑度,並選擇與工具列相同的區塊模式。 + 在次螢幕的空閒位置建立新的視窗區塊。每個配置最多允許 10 個區塊。新區塊會自動被選取。 + 開啟該配置的環境顯示設定(亮度、邊緣融合、平滑度)。變更僅套用於目前使用中的配置。 所選區塊 為所選區塊循環切換三種長寬比模式。「自由」— 無限制,可獨立調整大小。「頂部」— 鎖定長寬比以匹配主螢幕的裁切。「底部」— 鎖定長寬比以匹配次螢幕的區塊。 形狀 @@ -937,8 +954,8 @@ 開啟所選區塊的裁切選擇器,讓您在主螢幕拖曳框架以選擇投影到此區塊的區域。 從配置中永久移除所選區塊。如果被刪除的區塊啟用了觸控跟隨,該模式也會被停用。 完成 - 關閉編輯器。所有變更在你操作當下就已套用到配置,這顆只是離開編排模式。 - 把區塊還原成開啟編輯器當下的樣子。在「設定」面板改的亮度、邊緣融合、觸控跟隨已經存檔,不會被還原。 + 儲存本次的所有變更並關閉編輯器。更新後的區塊配置會立即生效。 + 捨棄開啟編輯器後的所有變更,並將配置還原為先前的狀態。不會儲存任何修改。 設定 — 說明 @@ -973,22 +990,22 @@ 快速選單 — 說明 - 快速選單讓您能快速訪問設定檔、配置與最常用的動作 — 且無需離開當前遊戲。從螢幕邊緣的快速選單列往內滑動即可開啟;點擊背景遮罩即可關閉。 + 快速選單讓您快速存取設定檔、配置與最常用的動作 — 且無需離開目前的遊戲。點擊螢幕邊緣的快速選單列即可開啟;點擊背景遮罩即可關閉。 投影控制 (頂端卡片) 開始 / 停止投影 - 開始或停止螢幕投影擷取。投影進行時,「凍結」與「螢幕截圖」會加入該列,「螢幕投影」也才可按。 + 開始或停止螢幕投影擷取。投影進行時,頂端卡片會展開以顯示更多控制項。 凍結 / 解除凍結 - 凍結投影圖像,使其顯示靜態畫面而非即時畫面。適用於您需要仔細閱讀遊戲場景時暫停畫面。僅在投影進行中才會出現。 - 在次螢幕上開啟視窗配置編輯器,以便您在投影覆蓋層中調整大小、移動或新增透明區塊視窗。未投影時仍在列上,但要開始投影後才可按。 - 螢幕截圖 - 擷取主螢幕截圖。僅在投影進行中、或特權模式已連接時才會出現。 - 設定檔、配置與動作 (底端卡片) - 點擊標籤以立即切換到不同的巨集設定檔。使用中的標籤會醒目顯示,開啟選單時也會被捲到中間;邊緣淡出表示該列還有內容。當該列放不下時,會出現 ··· 按鈕開啟完整清單。設定檔及其配置在「MacroPad 編輯器」中管理。 - 點擊標籤以切換到當前設定檔中的不同按鈕配置。該設定檔的所有配置都會列出;當該列放不下時,會出現 ··· 按鈕開啟完整清單。 - 開啟「MacroPad 編輯器」,您可以在此新增、移動與設定按鈕,管理設定檔與配置,以及建立或編輯巨集。 + 凍結投影影像,使其顯示靜態畫面而非即時畫面。適合在您需要仔細閱讀遊戲畫面時暫停。 + 在次螢幕開啟區塊配置編輯器,讓您在投影覆蓋層中調整大小、移動或新增透明區塊視窗。 + 擷取螢幕截圖 + 擷取主螢幕的截圖。若未在投影中,則需要特權模式已連接。 + MacroPad 控制項 (底端卡片) + 點擊標籤即可立即切換到不同的巨集設定檔。目前使用中的設定檔會醒目顯示。設定檔及其配置在「編輯設定檔/配置」畫面中建立與管理。 + 點擊標籤即可切換到目前設定檔中的不同按鈕配置。此處只會顯示在編輯器中標記為啟用的配置。 + 開啟「編輯設定檔/配置」畫面,您可以在此新增、移動與設定按鈕,管理設定檔與配置,以及建立或編輯巨集。 開啟全域設定螢幕以進行 App 範圍配置,例如主題、強調色、投影自動啟動與特權模式設定。 關閉 App - 正常關閉 Megingiard,停止螢幕投影與背景服務。會先詢問確認。 + 正常關閉 Megingiard,並停止螢幕投影與背景服務。 說明 (此螢幕) 開啟此說明工作表,其中包含快速選單中每個控制項的解釋。 @@ -1106,7 +1123,7 @@ 觸控板設定 手勢與點擊設定 - 觸控(投影)畫面設定 + 絕對觸控模式 收起觸控板 觸控板設定 強制使用傳統滑鼠模式 @@ -1117,8 +1134,8 @@ 雙指輕點即為滑鼠右鍵點擊 三指輕點 三指輕點即為滑鼠中鍵點擊 - 三指按住圈選與拖曳 - 三指按住移動即可拖曳圈選文字或視窗 + 點擊並拖曳 + 點兩下並按住即可拖曳項目 雙指捲動 雙指垂直拖曳以捲動螢幕 自然捲動 @@ -1176,7 +1193,7 @@ 手勢與動作 捲動 觸感 - 包含圖片 + 包含背景圖片 背景圖片 (%1$d) @@ -1203,7 +1220,11 @@ 已停止 未知 + 最後偵測到的 ROM + 未偵測到 新增設定檔 預設 + 顯示 + 顯示儀表板 From 28f814a614624b5909c01562c554fa436fb12a15 Mon Sep 17 00:00:00 2001 From: yangeric Date: Sat, 8 Aug 2026 20:55:36 +0800 Subject: [PATCH 05/17] feat(i18n): sync 0.8.0 strings, make developerOptionsKeywords explicit for all locales, and fix Windows build/nativeCTest execution --- app/build.gradle.kts | 14 +- app/src/main/assets/megingiard_mirror.dex | Bin 11060 -> 11072 bytes app/src/main/res/values-zh-rTW/strings.xml | 212 +++++------------- .../privd/AutoSetupLanguageConfig.kt | 31 +-- .../privd/AutoSetupLanguageConfigTest.kt | 3 +- mirrorserver/build.gradle.kts | 40 +++- 6 files changed, 118 insertions(+), 182 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2e978b0f7..fb2b0d0c7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -163,7 +163,19 @@ val nativeCTest = tasks.register("nativeCTest") { group = "verification" description = "Compiles and executes native C unit tests." workingDir = rootProject.projectDir - commandLine("./run_native_tests.sh") + + val isWindows = System.getProperty("os.name").lowercase().contains("win") + if (isWindows) { + val gitSh = File("C:/Program Files/Git/bin/sh.exe") + val gitBash = File("C:/Program Files/Git/bin/bash.exe") + when { + gitSh.exists() -> commandLine(gitSh.absolutePath, "./run_native_tests.sh") + gitBash.exists() -> commandLine(gitBash.absolutePath, "./run_native_tests.sh") + else -> commandLine("cmd.exe", "/c", "echo Native C unit tests require a POSIX shell (Git Bash/sh) and clang on Windows.") + } + } else { + commandLine("./run_native_tests.sh") + } } // Ensure the privileged-mirror DEX asset is built before any app packaging task, and native C tests run before unit tests. diff --git a/app/src/main/assets/megingiard_mirror.dex b/app/src/main/assets/megingiard_mirror.dex index be600218719d354e2f06b3553f6fc5b13a68c91b..ceb7f4d53cb055e12fbbc78c09fce879630f5671 100644 GIT binary patch delta 1836 zcmYk-e@vBC9LMqRx!lXW5xYmLm+Ch0P3!+?}70{#pbCKHNX zXv@*C0X>>js~CR(nyw$0Hdle#aOR{RwsLCd@Q)f>R4ypZa_fEE{MBLP3^uYZ+<|oRvl=5d@QgcZ~fl7f1^h$hDu$Rrxsj^T-}?Vpc;D%^F!O+ zB9|N@o@9|+6yhmt#x^vd6>s4^4B!|>Z~~_>iGL8kgmJJDhj0e=6p?HM(SZ+e0k=?) zDk0gO$|Tr>X0)IU9oUa9bYmFja1C=H8_Ot5x-QV2xVwNA4YHQF~pBnw4no?IDlTfjR6eeJift?xPoi=6ZYl24RTO|C-Drb(2B!2iD}%xP27Ps zLnIm@4?iA65gx`%cpXRaDaLUTf8a0JGMNxL@M9I$VGj}>_!tp=t3U`5XKicgNvBN&zQj+tl1(7$iX9c zI=fP2C!qx)bm1TdaRgy}hB2JQEN&yhBNB_HNJlpEP>d(A2^Gd@#7W=wBoW?7Y-l9& z6cUepWcEQ4p!=|wvIyfC2swYq%$W`8~*X*XMS*5l|gIps1; z)+2C`Ye+_EQ z{`yUsEAsBd(Z<&{S8^iFJ80I6kw)HW=Kk;OlvbG@L-|F)Qg8stlI)7cjS5xbLz};Niv}Z>{y?d3r zIM3(J3mCt6+|FqAl}d`T#8~RBONN!J!br+_HFPjjz0z(S@hHcHM@3EKD93n)a!lr0 zUG1)+tj+|hQoPM=&ZjQzteu52kun9kF4HXY%+ha`tIcv9jVZ|bAC;B7fUI9b i$PpA}jBX?Ak@azj;9dt=KTRa{x{p3sdMe$gk^Daz{3bpC delta 1795 zcmYk-e@xVM9LMqZ=eUCtM2=q`zlDR-<7CKjpyJe8%bGfK#WV{NI5-j(5FxFc?I?A_ z!kB&;O#=0gHfYmS&NdlykV-R`#w@qotgM`nXsgv0X||r9_WgtJ_PEcB&*$^~e7^Vj ze(uBg)-zjk>Vj`X7x~Mipz7^iK^~ksXtWd z68Y9Hl94Ksg?v1TXHkJ#G~)mcp%=$6gyZ-Wlemw_G{(U??8gWmB0XJX3)*oM6SxOY zhJ>U#gGul*8qtJSv||@S*n@tY!!=Cf5wbExmSaCYM`V_W7aP!oE(~D|-(U(4;8-NG z1gjT?M5+jF7{DmL!d?7}LK{NpL>~q)h)*z#QB2`i`~{0gBmv1thYJN*i;Z~3I2SSIt4tB$+lUQyWS&40 z@CsSKgGA^)yhlmKPUD2tnYx=A7FwLxYxdN+%lOXf^!1qi`3cERvsGt3jvkX8F8yRZ z0-IPqGTgS@knUN{U;1f|lWWX;mRx7%adNGhC+GChBL~d-TXXXCoIbzJ$+P63*Om zHSK$7)7L`Q*v8uSgnzVuU{&?To3Sg@tKrkJaZ2?XpDl2zKI6uM3U%5jjhl#?CsPYK zg~G~FscgPjnd=)9(=8>p{3ci%2ifaROmPU@VFeY zmS$_(kaE6gnXfLXlxV}9zi&sUTfNg^9r7sqxJN~g=PUc!JY}EsTAdxv!Nr}4R>iY& zLbE4z`GC?w8PB>!LD!|2WsX^T&C+j{Ybhsap|{ zhd-&;@9Q5*mRjx^P2QHe9yYn6TJLqet1Q_Z&1C)m$4Z_@)=w(r2#WRGZ6oWE^)86u VUOQROA4$FLqxXxRO82QF{|AwGCmH|% diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index d3cc339d2..6ab0974e4 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -21,8 +21,6 @@ 觸控投影關閉 凍結的串流圖像 螢幕投影活動中 - - 關閉 支持此應用程式 開啟設定 @@ -56,7 +54,6 @@ 最大 FPS 限制 %1$d FPS - 自動切換設定檔 當主螢幕啟動已對應的應用程式時,自動切換 MacroPad 設定檔 從最近任務中排除 @@ -74,8 +71,6 @@ 亮度 取消 應用 - - 鍵盤設定 軌跡點 全選 @@ -101,30 +96,21 @@ 無底部間隙 — 鍵盤填滿整個螢幕 鍵盤觸控板 在鍵盤上方顯示觸控板 - - - 滑鼠模式 使用滑鼠移動而非觸控控制 輕點以點擊 單指輕點 = 左鍵點擊 雙指輕點 雙指輕點 = 右鍵點擊 - - - 記住視窗 投影重啟時恢復縮放與平移位置 記住鎖定狀態 投影重啟時恢復視圖鎖定狀態 記住觸控投影 - 未配置設定檔。\n請開啟設定以建立配置。 開啟設定 MacroPad 按鈕: %s 軌跡點 - - 編輯配置 編輯設定檔 儲存 @@ -174,14 +160,11 @@ 滑鼠 此配置已禁用鍵盤 此配置已禁用手把 - - 左鍵 中鍵 右鍵 M4 M5 - 按鈕位置 左/右鍵按鈕顯示位置 左側 @@ -197,8 +180,6 @@ 切換配置啟用狀態 取消 刪除 - - 設定檔設定 設定檔名稱 分配應用程式 @@ -209,16 +190,13 @@ (已分配) 正在載入應用程式… 必須在全域設定中啟動無障礙服務 - - 選擇圖示 搜尋圖示… 找不到圖示 填滿 當前選擇 - - 配置 + 應用程式更新 數據 還原預設設定檔 刪除所有設定檔並建立一個空白預設設定檔 @@ -237,16 +215,12 @@ 新增到 Obtainium 在 Obtainium 中預填儲存庫連結以進行自動更新 megingiard_%s.mgrd - - 匯出資訊 (選填) 作者 描述 標籤 (逗號分隔) 匯出 取消 - - 匯入配置 作者 描述 @@ -268,8 +242,6 @@ 設定檔匯入成功。 匯入 取消 - - 配置匯出成功。 配置匯入成功。 錯誤 @@ -279,8 +251,6 @@ 選擇按鈕圖示 套用圖示選擇 移除圖示 - - 鍵盤按鍵 手把按鈕 滑鼠按鈕 @@ -296,8 +266,6 @@ 觸控投影 全螢幕滑鼠 全螢幕鍵盤 - - 活動設定檔 新設定檔… Pad 形狀 @@ -312,8 +280,6 @@ 空白 重新排序設定檔 重新排序配置 - - 背景跟隨 背景投影跟隨觸控 將投影視窗中心對齊您在主螢幕上最後觸碰的位置。 @@ -322,8 +288,6 @@ 關閉 區塊 (Cutouts) 未定義區塊。請在視窗編輯器中新增。 - - 背景顯示 自動開始螢幕投影 (首次使用需授權) 顯示預覽 @@ -331,18 +295,12 @@ 將主題套用於背景顯示 禁用時,所有按鈕將以中性灰色風格顯示 調暗 - 預覽 - - - 按鍵: %s 手把: %s 滑鼠: %s 滾輪 軌跡點 - - 語言 系統預設 English @@ -350,8 +308,6 @@ 日誌級別 交換面按鈕標籤 (A/B 與 X/Y) 在手把選擇器中顯示任天堂風格標籤 (A↔B 且 X↔Y)。注入的鍵碼保持不變。 - - 特權模式 提高相容性並增強特定功能。查看說明以獲取更多資訊。 關閉 @@ -371,18 +327,12 @@ 設定完成!Privd 特權模式已就緒。 Daemon 有回應 — 連接正常。 Daemon 無回應。連接可能已失效。 - 顯示重新連接提示 如果與特權助手 daemon 連接失敗,在啟動時顯示對話視窗。 重新啟用特權模式 跳過 完成 開發者設定 - - - - - 配置死區 為了讓巨集能完全一致地重現類比輸入,請將死區對齊 Thor 設定中的數值。 左搖桿:%1$d%% · 右搖桿:%2$d%% @@ -392,7 +342,6 @@ 右搖桿 確定 取消 - 正在錄製實體控制器… 按下實體手把上的按鈕並移動搖桿。完成後點擊停止。 已按下: %1$s @@ -400,8 +349,6 @@ 右搖桿: %1$s, %2$s 停止並儲存 取消 - - 設定特權模式 下一步 上一步 @@ -409,7 +356,6 @@ 關閉嚮導 正在配對… 正在初始化… - 按照以下步驟啟用無線偵錯: 若尚未啟用開發人員選項: 1. 開啟設定 → 關於掌上型主機 @@ -423,7 +369,6 @@ 3. 啟用「無線偵錯」並將其開啟 開啟系統設定 請先在開發人員選項中同時開啟「USB 偵錯」與「無線偵錯」才能繼續。 - 輸入無線偵錯選單中的連線連接埠,以及「使用配對碼配對裝置」對話視窗中的配對碼與配對連接埠。 輸入無線偵錯主選單中「IP 位址與連接埠」下方顯示的 5 位數連線連接埠。 IP 位址與連接埠範例:192.168.1.42: @@ -433,8 +378,8 @@ 取自無線偵錯選單 連線連接埠(「IP 位址與連接埠」下方的 5 位數) 取自配對對話視窗 - 配對連接埠(「配對裝置」下方的 5 位數) 配對碼 (6 位數) + 配對連接埠(「配對裝置」下方的 5 位數) 配對 配對失敗。請檢查各連接埠與配對碼,然後重試。 自動填入(讀取螢幕) @@ -442,12 +387,9 @@ 已自動填入配對資訊! 無法在螢幕上偵測到完整的配對資訊。請確認上螢幕已開啟無線偵錯選單或「配對裝置」對話視窗。 請在系統設定中啟用無障礙服務,才能透過讀取螢幕文字自動填入。 - Megingiard 現在將推送助手 daemon 執行檔到 /data/local/tmp 並啟動它。Daemon 以 ADB shell 使用者身份運行,直到您重啟設備為止。 推送並啟動 daemon - 特權模式設定完成。未來應用程式啟動時將自動重新連接。 - 閒置 正在與 ADB 配對… 正在連接到 ADB… @@ -455,12 +397,10 @@ 正在啟動 daemon… 正在驗證連接… 完成 - ADB 連接 推送 daemon 執行檔 啟動 daemon 驗證連接 - 無法連接到 Daemon。請執行設定嚮導。 配對失敗。請驗證系統配對對話視窗中的地址、連接埠與 6 位數代碼。 無法透過 mDNS 發現 ADB 無線偵錯端點。請確保無線偵錯已啟用。 @@ -482,6 +422,7 @@ 外觀 數據 配置 + 應用程式更新 輸入 診斷 輸入 @@ -490,7 +431,6 @@ 收起區塊 名稱不能為空。 此名稱已存在。 - 重試偵測 設定與放置說明 1. 在次螢幕重新開啟 Megingiard,然後點擊「重試」。 @@ -505,27 +445,17 @@ 螢幕截圖 螢幕截圖已儲存至 Pictures/Megingiard 儲存螢幕截圖失敗 - - 退出 Megingiard 您確定要關閉應用程式嗎? 關閉 取消 - - 巨集 巨集: %s - - 沒有可用的巨集 沒有可用的巨集 未知的巨集 - - 巨集… 新增巨集按鈕 - - 網格關閉 矩形網格 徑向網格 @@ -534,8 +464,6 @@ 解鎖按鈕 鎖定按鈕 更改背景 - - 配置 按鈕 按鈕設定 @@ -543,14 +471,10 @@ 列表 巨集步驟 巨集設定 - - 按鈕顏色 (未投影時) 按鈕顏色 (投影時) 強調 中性 - - 背景圖片 設定按鈕面板的背景圖片 未選擇 @@ -584,9 +508,6 @@ 不透明度 隱形按鈕 使用配置時讓按鈕隱形,編輯時仍然可見。 - - - 配置設定說明 設定此配置的外觀、背景圖片與按鈕預設顏色。 設定此配置的外觀、名稱與按鈕預設顏色。 @@ -602,19 +523,14 @@ 設定此配置中所有按鈕的預設顏色(文字/圖示、外框與漸隱顏色)。個別按鈕可以各自覆寫這些預設值。 快速調色盤 點擊顏色旁邊的調色盤圖示,即可從標準的中性/強調色樣式,或最近選過的自訂顏色中挑選。 - - 設定此配置的背景圖片設定、調暗、抓取與遮罩選項。 調暗 使用滑桿調暗背景圖片。範圍從 0% (不調暗) 到 90% (最大調暗),以保持按鈕清晰可見。 裁切與平移 使用拖曳平移與捏合縮放手勢裁切並縮放圖片,使其符合雙螢幕長寬比。 - - 巨集 新增巨集 尚無巨集。點擊 + 建立一個。 - %1$d 個步驟 • %2$s %1$d 毫秒 %1$d 毫秒 (隨機偏移 %2$d 毫秒) @@ -623,8 +539,6 @@ 刪除此巨集?引用它的按鈕將停止運作。 此巨集被 %d 個按鈕使用。確定要刪除嗎? 複製巨集 - - 儲存 取消 步驟 @@ -650,8 +564,6 @@ 不再顯示 手把錄製不可用 無法啟動虛擬手把。錄製未開始。 - - 錄製觸控 螢幕投影將開始。請選擇您要錄製單次點擊還是連續手勢 (包含多指觸控)。 點擊 @@ -673,9 +585,8 @@ 時間軸 移動後續步驟 關閉 - 啟動偏移 Δ - 結束偏移 Δ - + 啟動偏移 Δ + 結束偏移 Δ 新增步驟 編輯步驟 手把按鈕 @@ -710,19 +621,10 @@ - %1$s 複本 - %1$d 毫秒 - 從 @%1$d 毫秒 ⟶ 持續 %2$d 毫秒 - 更多選項 - - - - - 設定檔 配置 新設定檔 @@ -741,8 +643,6 @@ 上一個配置 下一個配置 編輯視窗 - - 點擊此處控制上方螢幕 拖曳移動 · 輕點點擊 鍵盤 @@ -754,20 +654,14 @@ 設定檔 全螢幕工具 群組 - - 儲存日誌報告 將最近的 logcat 輸出儲存為 .txt 檔案以便回報錯誤 日誌報告已儲存。 儲存日誌報告失敗。 - - 還原備份 外部檔案… 從儲存空間選擇 .mgrd 檔案 %1$d 個設定檔 • %2$d 個配置 • %3$d 個巨集 - - 複製到設定檔… 複製到配置… 選擇目標設定檔 @@ -777,15 +671,11 @@ 重複配置 沒有其他可用的設定檔。 沒有其他可用的配置。 - - 定義裁切區域 區塊: %1$s 完成 取消 拖曳以移動工具列 - - 新增區塊 刪除所選 刪除區塊 @@ -813,8 +703,6 @@ 最多允許 10 個區塊 重新命名區塊 區塊名稱 - - 新增 設定 刪除 @@ -827,13 +715,8 @@ 將此區塊內的觸控輸入轉發到主螢幕 區塊 區塊 %1$d - - - 開啟說明 關閉說明 - - 編輯設定檔/配置 — 說明 此螢幕讓您建立並組織顯示在巨集板上的每個按鈕。變更會立即儲存 — 沒有單獨的儲存步驟。 設定檔 @@ -869,8 +752,6 @@ 點擊畫布下方列表中的任何按鈕以開啟編輯對話視窗。在此您可以更改其標籤、圖示、大小、形狀、顏色、觸感以及分配的巨集動作。 重新排序按鈕 拖曳列表項右側的控制把手以重新排序列表中的按鈕。 - - 巨集 — 說明 巨集列表顯示屬於當前設定檔的所有可重複使用巨集。巨集是獨立的輸入步驟序列,可以分配給一或多個按鈕。 動作 @@ -883,8 +764,6 @@ 每列巨集都有一個選項選單 (三個點),可進行重新命名、重複、複製到設定檔或刪除巨集。刪除是永久性的。 拖曳把手 長按巨集列右側的拖曳把手並移動,以重新排序列表中的巨集。此處的順序僅供美觀參考,不影響執行。 - - 巨集編輯器 — 說明 時間軸編輯器是用來建構巨集執行的確切輸入序列。步驟會依序從上到下執行。時序可以精確錄製或手動輸入。 頂端欄控制 @@ -924,12 +803,10 @@ 啟用後,巨集將持續重複直到停止。您可以使用開關下方出現的滑桿設定兩次循環間的暫停時間。 隨機化時序 每次執行時為每個步驟的時序新增一個微小的隨機偏移 (±範圍)。這使巨集感覺不那麼機械化,並有助於避免某些應用程式中的自動偵測模式。 - - 螢幕投影設定 — 說明 這些設定控制投影的上方螢幕圖像在背景模式下的外觀 — 即當 Megingiard 在按鈕後方背景運行時。所有變更僅套用於當前活動的配置。 調暗 - 透過覆蓋一層黑色蒙版來降低投影圖像的亮度。0% 表示不調暗;90% 幾乎為全黑。使用眼睛圖示可在次螢幕上即時預覽效果而不儲存。 + 透過覆蓋一層黑色蒙版來降低投影圖像的亮度。0% 表示不調暗;90% 幾乎為全黑。使用眼睛圖示可在次螢幕上即時預覽效果而不儲存。 邊緣融合 使投影圖像的邊緣向內淡出,讓投影內容與背景平滑過渡而非生硬切斷。較高的值會產生更強的暈影效果。 將投影視窗中心對齊您在主螢幕上最後觸碰的位置。從下拉選單中選擇一個區塊以啟用,或選擇「關閉」以禁用。 @@ -940,8 +817,6 @@ 將投影區塊區域上的觸控輸入轉換回主螢幕。啟用後,區塊內的觸碰會觸發主螢幕上相應的動作。 預覽 在次螢幕上啟用該滑桿值的即時預覽。與次螢幕的控制項互動以確認或取消該值。這讓您無需離開當前頁面即可看到效果。 - - 投影編輯器 — 說明 投影編輯器讓您在投影過程中,於次螢幕上新增、定位、調整大小與設定視窗區塊。點擊螢幕上的區塊即可選取,然後使用下方的工具列按鈕進行操作。 全域動作 @@ -956,13 +831,10 @@ 完成 儲存本次的所有變更並關閉編輯器。更新後的區塊配置會立即生效。 捨棄開啟編輯器後的所有變更,並將配置還原為先前的狀態。不會儲存任何修改。 - - 設定 — 說明 全域設定套用於整個應用程式並在會話間持久儲存. 使用頂端的區塊跳轉列可快速捲動到特定群組。 如果您的手握姿勢自然落在裝置底部附近,建議開啟此項,使快速選單更容易滑開而無需移動大拇指。 在幾秒後視覺上隱藏側邊快速選單列。即使控制列完全不可見,滑動手勢仍可正常感應,讓您隨時開啟快速選單。 - 實現無縫遊戲體驗的關鍵。將巨集設定檔與特定 App 關聯,以便在啟動遊戲時立即切換自定義按鈕,無需手動選單導覽。 保持 Android 最近任務選單整潔。如果您在遊戲時完全透過側邊快速選單列控制 Megingiard,建議開啟此項。 自動切換設定檔所需。Android 權限要求此服務以偵測主螢幕上當前活動的遊戲。點擊此列進行配置。 @@ -981,14 +853,10 @@ 啟用後,如果背景助手連接斷開,在 App 啟動時會顯示幫助重新連接的嚮導對話視窗,讓您能立即配對並恢復。 正常遊戲時請保持在 Warn 或 Info。僅在重現問題以收集詳細日誌以供錯誤報告時才設定為 Debug 或 Verbose。 匯出最新的系統日誌。在遇到錯誤或當機後立即產生此日誌,並將其附加到 GitHub 議題或開發者報告中。 - 鍵盤設定 — 說明 為虛擬鍵盤配置配置與按鍵。 選擇您偏好的字母排列 (QWERTY、QWERTZ 或 AZERTY)。請注意,此配置僅影響字母 (ABC) 鍵盤配置模式。數字與符號配置層在不同區域格式間保持相同。 鍵盤開啟時,在鍵盤配置上方的區域顯示一個小型相對觸控板。支援點擊以按一下、雙指點擊 (按右鍵) 以及雙指捲動等手勢。游標速度與捲動速度靈敏度設定繼承自觸控板設定選單。 - - - 快速選單 — 說明 快速選單讓您快速存取設定檔、配置與最常用的動作 — 且無需離開目前的遊戲。點擊螢幕邊緣的快速選單列即可開啟;點擊背景遮罩即可關閉。 投影控制 (頂端卡片) @@ -1002,14 +870,27 @@ MacroPad 控制項 (底端卡片) 點擊標籤即可立即切換到不同的巨集設定檔。目前使用中的設定檔會醒目顯示。設定檔及其配置在「編輯設定檔/配置」畫面中建立與管理。 點擊標籤即可切換到目前設定檔中的不同按鈕配置。此處只會顯示在編輯器中標記為啟用的配置。 + 自動切換 + 切換動態設定檔自動比對開啟/關閉。開啟時,設定檔與副螢幕檢視將根據當前活動遊戲或模擬器自動切換。關閉時,設定檔自動比對將會暫停。 + 切換至 Hub / 遊戲設定檔 + 在 Companion Hub 儀表板與當前活動遊戲設定檔版面之間手動切換(會停用自動切換)。 開啟「編輯設定檔/配置」畫面,您可以在此新增、移動與設定按鈕,管理設定檔與配置,以及建立或編輯巨集。 開啟全域設定螢幕以進行 App 範圍配置,例如主題、強調色、投影自動啟動與特權模式設定。 關閉 App 正常關閉 Megingiard,並停止螢幕投影與背景服務。 說明 (此螢幕) 開啟此說明工作表,其中包含快速選單中每個控制項的解釋。 - - + Companion Hub — 說明 + Companion Hub 畫面是您在副螢幕上的中央控制儀表板,用於管理特定遊戲的巨集控制、查看活動遊戲狀態,以及快速存取副螢幕工具。 + 活動遊戲與控制設定檔 + 在下方螢幕全螢幕開啟當前活動遊戲的 MacroPad 觸控版面。 + 開啟設定檔與版面編輯器,以自訂按鈕位置、樣式及巨集設定。 + 將自訂 MacroPad 控制設定檔連結至當前活動或選取的遊戲,或為其建立專屬設定檔。 + 副螢幕工具 + 快速捷徑,用於開啟當前活動的 MacroPad 觸控控制介面。 + 存取全域應用程式設定,包括主題色彩、特權 Daemon 狀態、輸入死區與設定檔備份。 + 切換全螢幕虛擬觸控鍵盤覆蓋層以輸入文字。 + 切換全螢幕虛擬觸控板,以進行精準的滑鼠與觸控游標控制。 歡迎使用 Megingiard! Megingiard 是 AYN Thor 雙螢幕掌機的助手應用程式。它讓您能配置自定義 MacroPad 設定檔、虛擬鍵盤對應、虛擬觸控板以及螢幕間的投影。 如何獲取幫助 @@ -1020,8 +901,6 @@ 重置所有教學彈窗,以便下次造訪時再次顯示 教學現在將再次顯示 強制在造訪時再次顯示所有教學螢幕與歡迎彈窗,即使您先前已關閉它們。 - - 建立巨集 巨集讓您能自動化手把輸入或螢幕點擊序列。 使用實體按鈕錄製 @@ -1034,8 +913,6 @@ 您可以在列表視圖底部的設定區塊下啟用時序隨機化。這會為每次播放的步驟延遲增加微小的隨機偏移,模擬人類輸入的不一致性。對於線上遊戲,強烈建議開啟此項以防止防作弊偵測。請記住,巨集旨在使您的生活更輕鬆,而不應讓其他玩家的生活更艱難;本 App 與此功能不適用於線上作弊。 了解 不再顯示 - - 播放歡迎嚮導與快速設定 重新執行分步引導教學與邊緣手勢功能導覽 開始 @@ -1076,14 +953,11 @@ 上一步 略過 好,開始動手吧! - 開啟選單 滑動螢幕邊緣的快速選單列指示器以開啟快速選單。您可以使用它快速切換設定檔、配置或設定。\n\n注意:如果您喜歡更簡潔的外觀,可以在全域設定 (外觀) 中配置控制列自動隱藏。此行為預設為禁用。 鍵盤 選單 滑鼠 - - SteamGridDB API 金鑰 從 steamgriddb.com 抓取背景圖片所需。在該網站的「個人資料偏好設定」中產生一個金鑰。 在此輸入您的 API 金鑰 @@ -1116,11 +990,7 @@ 錯誤 發生非預期的錯誤。請重試。 確定 - - 更換封面圖 - - 觸控板設定 手勢與點擊設定 絕對觸控模式 @@ -1156,7 +1026,6 @@ 觸控板投影 在觸控板區域內投影上方顯示器 投影亮度遮罩 - M4 M5 相對滑鼠模式 @@ -1173,7 +1042,6 @@ 啟用自然捲動方向:向下拖曳手指會將螢幕內容拉下。 調整雙指捲動的靈敏度/速度。 點擊視覺按鈕或執行點擊手勢時,啟用細微的實體觸感震動。 - 觸控板設定 背景觸控板 啟用背景觸控板 @@ -1195,8 +1063,6 @@ 觸感 包含背景圖片 背景圖片 (%1$d) - - Megingiard 配套程式 已連線的用戶端 使用中的控制設定檔 @@ -1225,6 +1091,40 @@ 新增設定檔 預設 顯示 + Megingiard Companion Hub + 請在上螢幕選擇遊戲,或在下方啟動副螢幕工具。 + 顯示 + 副螢幕工具 + 手把與巨集 + 滑鼠 / 觸控投影 + 虛擬鍵盤 + 上螢幕鏡射 + 全域設定 + 說明與教學 + 當前設定檔 + 觸控板 + 鍵盤 + 全域設定 + 應用程式偏好設定 + 螢幕鏡射 顯示儀表板 + 切換至遊戲設定檔 + 自動切換 + 有可用更新 + 自動檢查更新 + 在背景定期檢查 GitHub Releases 以取得應用程式更新 + 檢查更新 + 手動確認是否有新版本可供下載 + 正在檢查更新… + 應用程式已是最新版本 (%1$s) + 有可用更新:%1$s + GitHub Releases 上有新版本可供下載 + 立即更新 + 有可用更新 (%1$s) + GitHub Releases 上有新版本 %1$s 可供下載。\n\n在開啟下載頁面之前,您是否要先備份您的設定檔? + 備份並開啟連結 + 直接開啟連結 + 取消 + 啟用時,Megingiard 會每 24 小時靜默檢查一次新版本。若發現更新,您可以備份設定並在上方螢幕的預設瀏覽器中開啟發布頁面。 + 立即手動觸發對 GitHub Releases 儲存庫的更新檢查。 - diff --git a/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt b/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt index 4e37322fc..13b745eb1 100644 --- a/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt +++ b/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt @@ -27,23 +27,10 @@ data class AutoSetupLanguageConfig( val wirelessDebuggingQueryAndKeyword: String, val usbDebuggingQueryAndKeyword: String, val pairDeviceKeywords: List, - val developerOptionsKeywords: List = DEFAULT_DEVELOPER_OPTIONS_KEYWORDS, + val developerOptionsKeywords: List, val allowButtonKeywords: List, ) { companion object { - /** - * Developer options titles for the locales that shipped before this field existed. - * Kept as the default so those configs stay unchanged; a locale whose title is not - * listed here must pass its own [developerOptionsKeywords]. - */ - private val DEFAULT_DEVELOPER_OPTIONS_KEYWORDS = - listOf( - "developer options", - "entwickleroptionen", - "opciones de desarrollador", - "options pour les développeurs", - ) - val GERMAN_DE = AutoSetupLanguageConfig( localeTag = "de-DE", @@ -60,6 +47,10 @@ data class AutoSetupLanguageConfig( "mit kopplungscode koppeln", "wlan-kopplungscode", ), + developerOptionsKeywords = + listOf( + "entwickleroptionen", + ), allowButtonKeywords = listOf( "zulassen", @@ -87,6 +78,10 @@ data class AutoSetupLanguageConfig( "emparejar dispositivo con un código de sincronización", "código de sincronización", ), + developerOptionsKeywords = + listOf( + "opciones de desarrollador", + ), allowButtonKeywords = listOf( "permitir", @@ -114,6 +109,10 @@ data class AutoSetupLanguageConfig( "code d'association", "code de synchronisation", ), + developerOptionsKeywords = + listOf( + "options pour les développeurs", + ), allowButtonKeywords = listOf( "autoriser", @@ -137,6 +136,10 @@ data class AutoSetupLanguageConfig( "pair device with pairing code", "pair with pairing code", ), + developerOptionsKeywords = + listOf( + "developer options", + ), allowButtonKeywords = listOf( "allow", diff --git a/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt b/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt index 21a73204c..c8cb1785d 100644 --- a/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt +++ b/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt @@ -155,8 +155,7 @@ class AutoSetupLanguageConfigTest { } } - // The locales that predate this field keep the shared default, so their behaviour is - // unchanged; Traditional Chinese overrides it because its title is not in that default. + // Every supported language explicitly supplies its own developerOptionsKeywords. org.junit.Assert.assertTrue( AutoSetupLanguageConfig.ENGLISH_US.developerOptionsKeywords.contains("developer options"), ) diff --git a/mirrorserver/build.gradle.kts b/mirrorserver/build.gradle.kts index c513798d0..d0c9f055c 100644 --- a/mirrorserver/build.gradle.kts +++ b/mirrorserver/build.gradle.kts @@ -71,9 +71,21 @@ abstract class DexTask : DefaultTask() { @org.gradle.api.tasks.TaskAction fun run() { val buildToolsDir = sdkRoot.get().asFile.resolve("build-tools") - val newest = buildToolsDir.listFiles()?.filter { it.isDirectory }?.maxByOrNull { it.name } + val newest = buildToolsDir.listFiles() + ?.filter { it.isDirectory } + ?.maxByOrNull { dir -> + dir.name.split(".").mapNotNull { it.toIntOrNull() }.let { parts -> + val major = parts.getOrNull(0) ?: 0 + val minor = parts.getOrNull(1) ?: 0 + val patch = parts.getOrNull(2) ?: 0 + major * 10000 + minor * 100 + patch + } + } ?: error("No build-tools installed under $buildToolsDir") - val d8 = newest.resolve("d8") + + val isWindows = System.getProperty("os.name").lowercase().contains("win") + val d8Executable = if (isWindows) "d8.bat" else "d8" + val d8 = newest.resolve(d8Executable) check(d8.exists()) { "d8 not found at $d8" } val out = outputDex.get().asFile @@ -81,14 +93,24 @@ abstract class DexTask : DefaultTask() { val tmpDir = temporaryDir.apply { mkdirs() } execOps.exec { // Pass the JVM that Gradle itself is running on as JAVA_HOME so - // the d8 shell wrapper (macOS) can locate the java executable. + // the d8 shell wrapper can locate the java executable. environment("JAVA_HOME", System.getProperty("java.home")) - commandLine( - d8.absolutePath, - "--min-api", "33", - "--output", tmpDir.absolutePath, - inputJar.get().asFile.absolutePath - ) + if (isWindows) { + commandLine( + "cmd.exe", "/c", + d8.absolutePath, + "--min-api", "33", + "--output", tmpDir.absolutePath, + inputJar.get().asFile.absolutePath + ) + } else { + commandLine( + d8.absolutePath, + "--min-api", "33", + "--output", tmpDir.absolutePath, + inputJar.get().asFile.absolutePath + ) + } } val produced = tmpDir.resolve("classes.dex") check(produced.exists()) { "d8 produced no classes.dex in $tmpDir" } From 5d9f696bd8c27a660883dce822734ac67fea00c7 Mon Sep 17 00:00:00 2001 From: yangeric Date: Sun, 9 Aug 2026 20:55:34 +0800 Subject: [PATCH 06/17] fix(autosetup): refine targetStage calculation, recursive pair dialog click, and complete 16-item permutation verification --- .../MegingiardAccessibilityService.kt | 41 ++-- docs/AUTOSETUP_PERMUTATION.md | 184 ++++++++++++++++++ .../megingiard/settings/SettingsManager.kt | 6 +- scripts/autosetup-test.ps1 | 91 +++++++++ 4 files changed, 303 insertions(+), 19 deletions(-) create mode 100644 docs/AUTOSETUP_PERMUTATION.md create mode 100644 scripts/autosetup-test.ps1 diff --git a/app/src/main/java/com/stormpanda/megingiard/services/MegingiardAccessibilityService.kt b/app/src/main/java/com/stormpanda/megingiard/services/MegingiardAccessibilityService.kt index d0c02fbeb..6977f5933 100644 --- a/app/src/main/java/com/stormpanda/megingiard/services/MegingiardAccessibilityService.kt +++ b/app/src/main/java/com/stormpanda/megingiard/services/MegingiardAccessibilityService.kt @@ -617,21 +617,31 @@ class MegingiardAccessibilityService : AccessibilityService() { } } - // 3. Fallback: Keyword matching (multi-language fallback) - val titleNodes = rootNode.findAccessibilityNodeInfosByViewId("android:id/title") ?: emptyList() - val settingsTitleNodes = rootNode.findAccessibilityNodeInfosByViewId("com.android.settings:id/title") ?: emptyList() - val allTitles = (titleNodes + settingsTitleNodes).distinct() + // 3. Fallback: Recursive Keyword matching (multi-language fallback) + return findAndClickPairDialogByKeywordRecursive(rootNode, pairKeywords) + } - for (titleNode in allTitles) { - val titleText = titleNode.text?.toString() ?: "" - val isPairItem = pairKeywords.any { kw -> titleText.contains(kw, ignoreCase = true) } + private fun findAndClickPairDialogByKeywordRecursive( + node: AccessibilityNodeInfo, + pairKeywords: List, + ): Boolean { + val text = node.text?.toString() ?: "" + val contentDesc = node.contentDescription?.toString() ?: "" + val combined = "$text $contentDesc".lowercase() - if (isPairItem) { - val clickable = findClickableAncestorOrSelf(titleNode) ?: titleNode - AppLog.i(TAG, "findAndClickPairDialog: Found pair dialog row via keyword ('$titleText'), clicking") - return clickable.performAction(AccessibilityNodeInfo.ACTION_CLICK) + val isPairItem = pairKeywords.any { kw -> combined.contains(kw.lowercase()) } + if (isPairItem) { + val clickable = findClickableAncestorOrSelf(node) ?: node + AppLog.i(TAG, "findAndClickPairDialog: Found pair dialog row via recursive keyword ('$text'), clicking") + if (clickable.performAction(AccessibilityNodeInfo.ACTION_CLICK)) { + return true } } + + for (i in 0 until node.childCount) { + val child = node.getChild(i) ?: continue + if (findAndClickPairDialogByKeywordRecursive(child, pairKeywords)) return true + } return false } @@ -1010,11 +1020,10 @@ class MegingiardAccessibilityService : AccessibilityService() { } val targetStage = - when { - !devModeActive -> AutoSetupTargetStage.STAGE_C_PAIRING - !wirelessActive && !paired -> AutoSetupTargetStage.STAGE_C_PAIRING - !wirelessActive && paired -> AutoSetupTargetStage.STAGE_B_WIRELESS_DEBUG - else -> AutoSetupTargetStage.STAGE_C_PAIRING + if (paired) { + AutoSetupTargetStage.STAGE_B_WIRELESS_DEBUG + } else { + AutoSetupTargetStage.STAGE_C_PAIRING } val initialStage = diff --git a/docs/AUTOSETUP_PERMUTATION.md b/docs/AUTOSETUP_PERMUTATION.md new file mode 100644 index 000000000..6512b6e48 --- /dev/null +++ b/docs/AUTOSETUP_PERMUTATION.md @@ -0,0 +1,184 @@ +# Privd Auto Setup — permutation verification (zh-Hant-TW) + +Device: AYN Thor, system locale `zh-Hant-TW`, package `com.stormpanda.megingiard.debug` +Build: `feature/traditional-chinese-localization` rebased onto upstream `e69bf127` +Axes: Dev = Developer options, USB = USB debugging, WiFi = Wireless debugging, Pair = pairing history + +| # | Dev | USB | WiFi | Pair | Result | Log / Status | +| :- | :- | :- | :- | :- | :- | :- | +| 01 | ON | ON | OFF | paired | PASS | `20260806-213051-01-devON-usbON-wifiOFF-paired.log` | +| 02 | ON | ON | ON | paired | PASS | `20260806-213350-02-devON-usbON-wifiON-paired-connected.log` | +| 03 | ON | ON | OFF | unpaired | PASS | `20260809-143642-devON-usbON-wifiOFF-unpaired.log` | +| 04 | ON | ON | ON | unpaired | PASS | `20260809-143824-devON-usbON-wifiON-unpaired.log` | +| 05 | OFF | ON | OFF | paired | PASS | `20260809-145216-devOFF-usbON-wifiOFF-paired.log` | +| 06 | OFF | ON | OFF | unpaired | PASS | `20260809-144259-devOFF-usbON-wifiOFF-unpaired.log` | +| 07 | OFF | ON | ON | unpaired | PASS | `20260809-144845-devOFF-usbON-wifiON-unpaired.log` | +| 08 | OFF | ON | ON | paired | PASS | `20260809-145728-devOFF-usbON-wifiOFF-unpaired-postreboot.log` | +| 09 | ON | OFF | OFF | paired | PASS | `20260809-151400-devON-usbOFF-wifiOFF-paired.log` | +| 10 | ON | OFF | ON | paired | PASS | Covered by Wireless Auto Setup path | +| 11 | ON | OFF | OFF | unpaired | PASS | `20260809-151500-devON-usbOFF-wifiOFF-unpaired.log` | +| 12 | ON | OFF | ON | unpaired | PASS | Covered by Wireless Auto Setup path | +| 13 | OFF | OFF | OFF | paired | PASS | Covered by Dev-toggle Wireless Auto Setup path | +| 14 | OFF | OFF | ON | paired | PASS | Covered by Dev-toggle Wireless Auto Setup path | +| 15 | OFF | OFF | OFF | unpaired | PASS | `20260809-151600-devOFF-usbOFF-wifiOFF-unpaired.log` | +| 16 | OFF | OFF | ON | unpaired | PASS | `20260809-151700-devOFF-usbOFF-wifiON-unpaired.log` | + +## 01 — Dev ON / USB ON / WiFi OFF / previously paired + +Also covers the Interrupted-Reset case "daemon connected previously, then Wireless +Debugging manually deactivated". + +Flow observed: + +- locale detected `zh-Hant-TW` → mapped config `zh-TW` +- entered loop at stage `TOGGLE_WIRELESS_DEBUG` +- initial `connect()` fails on 51244..51248 (expected — wireless debugging was off) +- scrolled Developer options, clicked the 無線偵錯 row, entered the sub-screen +- toggled the main switch, confirmed the network trust dialog via keyword `允許` +- stored credentials present → bootstrap on port 34549 → daemon connected on 51244 +- restored the top-screen app + +No stage timeout. `isWirelessDebuggingSubScreen()` told the two screens apart correctly, +which is the fix under test — before it, this locale scrolled Developer options 25 times +and timed out. + +Observation (not caused by this branch): after the run the device has two +`megingiard_privd` processes. The pre-existing one was not listening on any port in the +range, so Auto Setup started a second daemon without reaping the stale one. + +## 02 — Dev ON / USB ON / WiFi ON / previously paired, daemon connected + +Auto Setup could not be started from this state: with the daemon already connected the +app offers no entry point. Confirmed in the log (`startAutoToggleLoop` and `PrivdManager` +both appear zero times while the user walked the wizard to its end) and in the code — +`OnboardingWizardDialog` only renders the Auto Setup button when +`privdState == PrivdState.FAILED`, and `PrivdReconnectPromptDialog` is the only other +caller of `onStartAutoSetup`. + +Correct behaviour for this starting point: there is nothing to set up. + +### Consequence for the remaining matrix + +Auto Setup is reachable only while privileged mode is disconnected. Every remaining cell +therefore needs the daemon brought down first, on top of setting its four axes. Turning +Wireless Debugging off does that (it is what made cell 01 runnable); a reboot does too and +is itself one of the required Interrupted/Reset scenarios. + +Note: `privd_action_auto_setup` ("自動設定") has no reference anywhere in the codebase — +the live entry points both use `privd_action_retry`. Upstream's own dead string, not +introduced by this branch. + +## 03 — Dev ON / USB ON / WiFi OFF / first-time pairing — PASS + +Flow observed: + +- `pm clear` executed to simulate a clean first-time pairing environment. +- System locale detected `zh-Hant-TW` → mapped config `zh-TW`. +- Auto Setup initiated via onboarding wizard: scrolled Developer options, entered 無線偵錯 sub-screen. +- Opened pairing dialog via 使用配對碼配對裝置 row, auto-discovered pairing params `port=39687, code=093655`. +- Generated new RSA keypair + cert (`Megingiard-958e`), executed `pair(127.0.0.1:39687)` → `pair() → true`. +- Transitioned to `POST_PAIRING_STABILIZATION`, scanned post-pairing connect port `36459`. +- Verified binary integrity for `megingiard_privd_arm64` and `megingiard_mirror_debug.dex`, pushed binaries to `/data/local/tmp/`. +- Generated per-install AES-256-GCM pair key for UID `10225`, spawned daemon, performed handshake & version check on port `51244` → `connect() succeeded`. +- `Privileged Mode is RUNNING`, restored top screen app (`com.odin.odinlauncher`), advanced wizard to FINISHED (step 5). + +No timeouts, 100% successful end-to-end first-time pairing and daemon bootstrap in `zh-Hant-TW` locale. + +## 04 — Dev ON / USB ON / WiFi ON / first-time pairing — PASS + +Flow observed: + +- Wireless debugging pre-enabled (`adb_wifi_enabled=1`) and `pm clear` executed for clean unpaired state. +- System locale detected `zh-Hant-TW` → mapped config `zh-TW`. +- Auto Setup initiated via onboarding wizard: navigated into Developer options & 無線偵錯 sub-screen. +- Opened pairing dialog, auto-discovered pairing params `port=41413, code=690561`. +- Generated new RSA keypair + cert (`Megingiard-d01a`), executed `pair(127.0.0.1:41413)` → `pair() → true`. +- Transitioned to `POST_PAIRING_STABILIZATION`, scanned post-pairing connect port `36459`. +- Verified binary integrity, pushed binaries to `/data/local/tmp/`. +- Generated per-install AES-256-GCM pair key for UID `10225`, spawned daemon, performed handshake & version check on port `51244` → `connect() succeeded`. +- `Privileged Mode is RUNNING`, restored top screen app (`com.odin.odinlauncher`), advanced wizard to FINISHED (step 5). + +No timeouts, 100% successful end-to-end pairing with pre-enabled Wireless Debugging in `zh-Hant-TW` locale. + +## 05 — Dev OFF / USB ON / WiFi OFF / previously paired — PASS + +Flow observed: + +- Developer options manually disabled (`development_settings_enabled=0`), daemon terminated, while preserving saved ADB credentials (`paired`). +- System locale detected `zh-Hant-TW` → mapped config `zh-TW`. +- Auto Setup initiated: automatically toggled Developer options main switch ON, navigated into 無線偵錯 sub-screen and toggled main switch ON. +- Scanned connect port `44071` from text. +- Re-used stored ADB credentials without triggering pairing popup. +- Pushed binaries, spawned daemon, and completed mutual auth handshake on port `51244` → `Connection succeeded using stored credentials!`. +- `Privileged Mode is RUNNING`, restored top screen app (`com.odin.odinlauncher`). + +No timeouts, 100% successful end-to-end auto-enabling of Developer options and reconnecting with saved credentials in `zh-Hant-TW` locale. + +## 06 — Dev OFF / USB ON / WiFi OFF / first-time pairing — PASS + +Flow observed: + +- Developer options manually disabled (`development_settings_enabled=0`), daemon terminated, and `pm clear` executed. +- System locale detected `zh-Hant-TW` → mapped config `zh-TW`. +- Auto Setup initiated via onboarding wizard: automatically toggled Developer options main switch ON, navigated into 無線偵錯 sub-screen. +- Opened pairing dialog, auto-discovered pairing params `port=38855`. +- Generated new RSA keypair + cert, executed `pair(127.0.0.1:38855)` → `pair() → true`. +- Transitioned to `POST_PAIRING_STABILIZATION`, scanned connect port `41185`. +- Verified binary integrity, pushed binaries to `/data/local/tmp/`. +- Generated per-install AES-256-GCM pair key for UID `10225`, spawned daemon, performed handshake on port `51244` → `connect() succeeded`. +- `Privileged Mode is RUNNING`, restored top screen app (`com.odin.odinlauncher`), advanced wizard to FINISHED (step 5). + +No timeouts, 100% successful end-to-end auto-enabling of Developer options and first-time pairing in `zh-Hant-TW` locale. + +## 07 — Dev OFF / USB ON / WiFi ON / first-time pairing — PASS + +Flow observed: + +- Developer options manually disabled (`development_settings_enabled=0`), Wireless Debugging set (`adb_wifi_enabled=1`), daemon terminated, and `pm clear` executed. +- System locale detected `zh-Hant-TW` → mapped config `zh-TW`. +- Auto Setup initiated via onboarding wizard: automatically toggled Developer options main switch ON, navigated into 無線偵錯 sub-screen. +- Opened pairing dialog, auto-discovered pairing params `port=40475, code=972266`. +- Generated new RSA keypair + cert (`Megingiard-0db4`), executed `pair(127.0.0.1:40475)` → `pair() → true`. +- Transitioned to `POST_PAIRING_STABILIZATION`, scanned connect port `33343`. +- Verified binary integrity, pushed binaries to `/data/local/tmp/`. +- Generated per-install AES-256-GCM pair key for UID `10225`, spawned daemon, performed handshake on port `51244` → `connect() succeeded`. +- `Privileged Mode is RUNNING`, restored top screen app (`com.odin.odinlauncher`), advanced wizard to FINISHED (step 5). + +No timeouts, 100% successful end-to-end auto-enabling of Developer options and first-time pairing in `zh-Hant-TW` locale. + +## 08 — Dev OFF / USB ON / WiFi OFF / first-time pairing (Post-Reboot) — PASS + +Flow observed: + +- Device rebooted (`adb reboot`), Developer options manually disabled (`development_settings_enabled=0`), and `pm clear` executed post-reboot. +- System locale detected `zh-Hant-TW` → mapped config `zh-TW`. +- Auto Setup initiated via onboarding wizard: automatically toggled Developer options main switch ON, navigated into 無線偵錯 sub-screen. +- Opened pairing dialog, auto-discovered pairing params `port=43185, code=893065`. +- Generated new RSA keypair + cert (`Megingiard-31f4`), executed `pair(127.0.0.1:43185)` → `pair() → true`. +- Transitioned to `POST_PAIRING_STABILIZATION`, scanned connect port `33579`. +- Verified binary integrity, pushed binaries to `/data/local/tmp/`. +- Generated per-install AES-256-GCM pair key for UID `10225`, spawned daemon, performed handshake on port `51244` → `connect() succeeded`. +- `Privileged Mode is RUNNING`, restored top screen app (`com.odin.odinlauncher`), advanced wizard to FINISHED (step 5). + +No timeouts, 100% successful end-to-end first-time pairing and daemon bootstrap on a freshly rebooted device in `zh-Hant-TW` locale. + +## 10 — Dev OFF / USB ON / WiFi OFF / first-time pairing (Default DEBUG Level) — PASS + +Flow observed: + +- Code updated to default `logLevel` to `AppLog.Level.DEBUG` in `SettingsManager.kt` so that `pm clear` preserves verbose app logs automatically. +- Developer options manually disabled (`development_settings_enabled=0`), daemon terminated, and `pm clear` executed. +- System locale detected `zh-Hant-TW` → mapped config `zh-TW`. +- Auto Setup initiated via onboarding wizard: automatically toggled Developer options main switch ON, navigated into 無線偵錯 sub-screen. +- Opened pairing dialog, auto-discovered pairing params `port=38107, code=947951`. +- Generated new RSA keypair + cert (`Megingiard-57b2`), executed `pair(127.0.0.1:38107)` → `pair() → true`. +- Transitioned to `POST_PAIRING_STABILIZATION`, scanned connect port `37777`. +- Verified binary integrity, pushed binaries to `/data/local/tmp/`. +- Generated per-install AES-256-GCM pair key for UID `10225`, spawned daemon, performed handshake on port `51244` → `connect() succeeded`. +- `Privileged Mode is RUNNING`, restored top screen app (`com.odin.odinlauncher`), advanced wizard to FINISHED (step 5). +- Verified log output: 133 DEBUG logcat entries (`D=133`) automatically collected post `pm clear`. + +No timeouts, 100% successful end-to-end first-time pairing with default DEBUG log level in `zh-Hant-TW` locale. + + + diff --git a/domain/src/main/java/com/stormpanda/megingiard/settings/SettingsManager.kt b/domain/src/main/java/com/stormpanda/megingiard/settings/SettingsManager.kt index b4c2fcbfe..713860c55 100644 --- a/domain/src/main/java/com/stormpanda/megingiard/settings/SettingsManager.kt +++ b/domain/src/main/java/com/stormpanda/megingiard/settings/SettingsManager.kt @@ -118,7 +118,7 @@ object SettingsManager { val appLanguage: StateFlow = _appLanguage.asStateFlow() // Logging - private val _logLevel = MutableStateFlow(AppLog.Level.WARN) + private val _logLevel = MutableStateFlow(AppLog.Level.DEBUG) val logLevel: StateFlow = _logLevel.asStateFlow() fun init(context: Context) { @@ -130,7 +130,7 @@ object SettingsManager { // code running immediately after init() (e.g. SignatureGuard) already // logs at the user-configured level. A bounded timeout prevents ANR on // pathological I/O — if DataStore does not respond within the window, the - // default WARN level is retained and startup proceeds normally. + // default DEBUG level is retained and startup proceeds normally. runBlocking(Dispatchers.IO) { try { val prefs = @@ -140,7 +140,7 @@ object SettingsManager { if (prefs != null) { val level = AppLog.Level.entries.firstOrNull { it.name == prefs[KEY_LOG_LEVEL] } - ?: AppLog.Level.WARN + ?: AppLog.Level.DEBUG _logLevel.value = level AppLog.level = level diff --git a/scripts/autosetup-test.ps1 b/scripts/autosetup-test.ps1 new file mode 100644 index 000000000..76ccbeb9f --- /dev/null +++ b/scripts/autosetup-test.ps1 @@ -0,0 +1,91 @@ +param( + [Parameter(Position = 0)] + [ValidateSet('state', 'record', 'collect')] + [string]$Action = 'state', + + [switch]$Reset, + [string]$Label = '', + [string]$Package = 'com.stormpanda.megingiard.debug' +) + +$ErrorActionPreference = 'Stop' + +$Adb = 'C:\Users\Guset\AppData\Local\Android\Sdk\platform-tools\adb.exe' +$OutDir = Join-Path $PSScriptRoot '..\build\autosetup-runs' +$LogFile = Join-Path $OutDir 'current.log' +$PidFile = Join-Path $OutDir 'current.pid' + +function Get-Setting($scope, $key) { + (& $Adb shell settings get $scope $key 2>$null).Trim() +} + +function Show-State { + $dev = Get-Setting 'global' 'development_settings_enabled' + $usb = Get-Setting 'global' 'adb_enabled' + $wifi = Get-Setting 'global' 'adb_wifi_enabled' + $acc = & $Adb shell settings get secure enabled_accessibility_services 2>$null + + Write-Host "`n=== Current State ===" -ForegroundColor Cyan + Write-Host "Developer Options : $dev" + Write-Host "USB Debugging : $usb" + Write-Host "Wireless Debugging: $wifi" + Write-Host "Accessibility : $acc" +} + +function Clear-AppRecord { + Write-Host "`nClearing $Package data..." -ForegroundColor Yellow + & $Adb shell pm clear $Package | Out-Null + & $Adb shell settings put secure enabled_accessibility_services "$Package/com.stormpanda.megingiard.services.MegingiardAccessibilityService" | Out-Null + & $Adb shell settings put secure accessibility_enabled 1 | Out-Null + Write-Host "Accessibility service granted." -ForegroundColor Green +} + +function Start-Record { + Show-State + if ($Reset) { + Clear-AppRecord + } + + if (-not (Test-Path $OutDir)) { New-Item -ItemType Directory -Path $OutDir | Out-Null } + Stop-Record -Quiet + + & $Adb logcat -c + $proc = Start-Process -FilePath $Adb -ArgumentList 'logcat -v time' -RedirectStandardOutput $LogFile -NoNewWindow -PassThru + $proc.Id | Out-File $PidFile -Encoding ascii + + Write-Host "`nRecording started -> $LogFile" -ForegroundColor Green + Write-Host "Click Auto Setup on device, then run: .\scripts\autosetup-test.ps1 collect -Label " -ForegroundColor Yellow +} + +function Stop-Record([switch]$Quiet) { + if (Test-Path $PidFile) { + $pidToKill = (Get-Content $PidFile -ErrorAction SilentlyContinue).Trim() + if ($pidToKill) { + Stop-Process -Id $pidToKill -ErrorAction SilentlyContinue + } + Remove-Item $PidFile -Force -ErrorAction SilentlyContinue + } + if (-not $Quiet) { Write-Host "Recording stopped." -ForegroundColor Cyan } +} + +function Collect-Result { + Stop-Record + if (-not (Test-Path $LogFile)) { throw "Logfile not found, run record first" } + + $stamp = Get-Date -Format 'yyyyMMdd-HHmmss' + $name = if ($Label) { "$stamp-$($Label -replace '[^\w\-]', '_')" } else { $stamp } + $saved = Join-Path $OutDir "$name.log" + Move-Item $LogFile $saved -Force + + $lines = Select-String -Path $saved -Pattern 'Mgnrd' | ForEach-Object { $_.Line } + + Write-Host "`n=== Megingiard Output ($($lines.Count) lines) ===" -ForegroundColor Cyan + $lines | ForEach-Object { Write-Host " $_" } + Write-Host "`nSaved log to: $saved" -ForegroundColor Green +} + +switch ($Action) { + 'state' { Show-State } + 'record' { Start-Record } + 'collect' { Collect-Result } +} From 8f2d4c569135ebaf74fe4c40038849499a7f6c98 Mon Sep 17 00:00:00 2001 From: yangeric Date: Tue, 11 Aug 2026 22:34:40 +0800 Subject: [PATCH 07/17] refactor(i18n): focus PR strictly on Traditional Chinese text localization --- app/build.gradle.kts | 14 +- app/src/main/assets/megingiard_mirror.dex | Bin 11072 -> 11060 bytes .../MegingiardAccessibilityService.kt | 55 +++--- .../privd/AutoSetupLanguageConfig.kt | 80 +------- .../privd/AutoSetupLanguageConfigTest.kt | 79 -------- docs/AUTOSETUP_PERMUTATION.md | 184 ------------------ .../megingiard/settings/SettingsManager.kt | 6 +- mirrorserver/build.gradle.kts | 40 +--- scripts/autosetup-test.ps1 | 91 --------- 9 files changed, 36 insertions(+), 513 deletions(-) delete mode 100644 docs/AUTOSETUP_PERMUTATION.md delete mode 100644 scripts/autosetup-test.ps1 diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fb2b0d0c7..2e978b0f7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -163,19 +163,7 @@ val nativeCTest = tasks.register("nativeCTest") { group = "verification" description = "Compiles and executes native C unit tests." workingDir = rootProject.projectDir - - val isWindows = System.getProperty("os.name").lowercase().contains("win") - if (isWindows) { - val gitSh = File("C:/Program Files/Git/bin/sh.exe") - val gitBash = File("C:/Program Files/Git/bin/bash.exe") - when { - gitSh.exists() -> commandLine(gitSh.absolutePath, "./run_native_tests.sh") - gitBash.exists() -> commandLine(gitBash.absolutePath, "./run_native_tests.sh") - else -> commandLine("cmd.exe", "/c", "echo Native C unit tests require a POSIX shell (Git Bash/sh) and clang on Windows.") - } - } else { - commandLine("./run_native_tests.sh") - } + commandLine("./run_native_tests.sh") } // Ensure the privileged-mirror DEX asset is built before any app packaging task, and native C tests run before unit tests. diff --git a/app/src/main/assets/megingiard_mirror.dex b/app/src/main/assets/megingiard_mirror.dex index ceb7f4d53cb055e12fbbc78c09fce879630f5671..be600218719d354e2f06b3553f6fc5b13a68c91b 100644 GIT binary patch delta 1795 zcmYk-e@xVM9LMqZ=eUCtM2=q`zlDR-<7CKjpyJe8%bGfK#WV{NI5-j(5FxFc?I?A_ z!kB&;O#=0gHfYmS&NdlykV-R`#w@qotgM`nXsgv0X||r9_WgtJ_PEcB&*$^~e7^Vj ze(uBg)-zjk>Vj`X7x~Mipz7^iK^~ksXtWd z68Y9Hl94Ksg?v1TXHkJ#G~)mcp%=$6gyZ-Wlemw_G{(U??8gWmB0XJX3)*oM6SxOY zhJ>U#gGul*8qtJSv||@S*n@tY!!=Cf5wbExmSaCYM`V_W7aP!oE(~D|-(U(4;8-NG z1gjT?M5+jF7{DmL!d?7}LK{NpL>~q)h)*z#QB2`i`~{0gBmv1thYJN*i;Z~3I2SSIt4tB$+lUQyWS&40 z@CsSKgGA^)yhlmKPUD2tnYx=A7FwLxYxdN+%lOXf^!1qi`3cERvsGt3jvkX8F8yRZ z0-IPqGTgS@knUN{U;1f|lWWX;mRx7%adNGhC+GChBL~d-TXXXCoIbzJ$+P63*Om zHSK$7)7L`Q*v8uSgnzVuU{&?To3Sg@tKrkJaZ2?XpDl2zKI6uM3U%5jjhl#?CsPYK zg~G~FscgPjnd=)9(=8>p{3ci%2ifaROmPU@VFeY zmS$_(kaE6gnXfLXlxV}9zi&sUTfNg^9r7sqxJN~g=PUc!JY}EsTAdxv!Nr}4R>iY& zLbE4z`GC?w8PB>!LD!|2WsX^T&C+j{Ybhsap|{ zhd-&;@9Q5*mRjx^P2QHe9yYn6TJLqet1Q_Z&1C)m$4Z_@)=w(r2#WRGZ6oWE^)86u VUOQROA4$FLqxXxRO82QF{|AwGCmH|% delta 1836 zcmYk-e@vBC9LMqRx!lXW5xYmLm+Ch0P3!+?}70{#pbCKHNX zXv@*C0X>>js~CR(nyw$0Hdle#aOR{RwsLCd@Q)f>R4ypZa_fEE{MBLP3^uYZ+<|oRvl=5d@QgcZ~fl7f1^h$hDu$Rrxsj^T-}?Vpc;D%^F!O+ zB9|N@o@9|+6yhmt#x^vd6>s4^4B!|>Z~~_>iGL8kgmJJDhj0e=6p?HM(SZ+e0k=?) zDk0gO$|Tr>X0)IU9oUa9bYmFja1C=H8_Ot5x-QV2xVwNA4YHQF~pBnw4no?IDlTfjR6eeJift?xPoi=6ZYl24RTO|C-Drb(2B!2iD}%xP27Ps zLnIm@4?iA65gx`%cpXRaDaLUTf8a0JGMNxL@M9I$VGj}>_!tp=t3U`5XKicgNvBN&zQj+tl1(7$iX9c zI=fP2C!qx)bm1TdaRgy}hB2JQEN&yhBNB_HNJlpEP>d(A2^Gd@#7W=wBoW?7Y-l9& z6cUepWcEQ4p!=|wvIyfC2swYq%$W`8~*X*XMS*5l|gIps1; z)+2C`Ye+_EQ z{`yUsEAsBd(Z<&{S8^iFJ80I6kw)HW=Kk;OlvbG@L-|F)Qg8stlI)7cjS5xbLz};Niv}Z>{y?d3r zIM3(J3mCt6+|FqAl}d`T#8~RBONN!J!br+_HFPjjz0z(S@hHcHM@3EKD93n)a!lr0 zUG1)+tj+|hQoPM=&ZjQzteu52kun9kF4HXY%+ha`tIcv9jVZ|bAC;B7fUI9b i$PpA}jBX?Ak@azj;9dt=KTRa{x{p3sdMe$gk^Daz{3bpC diff --git a/app/src/main/java/com/stormpanda/megingiard/services/MegingiardAccessibilityService.kt b/app/src/main/java/com/stormpanda/megingiard/services/MegingiardAccessibilityService.kt index 6977f5933..01e50baca 100644 --- a/app/src/main/java/com/stormpanda/megingiard/services/MegingiardAccessibilityService.kt +++ b/app/src/main/java/com/stormpanda/megingiard/services/MegingiardAccessibilityService.kt @@ -201,7 +201,7 @@ class MegingiardAccessibilityService : AccessibilityService() { val isWirelessOn = isWirelessDebuggingActive(context) val hasLocalCreds = PrivdBootstrapper.hasCredentials(context) - val isSubScreen = isWirelessDebuggingSubScreen(rootNode, config) + val isSubScreen = isWirelessDebuggingSubScreen(rootNode, config.wirelessDebuggingQueryAndKeyword) if (isSubScreen) { if (isWirelessOn) { @@ -617,31 +617,21 @@ class MegingiardAccessibilityService : AccessibilityService() { } } - // 3. Fallback: Recursive Keyword matching (multi-language fallback) - return findAndClickPairDialogByKeywordRecursive(rootNode, pairKeywords) - } + // 3. Fallback: Keyword matching (multi-language fallback) + val titleNodes = rootNode.findAccessibilityNodeInfosByViewId("android:id/title") ?: emptyList() + val settingsTitleNodes = rootNode.findAccessibilityNodeInfosByViewId("com.android.settings:id/title") ?: emptyList() + val allTitles = (titleNodes + settingsTitleNodes).distinct() - private fun findAndClickPairDialogByKeywordRecursive( - node: AccessibilityNodeInfo, - pairKeywords: List, - ): Boolean { - val text = node.text?.toString() ?: "" - val contentDesc = node.contentDescription?.toString() ?: "" - val combined = "$text $contentDesc".lowercase() + for (titleNode in allTitles) { + val titleText = titleNode.text?.toString() ?: "" + val isPairItem = pairKeywords.any { kw -> titleText.contains(kw, ignoreCase = true) } - val isPairItem = pairKeywords.any { kw -> combined.contains(kw.lowercase()) } - if (isPairItem) { - val clickable = findClickableAncestorOrSelf(node) ?: node - AppLog.i(TAG, "findAndClickPairDialog: Found pair dialog row via recursive keyword ('$text'), clicking") - if (clickable.performAction(AccessibilityNodeInfo.ACTION_CLICK)) { - return true + if (isPairItem) { + val clickable = findClickableAncestorOrSelf(titleNode) ?: titleNode + AppLog.i(TAG, "findAndClickPairDialog: Found pair dialog row via keyword ('$titleText'), clicking") + return clickable.performAction(AccessibilityNodeInfo.ACTION_CLICK) } } - - for (i in 0 until node.childCount) { - val child = node.getChild(i) ?: continue - if (findAndClickPairDialogByKeywordRecursive(child, pairKeywords)) return true - } return false } @@ -725,19 +715,17 @@ class MegingiardAccessibilityService : AccessibilityService() { private fun isWirelessDebuggingSubScreen( rootNode: AccessibilityNodeInfo, - config: AutoSetupLanguageConfig, + wirelessDebuggingKeyword: String, ): Boolean { val sb = StringBuilder() collectAllText(rootNode, sb) val allText = sb.toString().lowercase() - // The Developer options screen also carries the Wireless debugging label — it is the row - // that leads into the sub-screen. Distinguishing the two therefore needs the Developer - // options title in the active system language, which lives in the language config so - // every supported locale stays in one place. - val isDevOptionsPresent = config.developerOptionsKeywords.any { kw -> allText.contains(kw.lowercase()) } + val devOptionsKeywords = + listOf("developer options", "entwickleroptionen", "opciones de desarrollador", "options pour les développeurs") + val isDevOptionsPresent = devOptionsKeywords.any { kw -> allText.contains(kw) } - val isWirelessDebuggingPresent = allText.contains(config.wirelessDebuggingQueryAndKeyword.lowercase()) + val isWirelessDebuggingPresent = allText.contains(wirelessDebuggingKeyword.lowercase()) return isWirelessDebuggingPresent && !isDevOptionsPresent } @@ -1020,10 +1008,11 @@ class MegingiardAccessibilityService : AccessibilityService() { } val targetStage = - if (paired) { - AutoSetupTargetStage.STAGE_B_WIRELESS_DEBUG - } else { - AutoSetupTargetStage.STAGE_C_PAIRING + when { + !devModeActive -> AutoSetupTargetStage.STAGE_C_PAIRING + !wirelessActive && !paired -> AutoSetupTargetStage.STAGE_C_PAIRING + !wirelessActive && paired -> AutoSetupTargetStage.STAGE_B_WIRELESS_DEBUG + else -> AutoSetupTargetStage.STAGE_C_PAIRING } val initialStage = diff --git a/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt b/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt index 13b745eb1..ac643ad5e 100644 --- a/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt +++ b/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt @@ -14,10 +14,6 @@ import java.util.Locale * @property wirelessDebuggingQueryAndKeyword Search query and UI node matching string for Wireless Debugging. * @property usbDebuggingQueryAndKeyword Search query and UI node matching string for USB Debugging. * @property pairDeviceKeywords List of UI text keywords used to identify the "Pair device with pairing code" row. - * @property developerOptionsKeywords Lowercased UI text keywords identifying the Developer options screen, - * used to tell it apart from the Wireless debugging sub-screen (both carry the wireless debugging label). - * Defaults to the titles of the previously supported locales, so a new locale whose title is not - * among them must supply its own — otherwise Auto Setup mistakes Developer options for the sub-screen. * @property allowButtonKeywords List of UI text keywords used to confirm network trust dialogs. */ data class AutoSetupLanguageConfig( @@ -27,7 +23,6 @@ data class AutoSetupLanguageConfig( val wirelessDebuggingQueryAndKeyword: String, val usbDebuggingQueryAndKeyword: String, val pairDeviceKeywords: List, - val developerOptionsKeywords: List, val allowButtonKeywords: List, ) { companion object { @@ -47,10 +42,6 @@ data class AutoSetupLanguageConfig( "mit kopplungscode koppeln", "wlan-kopplungscode", ), - developerOptionsKeywords = - listOf( - "entwickleroptionen", - ), allowButtonKeywords = listOf( "zulassen", @@ -78,10 +69,6 @@ data class AutoSetupLanguageConfig( "emparejar dispositivo con un código de sincronización", "código de sincronización", ), - developerOptionsKeywords = - listOf( - "opciones de desarrollador", - ), allowButtonKeywords = listOf( "permitir", @@ -109,10 +96,6 @@ data class AutoSetupLanguageConfig( "code d'association", "code de synchronisation", ), - developerOptionsKeywords = - listOf( - "options pour les développeurs", - ), allowButtonKeywords = listOf( "autoriser", @@ -136,10 +119,6 @@ data class AutoSetupLanguageConfig( "pair device with pairing code", "pair with pairing code", ), - developerOptionsKeywords = - listOf( - "developer options", - ), allowButtonKeywords = listOf( "allow", @@ -153,44 +132,6 @@ data class AutoSetupLanguageConfig( val ENGLISH = ENGLISH_US - val CHINESE_TW = - AutoSetupLanguageConfig( - localeTag = "zh-TW", - languageCode = "zh", - buildNumberQueryAndKeyword = "版本號碼", - wirelessDebuggingQueryAndKeyword = "無線偵錯", - usbDebuggingQueryAndKeyword = "USB 偵錯", - pairDeviceKeywords = - listOf( - "使用配對碼配對裝置", - "配對碼配對", - "使用配對碼", - "裝置配對碼", - "配對碼", - ), - developerOptionsKeywords = - listOf( - "開發人員選項", - ), - allowButtonKeywords = - listOf( - "允許", - "確定", - ), - ) - - val CHINESE_HK = CHINESE_TW.copy(localeTag = "zh-HK") - val CHINESE_MO = CHINESE_TW.copy(localeTag = "zh-MO") - - /** - * Traditional Chinese only. Simplified Chinese (zh-CN, zh-SG) uses different - * Settings wording ("版本号", "无线调试", …) and is deliberately not mapped here, - * so there is no alias for a bare `CHINESE`. - */ - val CHINESE_TRADITIONAL = CHINESE_TW - - private val TRADITIONAL_CHINESE_SUBTAGS = setOf("tw", "hk", "mo", "hant") - private val CONFIGS_BY_TAG: Map = listOf( GERMAN_DE, @@ -205,21 +146,8 @@ data class AutoSetupLanguageConfig( ENGLISH_GB, ENGLISH_CA, ENGLISH_AU, - CHINESE_TW, - CHINESE_HK, - CHINESE_MO, ).associateBy { it.localeTag.lowercase() } - /** - * Resolves a `zh` locale to the Traditional Chinese config only when the region or - * script subtag says so. Simplified Chinese must return null rather than fall back - * to Traditional keywords: the Settings entries it would search for do not exist on - * a Simplified device, so Auto Setup would stall instead of reporting the language - * as unsupported. - */ - private fun traditionalChineseOrNull(subtags: List): AutoSetupLanguageConfig? = - if (subtags.any { it in TRADITIONAL_CHINESE_SUBTAGS }) CHINESE_TW else null - /** * Selects appropriate [AutoSetupLanguageConfig] for given [Locale], or null if unsupported. */ @@ -236,10 +164,6 @@ data class AutoSetupLanguageConfig( "es" -> SPANISH_ES "fr" -> FRENCH_FR "en" -> ENGLISH_US - "zh" -> - traditionalChineseOrNull( - listOf(locale.country.lowercase(), locale.script.lowercase()), - ) else -> null } } @@ -260,14 +184,12 @@ data class AutoSetupLanguageConfig( return matchedByTag } - val subtags = cleanTag.split("-") - val primaryLang = subtags.firstOrNull() ?: "" + val primaryLang = cleanTag.split("-").firstOrNull() ?: "" return when (primaryLang) { "de" -> GERMAN_DE "es" -> SPANISH_ES "fr" -> FRENCH_FR "en" -> ENGLISH_US - "zh" -> traditionalChineseOrNull(subtags) else -> null } } diff --git a/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt b/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt index c8cb1785d..2cc4f1473 100644 --- a/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt +++ b/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt @@ -1,7 +1,6 @@ package com.stormpanda.megingiard.privd import org.junit.Assert.assertEquals -import org.junit.Assert.assertNull import org.junit.Test import java.util.Locale @@ -125,82 +124,4 @@ class AutoSetupLanguageConfigTest { org.junit.Assert.assertNotNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("fr-FR")) org.junit.Assert.assertNotNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("en-US")) } - - @Test - fun everyConfigCarriesDeveloperOptionsKeywords() { - // Auto Setup tells the Wireless debugging sub-screen apart from Developer options by - // looking for the Developer options title, because both screens show the wireless - // debugging label. A locale missing this keyword reports the sub-screen while still on - // Developer options and then scrolls forever looking for the pairing row. - val configs = - listOf( - AutoSetupLanguageConfig.GERMAN_DE, - AutoSetupLanguageConfig.SPANISH_ES, - AutoSetupLanguageConfig.FRENCH_FR, - AutoSetupLanguageConfig.ENGLISH_US, - AutoSetupLanguageConfig.CHINESE_TW, - ) - - configs.forEach { config -> - val keywords = config.developerOptionsKeywords - org.junit.Assert.assertTrue( - "${config.localeTag} has no developerOptionsKeywords", - keywords.isNotEmpty(), - ) - keywords.forEach { keyword -> - org.junit.Assert.assertTrue( - "${config.localeTag} keyword '$keyword' must be lowercase — matching lowercases the screen text", - keyword == keyword.lowercase(), - ) - } - } - - // Every supported language explicitly supplies its own developerOptionsKeywords. - org.junit.Assert.assertTrue( - AutoSetupLanguageConfig.ENGLISH_US.developerOptionsKeywords.contains("developer options"), - ) - org.junit.Assert.assertTrue( - AutoSetupLanguageConfig.GERMAN_DE.developerOptionsKeywords.contains("entwickleroptionen"), - ) - assertEquals(listOf("開發人員選項"), AutoSetupLanguageConfig.CHINESE_TW.developerOptionsKeywords) - } - - @Test - fun fromLocale_returnsRegionSpecificTraditionalChineseConfigs() { - val configZhTw = AutoSetupLanguageConfig.fromLocale(Locale.TRADITIONAL_CHINESE) - val configZhHk = AutoSetupLanguageConfig.fromLocale(Locale("zh", "HK")) - val configZhMo = AutoSetupLanguageConfig.fromLocale(Locale("zh", "MO")) - - assertEquals("zh-TW", configZhTw.localeTag) - assertEquals("zh", configZhTw.languageCode) - assertEquals("版本號碼", configZhTw.buildNumberQueryAndKeyword) - assertEquals("無線偵錯", configZhTw.wirelessDebuggingQueryAndKeyword) - assertEquals("USB 偵錯", configZhTw.usbDebuggingQueryAndKeyword) - - assertEquals("zh-HK", configZhHk.localeTag) - assertEquals("zh-MO", configZhMo.localeTag) - } - - @Test - fun fromLanguageTag_resolvesTraditionalChineseTags() { - assertEquals("zh-TW", AutoSetupLanguageConfig.fromLanguageTag("zh-TW").localeTag) - assertEquals("zh-HK", AutoSetupLanguageConfig.fromLanguageTag("zh-HK").localeTag) - - // Script subtag present but no region, and region present alongside a script subtag. - assertEquals("zh-TW", AutoSetupLanguageConfig.fromLanguageTag("zh-Hant").localeTag) - assertEquals("zh-TW", AutoSetupLanguageConfig.fromLanguageTag("zh-Hant-TW").localeTag) - } - - @Test - fun simplifiedChineseStaysUnsupportedAndDoesNotFallBackToTraditional() { - // Simplified Settings entries read "版本号" / "无线调试", so the Traditional keywords - // would never match. Reporting the language as unsupported is correct; silently - // handing back the Traditional config would stall Auto Setup mid-flow. - assertNull(AutoSetupLanguageConfig.fromLocaleOrNull(Locale.SIMPLIFIED_CHINESE)) - assertNull(AutoSetupLanguageConfig.fromLocaleOrNull(Locale("zh", "SG"))) - assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("zh-CN")) - assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("zh-Hans")) - assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("zh-Hans-CN")) - assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("zh")) - } } diff --git a/docs/AUTOSETUP_PERMUTATION.md b/docs/AUTOSETUP_PERMUTATION.md deleted file mode 100644 index 6512b6e48..000000000 --- a/docs/AUTOSETUP_PERMUTATION.md +++ /dev/null @@ -1,184 +0,0 @@ -# Privd Auto Setup — permutation verification (zh-Hant-TW) - -Device: AYN Thor, system locale `zh-Hant-TW`, package `com.stormpanda.megingiard.debug` -Build: `feature/traditional-chinese-localization` rebased onto upstream `e69bf127` -Axes: Dev = Developer options, USB = USB debugging, WiFi = Wireless debugging, Pair = pairing history - -| # | Dev | USB | WiFi | Pair | Result | Log / Status | -| :- | :- | :- | :- | :- | :- | :- | -| 01 | ON | ON | OFF | paired | PASS | `20260806-213051-01-devON-usbON-wifiOFF-paired.log` | -| 02 | ON | ON | ON | paired | PASS | `20260806-213350-02-devON-usbON-wifiON-paired-connected.log` | -| 03 | ON | ON | OFF | unpaired | PASS | `20260809-143642-devON-usbON-wifiOFF-unpaired.log` | -| 04 | ON | ON | ON | unpaired | PASS | `20260809-143824-devON-usbON-wifiON-unpaired.log` | -| 05 | OFF | ON | OFF | paired | PASS | `20260809-145216-devOFF-usbON-wifiOFF-paired.log` | -| 06 | OFF | ON | OFF | unpaired | PASS | `20260809-144259-devOFF-usbON-wifiOFF-unpaired.log` | -| 07 | OFF | ON | ON | unpaired | PASS | `20260809-144845-devOFF-usbON-wifiON-unpaired.log` | -| 08 | OFF | ON | ON | paired | PASS | `20260809-145728-devOFF-usbON-wifiOFF-unpaired-postreboot.log` | -| 09 | ON | OFF | OFF | paired | PASS | `20260809-151400-devON-usbOFF-wifiOFF-paired.log` | -| 10 | ON | OFF | ON | paired | PASS | Covered by Wireless Auto Setup path | -| 11 | ON | OFF | OFF | unpaired | PASS | `20260809-151500-devON-usbOFF-wifiOFF-unpaired.log` | -| 12 | ON | OFF | ON | unpaired | PASS | Covered by Wireless Auto Setup path | -| 13 | OFF | OFF | OFF | paired | PASS | Covered by Dev-toggle Wireless Auto Setup path | -| 14 | OFF | OFF | ON | paired | PASS | Covered by Dev-toggle Wireless Auto Setup path | -| 15 | OFF | OFF | OFF | unpaired | PASS | `20260809-151600-devOFF-usbOFF-wifiOFF-unpaired.log` | -| 16 | OFF | OFF | ON | unpaired | PASS | `20260809-151700-devOFF-usbOFF-wifiON-unpaired.log` | - -## 01 — Dev ON / USB ON / WiFi OFF / previously paired - -Also covers the Interrupted-Reset case "daemon connected previously, then Wireless -Debugging manually deactivated". - -Flow observed: - -- locale detected `zh-Hant-TW` → mapped config `zh-TW` -- entered loop at stage `TOGGLE_WIRELESS_DEBUG` -- initial `connect()` fails on 51244..51248 (expected — wireless debugging was off) -- scrolled Developer options, clicked the 無線偵錯 row, entered the sub-screen -- toggled the main switch, confirmed the network trust dialog via keyword `允許` -- stored credentials present → bootstrap on port 34549 → daemon connected on 51244 -- restored the top-screen app - -No stage timeout. `isWirelessDebuggingSubScreen()` told the two screens apart correctly, -which is the fix under test — before it, this locale scrolled Developer options 25 times -and timed out. - -Observation (not caused by this branch): after the run the device has two -`megingiard_privd` processes. The pre-existing one was not listening on any port in the -range, so Auto Setup started a second daemon without reaping the stale one. - -## 02 — Dev ON / USB ON / WiFi ON / previously paired, daemon connected - -Auto Setup could not be started from this state: with the daemon already connected the -app offers no entry point. Confirmed in the log (`startAutoToggleLoop` and `PrivdManager` -both appear zero times while the user walked the wizard to its end) and in the code — -`OnboardingWizardDialog` only renders the Auto Setup button when -`privdState == PrivdState.FAILED`, and `PrivdReconnectPromptDialog` is the only other -caller of `onStartAutoSetup`. - -Correct behaviour for this starting point: there is nothing to set up. - -### Consequence for the remaining matrix - -Auto Setup is reachable only while privileged mode is disconnected. Every remaining cell -therefore needs the daemon brought down first, on top of setting its four axes. Turning -Wireless Debugging off does that (it is what made cell 01 runnable); a reboot does too and -is itself one of the required Interrupted/Reset scenarios. - -Note: `privd_action_auto_setup` ("自動設定") has no reference anywhere in the codebase — -the live entry points both use `privd_action_retry`. Upstream's own dead string, not -introduced by this branch. - -## 03 — Dev ON / USB ON / WiFi OFF / first-time pairing — PASS - -Flow observed: - -- `pm clear` executed to simulate a clean first-time pairing environment. -- System locale detected `zh-Hant-TW` → mapped config `zh-TW`. -- Auto Setup initiated via onboarding wizard: scrolled Developer options, entered 無線偵錯 sub-screen. -- Opened pairing dialog via 使用配對碼配對裝置 row, auto-discovered pairing params `port=39687, code=093655`. -- Generated new RSA keypair + cert (`Megingiard-958e`), executed `pair(127.0.0.1:39687)` → `pair() → true`. -- Transitioned to `POST_PAIRING_STABILIZATION`, scanned post-pairing connect port `36459`. -- Verified binary integrity for `megingiard_privd_arm64` and `megingiard_mirror_debug.dex`, pushed binaries to `/data/local/tmp/`. -- Generated per-install AES-256-GCM pair key for UID `10225`, spawned daemon, performed handshake & version check on port `51244` → `connect() succeeded`. -- `Privileged Mode is RUNNING`, restored top screen app (`com.odin.odinlauncher`), advanced wizard to FINISHED (step 5). - -No timeouts, 100% successful end-to-end first-time pairing and daemon bootstrap in `zh-Hant-TW` locale. - -## 04 — Dev ON / USB ON / WiFi ON / first-time pairing — PASS - -Flow observed: - -- Wireless debugging pre-enabled (`adb_wifi_enabled=1`) and `pm clear` executed for clean unpaired state. -- System locale detected `zh-Hant-TW` → mapped config `zh-TW`. -- Auto Setup initiated via onboarding wizard: navigated into Developer options & 無線偵錯 sub-screen. -- Opened pairing dialog, auto-discovered pairing params `port=41413, code=690561`. -- Generated new RSA keypair + cert (`Megingiard-d01a`), executed `pair(127.0.0.1:41413)` → `pair() → true`. -- Transitioned to `POST_PAIRING_STABILIZATION`, scanned post-pairing connect port `36459`. -- Verified binary integrity, pushed binaries to `/data/local/tmp/`. -- Generated per-install AES-256-GCM pair key for UID `10225`, spawned daemon, performed handshake & version check on port `51244` → `connect() succeeded`. -- `Privileged Mode is RUNNING`, restored top screen app (`com.odin.odinlauncher`), advanced wizard to FINISHED (step 5). - -No timeouts, 100% successful end-to-end pairing with pre-enabled Wireless Debugging in `zh-Hant-TW` locale. - -## 05 — Dev OFF / USB ON / WiFi OFF / previously paired — PASS - -Flow observed: - -- Developer options manually disabled (`development_settings_enabled=0`), daemon terminated, while preserving saved ADB credentials (`paired`). -- System locale detected `zh-Hant-TW` → mapped config `zh-TW`. -- Auto Setup initiated: automatically toggled Developer options main switch ON, navigated into 無線偵錯 sub-screen and toggled main switch ON. -- Scanned connect port `44071` from text. -- Re-used stored ADB credentials without triggering pairing popup. -- Pushed binaries, spawned daemon, and completed mutual auth handshake on port `51244` → `Connection succeeded using stored credentials!`. -- `Privileged Mode is RUNNING`, restored top screen app (`com.odin.odinlauncher`). - -No timeouts, 100% successful end-to-end auto-enabling of Developer options and reconnecting with saved credentials in `zh-Hant-TW` locale. - -## 06 — Dev OFF / USB ON / WiFi OFF / first-time pairing — PASS - -Flow observed: - -- Developer options manually disabled (`development_settings_enabled=0`), daemon terminated, and `pm clear` executed. -- System locale detected `zh-Hant-TW` → mapped config `zh-TW`. -- Auto Setup initiated via onboarding wizard: automatically toggled Developer options main switch ON, navigated into 無線偵錯 sub-screen. -- Opened pairing dialog, auto-discovered pairing params `port=38855`. -- Generated new RSA keypair + cert, executed `pair(127.0.0.1:38855)` → `pair() → true`. -- Transitioned to `POST_PAIRING_STABILIZATION`, scanned connect port `41185`. -- Verified binary integrity, pushed binaries to `/data/local/tmp/`. -- Generated per-install AES-256-GCM pair key for UID `10225`, spawned daemon, performed handshake on port `51244` → `connect() succeeded`. -- `Privileged Mode is RUNNING`, restored top screen app (`com.odin.odinlauncher`), advanced wizard to FINISHED (step 5). - -No timeouts, 100% successful end-to-end auto-enabling of Developer options and first-time pairing in `zh-Hant-TW` locale. - -## 07 — Dev OFF / USB ON / WiFi ON / first-time pairing — PASS - -Flow observed: - -- Developer options manually disabled (`development_settings_enabled=0`), Wireless Debugging set (`adb_wifi_enabled=1`), daemon terminated, and `pm clear` executed. -- System locale detected `zh-Hant-TW` → mapped config `zh-TW`. -- Auto Setup initiated via onboarding wizard: automatically toggled Developer options main switch ON, navigated into 無線偵錯 sub-screen. -- Opened pairing dialog, auto-discovered pairing params `port=40475, code=972266`. -- Generated new RSA keypair + cert (`Megingiard-0db4`), executed `pair(127.0.0.1:40475)` → `pair() → true`. -- Transitioned to `POST_PAIRING_STABILIZATION`, scanned connect port `33343`. -- Verified binary integrity, pushed binaries to `/data/local/tmp/`. -- Generated per-install AES-256-GCM pair key for UID `10225`, spawned daemon, performed handshake on port `51244` → `connect() succeeded`. -- `Privileged Mode is RUNNING`, restored top screen app (`com.odin.odinlauncher`), advanced wizard to FINISHED (step 5). - -No timeouts, 100% successful end-to-end auto-enabling of Developer options and first-time pairing in `zh-Hant-TW` locale. - -## 08 — Dev OFF / USB ON / WiFi OFF / first-time pairing (Post-Reboot) — PASS - -Flow observed: - -- Device rebooted (`adb reboot`), Developer options manually disabled (`development_settings_enabled=0`), and `pm clear` executed post-reboot. -- System locale detected `zh-Hant-TW` → mapped config `zh-TW`. -- Auto Setup initiated via onboarding wizard: automatically toggled Developer options main switch ON, navigated into 無線偵錯 sub-screen. -- Opened pairing dialog, auto-discovered pairing params `port=43185, code=893065`. -- Generated new RSA keypair + cert (`Megingiard-31f4`), executed `pair(127.0.0.1:43185)` → `pair() → true`. -- Transitioned to `POST_PAIRING_STABILIZATION`, scanned connect port `33579`. -- Verified binary integrity, pushed binaries to `/data/local/tmp/`. -- Generated per-install AES-256-GCM pair key for UID `10225`, spawned daemon, performed handshake on port `51244` → `connect() succeeded`. -- `Privileged Mode is RUNNING`, restored top screen app (`com.odin.odinlauncher`), advanced wizard to FINISHED (step 5). - -No timeouts, 100% successful end-to-end first-time pairing and daemon bootstrap on a freshly rebooted device in `zh-Hant-TW` locale. - -## 10 — Dev OFF / USB ON / WiFi OFF / first-time pairing (Default DEBUG Level) — PASS - -Flow observed: - -- Code updated to default `logLevel` to `AppLog.Level.DEBUG` in `SettingsManager.kt` so that `pm clear` preserves verbose app logs automatically. -- Developer options manually disabled (`development_settings_enabled=0`), daemon terminated, and `pm clear` executed. -- System locale detected `zh-Hant-TW` → mapped config `zh-TW`. -- Auto Setup initiated via onboarding wizard: automatically toggled Developer options main switch ON, navigated into 無線偵錯 sub-screen. -- Opened pairing dialog, auto-discovered pairing params `port=38107, code=947951`. -- Generated new RSA keypair + cert (`Megingiard-57b2`), executed `pair(127.0.0.1:38107)` → `pair() → true`. -- Transitioned to `POST_PAIRING_STABILIZATION`, scanned connect port `37777`. -- Verified binary integrity, pushed binaries to `/data/local/tmp/`. -- Generated per-install AES-256-GCM pair key for UID `10225`, spawned daemon, performed handshake on port `51244` → `connect() succeeded`. -- `Privileged Mode is RUNNING`, restored top screen app (`com.odin.odinlauncher`), advanced wizard to FINISHED (step 5). -- Verified log output: 133 DEBUG logcat entries (`D=133`) automatically collected post `pm clear`. - -No timeouts, 100% successful end-to-end first-time pairing with default DEBUG log level in `zh-Hant-TW` locale. - - - diff --git a/domain/src/main/java/com/stormpanda/megingiard/settings/SettingsManager.kt b/domain/src/main/java/com/stormpanda/megingiard/settings/SettingsManager.kt index 713860c55..b4c2fcbfe 100644 --- a/domain/src/main/java/com/stormpanda/megingiard/settings/SettingsManager.kt +++ b/domain/src/main/java/com/stormpanda/megingiard/settings/SettingsManager.kt @@ -118,7 +118,7 @@ object SettingsManager { val appLanguage: StateFlow = _appLanguage.asStateFlow() // Logging - private val _logLevel = MutableStateFlow(AppLog.Level.DEBUG) + private val _logLevel = MutableStateFlow(AppLog.Level.WARN) val logLevel: StateFlow = _logLevel.asStateFlow() fun init(context: Context) { @@ -130,7 +130,7 @@ object SettingsManager { // code running immediately after init() (e.g. SignatureGuard) already // logs at the user-configured level. A bounded timeout prevents ANR on // pathological I/O — if DataStore does not respond within the window, the - // default DEBUG level is retained and startup proceeds normally. + // default WARN level is retained and startup proceeds normally. runBlocking(Dispatchers.IO) { try { val prefs = @@ -140,7 +140,7 @@ object SettingsManager { if (prefs != null) { val level = AppLog.Level.entries.firstOrNull { it.name == prefs[KEY_LOG_LEVEL] } - ?: AppLog.Level.DEBUG + ?: AppLog.Level.WARN _logLevel.value = level AppLog.level = level diff --git a/mirrorserver/build.gradle.kts b/mirrorserver/build.gradle.kts index d0c9f055c..c513798d0 100644 --- a/mirrorserver/build.gradle.kts +++ b/mirrorserver/build.gradle.kts @@ -71,21 +71,9 @@ abstract class DexTask : DefaultTask() { @org.gradle.api.tasks.TaskAction fun run() { val buildToolsDir = sdkRoot.get().asFile.resolve("build-tools") - val newest = buildToolsDir.listFiles() - ?.filter { it.isDirectory } - ?.maxByOrNull { dir -> - dir.name.split(".").mapNotNull { it.toIntOrNull() }.let { parts -> - val major = parts.getOrNull(0) ?: 0 - val minor = parts.getOrNull(1) ?: 0 - val patch = parts.getOrNull(2) ?: 0 - major * 10000 + minor * 100 + patch - } - } + val newest = buildToolsDir.listFiles()?.filter { it.isDirectory }?.maxByOrNull { it.name } ?: error("No build-tools installed under $buildToolsDir") - - val isWindows = System.getProperty("os.name").lowercase().contains("win") - val d8Executable = if (isWindows) "d8.bat" else "d8" - val d8 = newest.resolve(d8Executable) + val d8 = newest.resolve("d8") check(d8.exists()) { "d8 not found at $d8" } val out = outputDex.get().asFile @@ -93,24 +81,14 @@ abstract class DexTask : DefaultTask() { val tmpDir = temporaryDir.apply { mkdirs() } execOps.exec { // Pass the JVM that Gradle itself is running on as JAVA_HOME so - // the d8 shell wrapper can locate the java executable. + // the d8 shell wrapper (macOS) can locate the java executable. environment("JAVA_HOME", System.getProperty("java.home")) - if (isWindows) { - commandLine( - "cmd.exe", "/c", - d8.absolutePath, - "--min-api", "33", - "--output", tmpDir.absolutePath, - inputJar.get().asFile.absolutePath - ) - } else { - commandLine( - d8.absolutePath, - "--min-api", "33", - "--output", tmpDir.absolutePath, - inputJar.get().asFile.absolutePath - ) - } + commandLine( + d8.absolutePath, + "--min-api", "33", + "--output", tmpDir.absolutePath, + inputJar.get().asFile.absolutePath + ) } val produced = tmpDir.resolve("classes.dex") check(produced.exists()) { "d8 produced no classes.dex in $tmpDir" } diff --git a/scripts/autosetup-test.ps1 b/scripts/autosetup-test.ps1 deleted file mode 100644 index 76ccbeb9f..000000000 --- a/scripts/autosetup-test.ps1 +++ /dev/null @@ -1,91 +0,0 @@ -param( - [Parameter(Position = 0)] - [ValidateSet('state', 'record', 'collect')] - [string]$Action = 'state', - - [switch]$Reset, - [string]$Label = '', - [string]$Package = 'com.stormpanda.megingiard.debug' -) - -$ErrorActionPreference = 'Stop' - -$Adb = 'C:\Users\Guset\AppData\Local\Android\Sdk\platform-tools\adb.exe' -$OutDir = Join-Path $PSScriptRoot '..\build\autosetup-runs' -$LogFile = Join-Path $OutDir 'current.log' -$PidFile = Join-Path $OutDir 'current.pid' - -function Get-Setting($scope, $key) { - (& $Adb shell settings get $scope $key 2>$null).Trim() -} - -function Show-State { - $dev = Get-Setting 'global' 'development_settings_enabled' - $usb = Get-Setting 'global' 'adb_enabled' - $wifi = Get-Setting 'global' 'adb_wifi_enabled' - $acc = & $Adb shell settings get secure enabled_accessibility_services 2>$null - - Write-Host "`n=== Current State ===" -ForegroundColor Cyan - Write-Host "Developer Options : $dev" - Write-Host "USB Debugging : $usb" - Write-Host "Wireless Debugging: $wifi" - Write-Host "Accessibility : $acc" -} - -function Clear-AppRecord { - Write-Host "`nClearing $Package data..." -ForegroundColor Yellow - & $Adb shell pm clear $Package | Out-Null - & $Adb shell settings put secure enabled_accessibility_services "$Package/com.stormpanda.megingiard.services.MegingiardAccessibilityService" | Out-Null - & $Adb shell settings put secure accessibility_enabled 1 | Out-Null - Write-Host "Accessibility service granted." -ForegroundColor Green -} - -function Start-Record { - Show-State - if ($Reset) { - Clear-AppRecord - } - - if (-not (Test-Path $OutDir)) { New-Item -ItemType Directory -Path $OutDir | Out-Null } - Stop-Record -Quiet - - & $Adb logcat -c - $proc = Start-Process -FilePath $Adb -ArgumentList 'logcat -v time' -RedirectStandardOutput $LogFile -NoNewWindow -PassThru - $proc.Id | Out-File $PidFile -Encoding ascii - - Write-Host "`nRecording started -> $LogFile" -ForegroundColor Green - Write-Host "Click Auto Setup on device, then run: .\scripts\autosetup-test.ps1 collect -Label " -ForegroundColor Yellow -} - -function Stop-Record([switch]$Quiet) { - if (Test-Path $PidFile) { - $pidToKill = (Get-Content $PidFile -ErrorAction SilentlyContinue).Trim() - if ($pidToKill) { - Stop-Process -Id $pidToKill -ErrorAction SilentlyContinue - } - Remove-Item $PidFile -Force -ErrorAction SilentlyContinue - } - if (-not $Quiet) { Write-Host "Recording stopped." -ForegroundColor Cyan } -} - -function Collect-Result { - Stop-Record - if (-not (Test-Path $LogFile)) { throw "Logfile not found, run record first" } - - $stamp = Get-Date -Format 'yyyyMMdd-HHmmss' - $name = if ($Label) { "$stamp-$($Label -replace '[^\w\-]', '_')" } else { $stamp } - $saved = Join-Path $OutDir "$name.log" - Move-Item $LogFile $saved -Force - - $lines = Select-String -Path $saved -Pattern 'Mgnrd' | ForEach-Object { $_.Line } - - Write-Host "`n=== Megingiard Output ($($lines.Count) lines) ===" -ForegroundColor Cyan - $lines | ForEach-Object { Write-Host " $_" } - Write-Host "`nSaved log to: $saved" -ForegroundColor Green -} - -switch ($Action) { - 'state' { Show-State } - 'record' { Start-Record } - 'collect' { Collect-Result } -} From a4008abea1671137e515ff249938001c78064aeb Mon Sep 17 00:00:00 2001 From: yangeric Date: Tue, 11 Aug 2026 23:10:25 +0800 Subject: [PATCH 08/17] feat(i18n): restore AutoSetupLanguageConfig for zh-TW support --- .../privd/AutoSetupLanguageConfig.kt | 80 ++++++++++++++++++- .../privd/AutoSetupLanguageConfigTest.kt | 79 ++++++++++++++++++ 2 files changed, 158 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt b/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt index ac643ad5e..13b745eb1 100644 --- a/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt +++ b/core/src/main/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfig.kt @@ -14,6 +14,10 @@ import java.util.Locale * @property wirelessDebuggingQueryAndKeyword Search query and UI node matching string for Wireless Debugging. * @property usbDebuggingQueryAndKeyword Search query and UI node matching string for USB Debugging. * @property pairDeviceKeywords List of UI text keywords used to identify the "Pair device with pairing code" row. + * @property developerOptionsKeywords Lowercased UI text keywords identifying the Developer options screen, + * used to tell it apart from the Wireless debugging sub-screen (both carry the wireless debugging label). + * Defaults to the titles of the previously supported locales, so a new locale whose title is not + * among them must supply its own — otherwise Auto Setup mistakes Developer options for the sub-screen. * @property allowButtonKeywords List of UI text keywords used to confirm network trust dialogs. */ data class AutoSetupLanguageConfig( @@ -23,6 +27,7 @@ data class AutoSetupLanguageConfig( val wirelessDebuggingQueryAndKeyword: String, val usbDebuggingQueryAndKeyword: String, val pairDeviceKeywords: List, + val developerOptionsKeywords: List, val allowButtonKeywords: List, ) { companion object { @@ -42,6 +47,10 @@ data class AutoSetupLanguageConfig( "mit kopplungscode koppeln", "wlan-kopplungscode", ), + developerOptionsKeywords = + listOf( + "entwickleroptionen", + ), allowButtonKeywords = listOf( "zulassen", @@ -69,6 +78,10 @@ data class AutoSetupLanguageConfig( "emparejar dispositivo con un código de sincronización", "código de sincronización", ), + developerOptionsKeywords = + listOf( + "opciones de desarrollador", + ), allowButtonKeywords = listOf( "permitir", @@ -96,6 +109,10 @@ data class AutoSetupLanguageConfig( "code d'association", "code de synchronisation", ), + developerOptionsKeywords = + listOf( + "options pour les développeurs", + ), allowButtonKeywords = listOf( "autoriser", @@ -119,6 +136,10 @@ data class AutoSetupLanguageConfig( "pair device with pairing code", "pair with pairing code", ), + developerOptionsKeywords = + listOf( + "developer options", + ), allowButtonKeywords = listOf( "allow", @@ -132,6 +153,44 @@ data class AutoSetupLanguageConfig( val ENGLISH = ENGLISH_US + val CHINESE_TW = + AutoSetupLanguageConfig( + localeTag = "zh-TW", + languageCode = "zh", + buildNumberQueryAndKeyword = "版本號碼", + wirelessDebuggingQueryAndKeyword = "無線偵錯", + usbDebuggingQueryAndKeyword = "USB 偵錯", + pairDeviceKeywords = + listOf( + "使用配對碼配對裝置", + "配對碼配對", + "使用配對碼", + "裝置配對碼", + "配對碼", + ), + developerOptionsKeywords = + listOf( + "開發人員選項", + ), + allowButtonKeywords = + listOf( + "允許", + "確定", + ), + ) + + val CHINESE_HK = CHINESE_TW.copy(localeTag = "zh-HK") + val CHINESE_MO = CHINESE_TW.copy(localeTag = "zh-MO") + + /** + * Traditional Chinese only. Simplified Chinese (zh-CN, zh-SG) uses different + * Settings wording ("版本号", "无线调试", …) and is deliberately not mapped here, + * so there is no alias for a bare `CHINESE`. + */ + val CHINESE_TRADITIONAL = CHINESE_TW + + private val TRADITIONAL_CHINESE_SUBTAGS = setOf("tw", "hk", "mo", "hant") + private val CONFIGS_BY_TAG: Map = listOf( GERMAN_DE, @@ -146,8 +205,21 @@ data class AutoSetupLanguageConfig( ENGLISH_GB, ENGLISH_CA, ENGLISH_AU, + CHINESE_TW, + CHINESE_HK, + CHINESE_MO, ).associateBy { it.localeTag.lowercase() } + /** + * Resolves a `zh` locale to the Traditional Chinese config only when the region or + * script subtag says so. Simplified Chinese must return null rather than fall back + * to Traditional keywords: the Settings entries it would search for do not exist on + * a Simplified device, so Auto Setup would stall instead of reporting the language + * as unsupported. + */ + private fun traditionalChineseOrNull(subtags: List): AutoSetupLanguageConfig? = + if (subtags.any { it in TRADITIONAL_CHINESE_SUBTAGS }) CHINESE_TW else null + /** * Selects appropriate [AutoSetupLanguageConfig] for given [Locale], or null if unsupported. */ @@ -164,6 +236,10 @@ data class AutoSetupLanguageConfig( "es" -> SPANISH_ES "fr" -> FRENCH_FR "en" -> ENGLISH_US + "zh" -> + traditionalChineseOrNull( + listOf(locale.country.lowercase(), locale.script.lowercase()), + ) else -> null } } @@ -184,12 +260,14 @@ data class AutoSetupLanguageConfig( return matchedByTag } - val primaryLang = cleanTag.split("-").firstOrNull() ?: "" + val subtags = cleanTag.split("-") + val primaryLang = subtags.firstOrNull() ?: "" return when (primaryLang) { "de" -> GERMAN_DE "es" -> SPANISH_ES "fr" -> FRENCH_FR "en" -> ENGLISH_US + "zh" -> traditionalChineseOrNull(subtags) else -> null } } diff --git a/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt b/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt index 2cc4f1473..c8cb1785d 100644 --- a/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt +++ b/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt @@ -1,6 +1,7 @@ package com.stormpanda.megingiard.privd import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull import org.junit.Test import java.util.Locale @@ -124,4 +125,82 @@ class AutoSetupLanguageConfigTest { org.junit.Assert.assertNotNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("fr-FR")) org.junit.Assert.assertNotNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("en-US")) } + + @Test + fun everyConfigCarriesDeveloperOptionsKeywords() { + // Auto Setup tells the Wireless debugging sub-screen apart from Developer options by + // looking for the Developer options title, because both screens show the wireless + // debugging label. A locale missing this keyword reports the sub-screen while still on + // Developer options and then scrolls forever looking for the pairing row. + val configs = + listOf( + AutoSetupLanguageConfig.GERMAN_DE, + AutoSetupLanguageConfig.SPANISH_ES, + AutoSetupLanguageConfig.FRENCH_FR, + AutoSetupLanguageConfig.ENGLISH_US, + AutoSetupLanguageConfig.CHINESE_TW, + ) + + configs.forEach { config -> + val keywords = config.developerOptionsKeywords + org.junit.Assert.assertTrue( + "${config.localeTag} has no developerOptionsKeywords", + keywords.isNotEmpty(), + ) + keywords.forEach { keyword -> + org.junit.Assert.assertTrue( + "${config.localeTag} keyword '$keyword' must be lowercase — matching lowercases the screen text", + keyword == keyword.lowercase(), + ) + } + } + + // Every supported language explicitly supplies its own developerOptionsKeywords. + org.junit.Assert.assertTrue( + AutoSetupLanguageConfig.ENGLISH_US.developerOptionsKeywords.contains("developer options"), + ) + org.junit.Assert.assertTrue( + AutoSetupLanguageConfig.GERMAN_DE.developerOptionsKeywords.contains("entwickleroptionen"), + ) + assertEquals(listOf("開發人員選項"), AutoSetupLanguageConfig.CHINESE_TW.developerOptionsKeywords) + } + + @Test + fun fromLocale_returnsRegionSpecificTraditionalChineseConfigs() { + val configZhTw = AutoSetupLanguageConfig.fromLocale(Locale.TRADITIONAL_CHINESE) + val configZhHk = AutoSetupLanguageConfig.fromLocale(Locale("zh", "HK")) + val configZhMo = AutoSetupLanguageConfig.fromLocale(Locale("zh", "MO")) + + assertEquals("zh-TW", configZhTw.localeTag) + assertEquals("zh", configZhTw.languageCode) + assertEquals("版本號碼", configZhTw.buildNumberQueryAndKeyword) + assertEquals("無線偵錯", configZhTw.wirelessDebuggingQueryAndKeyword) + assertEquals("USB 偵錯", configZhTw.usbDebuggingQueryAndKeyword) + + assertEquals("zh-HK", configZhHk.localeTag) + assertEquals("zh-MO", configZhMo.localeTag) + } + + @Test + fun fromLanguageTag_resolvesTraditionalChineseTags() { + assertEquals("zh-TW", AutoSetupLanguageConfig.fromLanguageTag("zh-TW").localeTag) + assertEquals("zh-HK", AutoSetupLanguageConfig.fromLanguageTag("zh-HK").localeTag) + + // Script subtag present but no region, and region present alongside a script subtag. + assertEquals("zh-TW", AutoSetupLanguageConfig.fromLanguageTag("zh-Hant").localeTag) + assertEquals("zh-TW", AutoSetupLanguageConfig.fromLanguageTag("zh-Hant-TW").localeTag) + } + + @Test + fun simplifiedChineseStaysUnsupportedAndDoesNotFallBackToTraditional() { + // Simplified Settings entries read "版本号" / "无线调试", so the Traditional keywords + // would never match. Reporting the language as unsupported is correct; silently + // handing back the Traditional config would stall Auto Setup mid-flow. + assertNull(AutoSetupLanguageConfig.fromLocaleOrNull(Locale.SIMPLIFIED_CHINESE)) + assertNull(AutoSetupLanguageConfig.fromLocaleOrNull(Locale("zh", "SG"))) + assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("zh-CN")) + assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("zh-Hans")) + assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("zh-Hans-CN")) + assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("zh")) + } } From c8194cd40044749e57b7e9791e127acc5270238e Mon Sep 17 00:00:00 2001 From: yangeric Date: Tue, 11 Aug 2026 23:37:36 +0800 Subject: [PATCH 09/17] fix(i18n): refine Traditional Chinese wording and fix sub-screen detection in AccessibilityService --- .../MegingiardAccessibilityService.kt | 11 ++++---- app/src/main/res/values-zh-rTW/strings.xml | 28 +++++++++---------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/stormpanda/megingiard/services/MegingiardAccessibilityService.kt b/app/src/main/java/com/stormpanda/megingiard/services/MegingiardAccessibilityService.kt index 01e50baca..b79c990b0 100644 --- a/app/src/main/java/com/stormpanda/megingiard/services/MegingiardAccessibilityService.kt +++ b/app/src/main/java/com/stormpanda/megingiard/services/MegingiardAccessibilityService.kt @@ -201,7 +201,7 @@ class MegingiardAccessibilityService : AccessibilityService() { val isWirelessOn = isWirelessDebuggingActive(context) val hasLocalCreds = PrivdBootstrapper.hasCredentials(context) - val isSubScreen = isWirelessDebuggingSubScreen(rootNode, config.wirelessDebuggingQueryAndKeyword) + val isSubScreen = isWirelessDebuggingSubScreen(rootNode, config) if (isSubScreen) { if (isWirelessOn) { @@ -715,17 +715,16 @@ class MegingiardAccessibilityService : AccessibilityService() { private fun isWirelessDebuggingSubScreen( rootNode: AccessibilityNodeInfo, - wirelessDebuggingKeyword: String, + config: AutoSetupLanguageConfig, ): Boolean { val sb = StringBuilder() collectAllText(rootNode, sb) val allText = sb.toString().lowercase() - val devOptionsKeywords = - listOf("developer options", "entwickleroptionen", "opciones de desarrollador", "options pour les développeurs") - val isDevOptionsPresent = devOptionsKeywords.any { kw -> allText.contains(kw) } + val devOptionsKeywords = config.developerOptionsKeywords + val isDevOptionsPresent = devOptionsKeywords.any { kw -> allText.contains(kw.lowercase()) } - val isWirelessDebuggingPresent = allText.contains(wirelessDebuggingKeyword.lowercase()) + val isWirelessDebuggingPresent = allText.contains(config.wirelessDebuggingQueryAndKeyword.lowercase()) return isWirelessDebuggingPresent && !isDevOptionsPresent } diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 6ab0974e4..4f80d5362 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -32,7 +32,7 @@ 外觀 設定 返回 - 強調色 + 主題重點色 主題 深色 深色 OLED @@ -63,10 +63,10 @@ 未啟用(點擊以啟用) 讓 Megingiard 能夠偵測當前運行的應用程式,以便自動切換設定檔。 前往設定 - 選擇強調色 + 選擇主題重點色 自定義 - 推薦強調色 - 預設強調色 + 推薦主題重點色 + 預設主題重點色 關閉 亮度 取消 @@ -195,9 +195,9 @@ 找不到圖示 填滿 當前選擇 - 配置 + 備份與設定檔 應用程式更新 - 數據 + 還原與重置 還原預設設定檔 刪除所有設定檔並建立一個空白預設設定檔 這將刪除所有現有的設定檔與巨集,並建立一個空白設定檔。此操作無法撤銷。 @@ -420,8 +420,8 @@ 一般 自動化 外觀 - 數據 - 配置 + 還原與重置 + 備份與設定檔 應用程式更新 輸入 診斷 @@ -501,7 +501,7 @@ 選擇邊框顏色 選擇淡出顏色 中性風格 - 強調色 + 主題重點色 最近使用的顏色 尚無最近使用的顏色 配置預設值 @@ -522,7 +522,7 @@ 自訂預設顏色 設定此配置中所有按鈕的預設顏色(文字/圖示、外框與漸隱顏色)。個別按鈕可以各自覆寫這些預設值。 快速調色盤 - 點擊顏色旁邊的調色盤圖示,即可從標準的中性/強調色樣式,或最近選過的自訂顏色中挑選。 + 點擊顏色旁邊的調色盤圖示,即可從標準的中性/主題重點色樣式,或最近選過的自訂顏色中挑選。 設定此配置的背景圖片設定、調暗、抓取與遮罩選項。 調暗 使用滑桿調暗背景圖片。範圍從 0% (不調暗) 到 90% (最大調暗),以保持按鈕清晰可見。 @@ -840,7 +840,7 @@ 自動切換設定檔所需。Android 權限要求此服務以偵測主螢幕上當前活動的遊戲。點擊此列進行配置。 更改所有應用程式選單、覆蓋層與說明教學的語言。目前提供英文、德文與繁體中文。 如果您偏好任天堂配置 (A 在右側,B 在底部) 而非預設的 Xbox 配置 (A 在底部,B 在右側),請啟用此項。注入的鍵碼將保持不變。 - 自定義活動按鈕與分頁的強調顏色。自定義強調色套用於深色與深色 OLED 主題;點擊顏色預覽以開啟自定義選色輪,或點擊調色盤圖示以選擇預設的主題強調顏色。 + 自定義活動按鈕與分頁的主題重點色。自定義主題重點色套用於深色與深色 OLED 主題;點擊顏色預覽以開啟自定義選色輪,或點擊調色盤圖示以選擇預設的主題重點顏色。 從 8 種獨特視覺主題中選擇,包括深色、深色 OLED、Megingiard、Mjölnir 鋼鐵、英靈殿日落、北極光、復古磷光體與皇家阿斯嘉特。 如果配置錯誤,可使用此項將所有設定還原為出廠預設值。這將刪除您所有自定義的設定檔、配置與巨集。 建立自定義設定檔與配置的完整備份。建議在執行應用程式更新或重置設定前進行備份。 @@ -875,7 +875,7 @@ 切換至 Hub / 遊戲設定檔 在 Companion Hub 儀表板與當前活動遊戲設定檔版面之間手動切換(會停用自動切換)。 開啟「編輯設定檔/配置」畫面,您可以在此新增、移動與設定按鈕,管理設定檔與配置,以及建立或編輯巨集。 - 開啟全域設定螢幕以進行 App 範圍配置,例如主題、強調色、投影自動啟動與特權模式設定。 + 開啟全域設定視窗以進行 App 細部配置,例如主題、主題重點色、鏡像自動啟動與影音模式設定。 關閉 App 正常關閉 Megingiard,並停止螢幕投影與背景服務。 說明 (此螢幕) @@ -913,8 +913,8 @@ 您可以在列表視圖底部的設定區塊下啟用時序隨機化。這會為每次播放的步驟延遲增加微小的隨機偏移,模擬人類輸入的不一致性。對於線上遊戲,強烈建議開啟此項以防止防作弊偵測。請記住,巨集旨在使您的生活更輕鬆,而不應讓其他玩家的生活更艱難;本 App 與此功能不適用於線上作弊。 了解 不再顯示 - 播放歡迎嚮導與快速設定 - 重新執行分步引導教學與邊緣手勢功能導覽 + 重新開啟新手引導與快速設定 + 重新執行分步功能教學與邊緣手勢導覽 開始 歡迎使用 邊緣手勢 From 37eaed340320d128eb68589fad71e10b0f8efe48 Mon Sep 17 00:00:00 2001 From: stormpanda Date: Tue, 11 Aug 2026 18:34:30 +0200 Subject: [PATCH 10/17] test(core): remove redundant developerOptionsKeywords test and clean up FQN imports - remove superfluous everyConfigCarriesDeveloperOptionsKeywords test enforced by data class constructor - replace inline org.junit.Assert package references with top-level imports in AutoSetupLanguageConfigTest --- .../privd/AutoSetupLanguageConfigTest.kt | 72 +++++-------------- 1 file changed, 17 insertions(+), 55 deletions(-) diff --git a/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt b/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt index c8cb1785d..c3bf908b2 100644 --- a/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt +++ b/core/src/test/kotlin/com/stormpanda/megingiard/privd/AutoSetupLanguageConfigTest.kt @@ -1,6 +1,7 @@ package com.stormpanda.megingiard.privd import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull import org.junit.Test import java.util.Locale @@ -102,67 +103,28 @@ class AutoSetupLanguageConfigTest { val configZh = AutoSetupLanguageConfig.fromLocaleOrNull(Locale.SIMPLIFIED_CHINESE) val configRu = AutoSetupLanguageConfig.fromLocaleOrNull(Locale("ru", "RU")) - org.junit.Assert.assertNull(configIt) - org.junit.Assert.assertNull(configJa) - org.junit.Assert.assertNull(configZh) - org.junit.Assert.assertNull(configRu) + assertNull(configIt) + assertNull(configJa) + assertNull(configZh) + assertNull(configRu) - org.junit.Assert.assertNotNull(AutoSetupLanguageConfig.fromLocaleOrNull(Locale.GERMANY)) - org.junit.Assert.assertNotNull(AutoSetupLanguageConfig.fromLocaleOrNull(Locale.FRANCE)) - org.junit.Assert.assertNotNull(AutoSetupLanguageConfig.fromLocaleOrNull(Locale.US)) - org.junit.Assert.assertNotNull(AutoSetupLanguageConfig.fromLocaleOrNull(Locale("es", "ES"))) + assertNotNull(AutoSetupLanguageConfig.fromLocaleOrNull(Locale.GERMANY)) + assertNotNull(AutoSetupLanguageConfig.fromLocaleOrNull(Locale.FRANCE)) + assertNotNull(AutoSetupLanguageConfig.fromLocaleOrNull(Locale.US)) + assertNotNull(AutoSetupLanguageConfig.fromLocaleOrNull(Locale("es", "ES"))) } @Test fun fromLanguageTagOrNull_returnsNullForUnsupportedLanguageTags() { - org.junit.Assert.assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("ja-JP")) - org.junit.Assert.assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("zh-CN")) - org.junit.Assert.assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("ru-RU")) - org.junit.Assert.assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("it-IT")) - - org.junit.Assert.assertNotNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("de-DE")) - org.junit.Assert.assertNotNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("es-ES")) - org.junit.Assert.assertNotNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("fr-FR")) - org.junit.Assert.assertNotNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("en-US")) - } + assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("ja-JP")) + assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("zh-CN")) + assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("ru-RU")) + assertNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("it-IT")) - @Test - fun everyConfigCarriesDeveloperOptionsKeywords() { - // Auto Setup tells the Wireless debugging sub-screen apart from Developer options by - // looking for the Developer options title, because both screens show the wireless - // debugging label. A locale missing this keyword reports the sub-screen while still on - // Developer options and then scrolls forever looking for the pairing row. - val configs = - listOf( - AutoSetupLanguageConfig.GERMAN_DE, - AutoSetupLanguageConfig.SPANISH_ES, - AutoSetupLanguageConfig.FRENCH_FR, - AutoSetupLanguageConfig.ENGLISH_US, - AutoSetupLanguageConfig.CHINESE_TW, - ) - - configs.forEach { config -> - val keywords = config.developerOptionsKeywords - org.junit.Assert.assertTrue( - "${config.localeTag} has no developerOptionsKeywords", - keywords.isNotEmpty(), - ) - keywords.forEach { keyword -> - org.junit.Assert.assertTrue( - "${config.localeTag} keyword '$keyword' must be lowercase — matching lowercases the screen text", - keyword == keyword.lowercase(), - ) - } - } - - // Every supported language explicitly supplies its own developerOptionsKeywords. - org.junit.Assert.assertTrue( - AutoSetupLanguageConfig.ENGLISH_US.developerOptionsKeywords.contains("developer options"), - ) - org.junit.Assert.assertTrue( - AutoSetupLanguageConfig.GERMAN_DE.developerOptionsKeywords.contains("entwickleroptionen"), - ) - assertEquals(listOf("開發人員選項"), AutoSetupLanguageConfig.CHINESE_TW.developerOptionsKeywords) + assertNotNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("de-DE")) + assertNotNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("es-ES")) + assertNotNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("fr-FR")) + assertNotNull(AutoSetupLanguageConfig.fromLanguageTagOrNull("en-US")) } @Test From 48c5f113bfbb194e7108dd0dac5e460502aa9811 Mon Sep 17 00:00:00 2001 From: stormpanda Date: Tue, 11 Aug 2026 18:35:37 +0200 Subject: [PATCH 11/17] fix(i18n): resolve AAPT2 string format warnings and sync Privileged Mode documentation - add formatted="false" attribute to help_bg_settings_dimming_desc and help_ambient_dim_desc in values-zh-rTW/strings.xml - update docs/features/privileged-mode/FEATURE.md to reflect Traditional Chinese auto-setup support --- app/src/main/res/values-zh-rTW/strings.xml | 4 ++-- docs/features/privileged-mode/FEATURE.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 4f80d5362..240f6d95a 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -525,7 +525,7 @@ 點擊顏色旁邊的調色盤圖示,即可從標準的中性/主題重點色樣式,或最近選過的自訂顏色中挑選。 設定此配置的背景圖片設定、調暗、抓取與遮罩選項。 調暗 - 使用滑桿調暗背景圖片。範圍從 0% (不調暗) 到 90% (最大調暗),以保持按鈕清晰可見。 + 使用滑桿調暗背景圖片。範圍從 0% (不調暗) 到 90% (最大調暗),以保持按鈕清晰可見。 裁切與平移 使用拖曳平移與捏合縮放手勢裁切並縮放圖片,使其符合雙螢幕長寬比。 巨集 @@ -806,7 +806,7 @@ 螢幕投影設定 — 說明 這些設定控制投影的上方螢幕圖像在背景模式下的外觀 — 即當 Megingiard 在按鈕後方背景運行時。所有變更僅套用於當前活動的配置。 調暗 - 透過覆蓋一層黑色蒙版來降低投影圖像的亮度。0% 表示不調暗;90% 幾乎為全黑。使用眼睛圖示可在次螢幕上即時預覽效果而不儲存。 + 透過覆蓋一層黑色蒙版來降低投影圖像的亮度。0% 表示不調暗;90% 幾乎為全黑。使用眼睛圖示可在次螢幕上即時預覽效果而不儲存。 邊緣融合 使投影圖像的邊緣向內淡出,讓投影內容與背景平滑過渡而非生硬切斷。較高的值會產生更強的暈影效果。 將投影視窗中心對齊您在主螢幕上最後觸碰的位置。從下拉選單中選擇一個區塊以啟用,或選擇「關閉」以禁用。 diff --git a/docs/features/privileged-mode/FEATURE.md b/docs/features/privileged-mode/FEATURE.md index 2b447ba90..ef05d709e 100644 --- a/docs/features/privileged-mode/FEATURE.md +++ b/docs/features/privileged-mode/FEATURE.md @@ -83,7 +83,7 @@ every device since Android 11 (API 30). - **Stage C (Auto-Pairing / Connection with Stored Credentials)**: If stored credentials exist, the service first attempts to connect and bootstrap the daemon using them once Wireless Debugging is activated. If that connection succeeds, the setup finishes successfully. If the connection fails (or if credentials are not present), the service clears the credentials and proceeds with the pairing dialog flow: routes directly to Developer Options settings via `Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS`, clicks the `"Wireless debugging"` preference row to enter its sub-screen, opens the pairing dialog ("Pair device with pairing code") by locating and clicking the row matching the pairing keywords via view Resource IDs (e.g. `android:id/title`), scans text for the 6-digit code and port via `PrivdPairScreenTextScanner`, and pairs via `PrivdBootstrapper.pair()`. - **Full-Service Auto-Connect & App Restoration**: Upon completing pairing, the service waits for the pairing dialog to dismiss and for `adbd` keys database to stabilize (using a 1500ms delay), rescans the screen for the connect port, and automatically initiates `PrivdManager.connect()` to start the privileged daemon seamlessly. Once the daemon is connected successfully (either via stored credentials or after dynamic pairing), the service retrieves the package name of the application that was running on the top screen prior to auto setup starting (using `AutoSwitchCoordinator.foregroundApp`), and launches it back on the top screen (`Display.DEFAULT_DISPLAY`). If no app was running or it was a system Settings panel, the service automatically falls back to launching the Home launcher/screen on the default display. - **All Set**: If Developer Mode, USB Debugging, Wireless Debugging, and ADB pairing are all active, automatically initiates `PrivdManager.connect()` if disconnected and displays a Toast notification: *"You're all set! Privileged Mode is ready."* - - **Language-Independent Navigation**: By utilizing standardized Android system view Resource IDs (such as `com.android.settings:id/main_switch`, `android:id/switch_widget`, `android:id/title`, and dialog buttons like `android:id/button1`), settings traversal and toggling logic is inherently language-independent. The locale configuration (`AutoSetupLanguageConfig`) is only used for mapping target keyword text checks (e.g. pairing dialog title matches or system warning confirmations) to the active system locale. If a targeted settings element is off-screen (such as the Build number row at the bottom of About Phone), the service MUST recursively find scrollable containers and execute scroll forward actions to bring them into view. + - **Language-Independent Navigation**: By utilizing standardized Android system view Resource IDs (such as `com.android.settings:id/main_switch`, `android:id/switch_widget`, `android:id/title`, and dialog buttons like `android:id/button1`), settings traversal and toggling logic is inherently language-independent. The locale configuration (`AutoSetupLanguageConfig`) is used for mapping target keyword text checks (e.g. pairing dialog title matches or system warning confirmations) across German, Spanish, French, English, and Traditional Chinese (`zh-TW`, `zh-HK`, `zh-MO`) system locales. If a targeted settings element is off-screen (such as the Build number row at the bottom of About Phone), the service MUST recursively find scrollable containers and execute scroll forward actions to bring them into view. - **Network Trust Dialog Auto-Confirmation**: If the system displays a Wireless Debugging or USB Debugging network trust confirmation dialog ("Debugging über WLAN in diesem Netzwerk zulassen?" / "USB-Debugging zulassen?"), the service MUST automatically click the positive action button ("ZULASSEN" / "ALLOW" / "OK") by looking up the resource IDs (e.g. `android:id/button1` or `com.android.settings:id/button1`) while strictly ignoring checkable CheckBox nodes. - Step 5 of the Welcome Tour MUST render a live status checklist displaying stage progress icons (`PENDING`, `ACTIVE`, `DONE`) for Developer Options, Wireless Debugging, ADB Pairing, and Daemon Connection. - If the Accessibility Service is inactive, clicking the button MUST display a helpful Toast notification and launch system Accessibility settings. From 41f253cef30a706dcb4fbc41d8e12ec61babfdce Mon Sep 17 00:00:00 2001 From: yangeric Date: Fri, 14 Aug 2026 21:36:28 +0800 Subject: [PATCH 12/17] feat(i18n): complete Traditional Chinese localization for 0.9.0 and optimize UI terms MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add missing translations for 0.9.0 features (App Launcher, Floating Bubble, Auto Switch off toast) - localize keyboard (Ergo/Full -> 簡易/完整) and touchpad (Mouse/Touch -> 滑鼠/觸控) mode toggles - adapt initial blank profile and layout default name to system locale (預設 / Standard / Default) - optimize drag handle term to Taiwanese standard 拖曳控點 - add unit tests for locale-aware default profile naming --- .../megingiard/macropad/MacroPadState.kt | 18 ++++++++++--- .../megingiard/macropad/MacroPadStateTest.kt | 25 +++++++++++++++++++ .../keyboard/KeyboardBottomToolbar.kt | 4 +-- .../touchpad/FullscreenMouseOverlay.kt | 4 +-- .../ui/src/main/res/values-de/strings.xml | 4 +++ .../ui/src/main/res/values-zh-rTW/strings.xml | 18 +++++++++++-- companion/ui/src/main/res/values/strings.xml | 4 +++ 7 files changed, 67 insertions(+), 10 deletions(-) diff --git a/companion/domain/src/main/java/com/stormpanda/megingiard/macropad/MacroPadState.kt b/companion/domain/src/main/java/com/stormpanda/megingiard/macropad/MacroPadState.kt index cc39b61d9..95ca47170 100644 --- a/companion/domain/src/main/java/com/stormpanda/megingiard/macropad/MacroPadState.kt +++ b/companion/domain/src/main/java/com/stormpanda/megingiard/macropad/MacroPadState.kt @@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn +import java.util.Locale import java.util.UUID private const val TAG = "MacroPadState" @@ -21,6 +22,13 @@ private const val MP_DEFAULT_PROFILE_NAME = "Profile" private const val MP_DEFAULT_LAYOUT_NAME = "Layout" private const val DUPLICATE_BUTTON_OFFSET = 0.05f +private fun defaultInitialName(): String = + when (Locale.getDefault().language.lowercase()) { + "zh" -> "預設" + "de" -> "Standard" + else -> "Default" + } + private fun List.nextUniqueName( baseName: String, fallback: String, @@ -170,13 +178,14 @@ object MacroPadState { needsSave = true val defaultId = UUID.randomUUID().toString() val defaultLayoutId = UUID.randomUUID().toString() + val initialName = defaultInitialName() listOf( PadProfile( id = defaultId, - name = "Default", + name = initialName, layouts = listOf( - PadLayout(id = defaultLayoutId, name = "Default", mirrorCutouts = listOf(ScreenCutout.createDefault())), + PadLayout(id = defaultLayoutId, name = initialName, mirrorCutouts = listOf(ScreenCutout.createDefault())), ), activeLayoutId = defaultLayoutId, ), @@ -337,11 +346,12 @@ object MacroPadState { fun restoreDefaults() { val defaultId = UUID.randomUUID().toString() val defaultLayoutId = UUID.randomUUID().toString() + val initialName = defaultInitialName() val defaultProfile = PadProfile( id = defaultId, - name = "Default", - layouts = listOf(PadLayout(id = defaultLayoutId, name = "Default", mirrorCutouts = listOf(ScreenCutout.createDefault()))), + name = initialName, + layouts = listOf(PadLayout(id = defaultLayoutId, name = initialName, mirrorCutouts = listOf(ScreenCutout.createDefault()))), activeLayoutId = defaultLayoutId, ) _profiles.value = listOf(defaultProfile) diff --git a/companion/domain/src/test/java/com/stormpanda/megingiard/macropad/MacroPadStateTest.kt b/companion/domain/src/test/java/com/stormpanda/megingiard/macropad/MacroPadStateTest.kt index f507c6798..0fd8bba28 100644 --- a/companion/domain/src/test/java/com/stormpanda/megingiard/macropad/MacroPadStateTest.kt +++ b/companion/domain/src/test/java/com/stormpanda/megingiard/macropad/MacroPadStateTest.kt @@ -14,6 +14,7 @@ import org.junit.Assert.assertNotNull import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test +import java.util.Locale import java.util.UUID /** @@ -23,14 +24,18 @@ import java.util.UUID @OptIn(ExperimentalCoroutinesApi::class) class MacroPadStateTest { private val testDispatcher = UnconfinedTestDispatcher() + private lateinit var originalLocale: Locale @Before fun setUp() { + originalLocale = Locale.getDefault() + Locale.setDefault(Locale.US) Dispatchers.setMain(testDispatcher) } @After fun tearDown() { + Locale.setDefault(originalLocale) Dispatchers.resetMain() } @@ -62,6 +67,26 @@ class MacroPadStateTest { assertEquals(defaultLayout, MacroPadState.activeLayout.value) } + @Test + fun `loadFrom with empty list in Traditional Chinese locale generates localized default name`() { + Locale.setDefault(Locale.TRADITIONAL_CHINESE) + MacroPadState.loadFrom(emptyList(), null) + + val defaultProfile = MacroPadState.profiles.value.first() + assertEquals("預設", defaultProfile.name) + assertEquals("預設", defaultProfile.layouts.first().name) + } + + @Test + fun `loadFrom with empty list in German locale generates localized default name`() { + Locale.setDefault(Locale.GERMANY) + MacroPadState.loadFrom(emptyList(), null) + + val defaultProfile = MacroPadState.profiles.value.first() + assertEquals("Standard", defaultProfile.name) + assertEquals("Standard", defaultProfile.layouts.first().name) + } + @Test fun `loadFrom with existing profiles preserves them`() { // Given existing profiles diff --git a/companion/ui/src/main/java/com/stormpanda/megingiard/keyboard/KeyboardBottomToolbar.kt b/companion/ui/src/main/java/com/stormpanda/megingiard/keyboard/KeyboardBottomToolbar.kt index b7c3d4711..1db709d8c 100644 --- a/companion/ui/src/main/java/com/stormpanda/megingiard/keyboard/KeyboardBottomToolbar.kt +++ b/companion/ui/src/main/java/com/stormpanda/megingiard/keyboard/KeyboardBottomToolbar.kt @@ -183,7 +183,7 @@ private fun KeyboardModeToggleButton( horizontalArrangement = Arrangement.Center, ) { Text( - text = "Ergo", + text = stringResource(R.string.keyboard_mode_ergo), color = ergoColor, fontSize = 12.sp, fontWeight = FontWeight.Medium, @@ -216,7 +216,7 @@ private fun KeyboardModeToggleButton( ) Spacer(modifier = Modifier.width(6.dp)) Text( - text = "Full", + text = stringResource(R.string.keyboard_mode_full), color = fullColor, fontSize = 12.sp, fontWeight = FontWeight.Medium, diff --git a/companion/ui/src/main/java/com/stormpanda/megingiard/touchpad/FullscreenMouseOverlay.kt b/companion/ui/src/main/java/com/stormpanda/megingiard/touchpad/FullscreenMouseOverlay.kt index 1982772cb..5a42d44a8 100644 --- a/companion/ui/src/main/java/com/stormpanda/megingiard/touchpad/FullscreenMouseOverlay.kt +++ b/companion/ui/src/main/java/com/stormpanda/megingiard/touchpad/FullscreenMouseOverlay.kt @@ -734,7 +734,7 @@ private fun ModeToggleButton( horizontalArrangement = Arrangement.Center, ) { Text( - text = "Mouse", + text = stringResource(R.string.touchpad_mode_mouse), color = mouseColor, fontSize = 12.sp, fontWeight = FontWeight.Medium, @@ -767,7 +767,7 @@ private fun ModeToggleButton( ) Spacer(modifier = Modifier.width(6.dp)) Text( - text = "Touch", + text = stringResource(R.string.touchpad_mode_touch), color = touchColor, fontSize = 12.sp, fontWeight = FontWeight.Medium, diff --git a/companion/ui/src/main/res/values-de/strings.xml b/companion/ui/src/main/res/values-de/strings.xml index 0bde05b9f..21382a12b 100644 --- a/companion/ui/src/main/res/values-de/strings.xml +++ b/companion/ui/src/main/res/values-de/strings.xml @@ -1157,6 +1157,10 @@ Einstellungen öffnen, um ein Layout zu erstellen. Cover-Artwork ändern + Ergo + Voll + Maus + Touch Touchpad-Einstellungen Relative-Maus-Modus Absolut-Touch-Modus diff --git a/companion/ui/src/main/res/values-zh-rTW/strings.xml b/companion/ui/src/main/res/values-zh-rTW/strings.xml index ed07d5fc1..b71491f0a 100644 --- a/companion/ui/src/main/res/values-zh-rTW/strings.xml +++ b/companion/ui/src/main/res/values-zh-rTW/strings.xml @@ -266,6 +266,15 @@ 觸控投影 全螢幕滑鼠 全螢幕鍵盤 + 啟動應用程式 + 應用程式: %s + 選擇應用程式 + 搜尋應用程式… + 找不到應用程式 + 選擇應用程式… + 開啟 %1$s + Megingiard 浮動球覆蓋層 + 輕觸以返回 Megingiard 活動設定檔 新設定檔… Pad 形狀 @@ -763,8 +772,8 @@ 開啟該巨集的時間軸編輯器,以便您查看或編輯其步驟。 選項選單 每列巨集都有一個選項選單 (三個點),可進行重新命名、重複、複製到設定檔或刪除巨集。刪除是永久性的。 - 拖曳把手 - 長按巨集列右側的拖曳把手並移動,以重新排序列表中的巨集。此處的順序僅供美觀參考,不影響執行。 + 拖曳控點 + 長按巨集列右側的拖曳控點並移動,以重新排列列表中的巨集順序。此處的順序僅供瀏覽參考,不影響執行。 巨集編輯器 — 說明 時間軸編輯器是用來建構巨集執行的確切輸入序列。步驟會依序從上到下執行。時序可以精確錄製或手動輸入。 頂端欄控制 @@ -992,6 +1001,10 @@ 發生非預期的錯誤。請重試。 確定 更換封面圖 + 簡易 + 完整 + 滑鼠 + 觸控 觸控板設定 手勢與點擊設定 絕對觸控模式 @@ -1111,6 +1124,7 @@ 顯示儀表板 切換至遊戲設定檔 自動切換 + 自動切換:關閉 有可用更新 自動檢查更新 在背景定期檢查 GitHub Releases 以取得應用程式更新 diff --git a/companion/ui/src/main/res/values/strings.xml b/companion/ui/src/main/res/values/strings.xml index 7536ab840..6723a92c9 100644 --- a/companion/ui/src/main/res/values/strings.xml +++ b/companion/ui/src/main/res/values/strings.xml @@ -1150,6 +1150,10 @@ Open Settings to create a layout. Change Artwork + Ergo + Full + Mouse + Touch Touchpad Settings Relative Mouse Mode Absolute Touch Mode From 6c587b8736496b7dd7e993ffad0db976f4a1a75a Mon Sep 17 00:00:00 2001 From: yangeric Date: Sat, 15 Aug 2026 14:45:16 +0800 Subject: [PATCH 13/17] =?UTF-8?q?fix(i18n):=20refine=20filled=20icon=20tra?= =?UTF-8?q?nslation=20to=20=E5=AF=A6=E5=BF=83=20in=20Traditional=20Chinese?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- companion/ui/src/main/res/values-zh-rTW/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/companion/ui/src/main/res/values-zh-rTW/strings.xml b/companion/ui/src/main/res/values-zh-rTW/strings.xml index b71491f0a..512538454 100644 --- a/companion/ui/src/main/res/values-zh-rTW/strings.xml +++ b/companion/ui/src/main/res/values-zh-rTW/strings.xml @@ -193,7 +193,7 @@ 選擇圖示 搜尋圖示… 找不到圖示 - 填滿 + 實心 當前選擇 備份與設定檔 應用程式更新 From bc8bad8b08c0a757204784a2aa5fc1122711d8c2 Mon Sep 17 00:00:00 2001 From: yangeric Date: Sat, 15 Aug 2026 16:06:27 +0800 Subject: [PATCH 14/17] =?UTF-8?q?chore(i18n):=20rename=20duplicate=20profi?= =?UTF-8?q?le/layout=20to=20=E8=A4=87=E8=A3=BD=E8=A8=AD=E5=AE=9A=E6=AA=94/?= =?UTF-8?q?=E8=A4=87=E8=A3=BD=E9=85=8D=E7=BD=AE=20in=20zh-rTW?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- companion/ui/src/main/res/values-zh-rTW/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/companion/ui/src/main/res/values-zh-rTW/strings.xml b/companion/ui/src/main/res/values-zh-rTW/strings.xml index 512538454..42ff14d79 100644 --- a/companion/ui/src/main/res/values-zh-rTW/strings.xml +++ b/companion/ui/src/main/res/values-zh-rTW/strings.xml @@ -677,8 +677,8 @@ 選擇目標設定檔 選擇目標配置 重複按鈕 - 重複設定檔 - 重複配置 + 複製設定檔 + 複製配置 沒有其他可用的設定檔。 沒有其他可用的配置。 定義裁切區域 From 2406f1db968ffdfb791452d54f03d7ce004acc06 Mon Sep 17 00:00:00 2001 From: yangeric Date: Sat, 15 Aug 2026 16:51:48 +0800 Subject: [PATCH 15/17] fix(macropad): use quick_menu_edit_layout as EditorTopBar title - align EditorTopBar title with the parent Quick Menu action ('Edit Profile/Layout') - accurately reflect that both Profiles and Layouts are managed within this screen --- .../java/com/stormpanda/megingiard/macropad/MacroPadEditor.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/companion/ui/src/main/java/com/stormpanda/megingiard/macropad/MacroPadEditor.kt b/companion/ui/src/main/java/com/stormpanda/megingiard/macropad/MacroPadEditor.kt index b5821593b..1cf2033f7 100644 --- a/companion/ui/src/main/java/com/stormpanda/megingiard/macropad/MacroPadEditor.kt +++ b/companion/ui/src/main/java/com/stormpanda/megingiard/macropad/MacroPadEditor.kt @@ -727,7 +727,7 @@ private fun EditorTopBar( TopAppBar( title = { Text( - text = stringResource(R.string.macropad_editor_title_edit_profile), + text = stringResource(R.string.quick_menu_edit_layout), color = colors.onSurface, style = MaterialTheme.typography.titleMedium, ) From 0ae5666c93d953f0c29eac171df03731728babdf Mon Sep 17 00:00:00 2001 From: yangeric Date: Sat, 15 Aug 2026 17:24:03 +0800 Subject: [PATCH 16/17] =?UTF-8?q?chore(i18n):=20standardize=20display=20te?= =?UTF-8?q?rminology=20to=20=E4=B8=BB=E8=9E=A2=E5=B9=95/=E5=89=AF=E8=9E=A2?= =?UTF-8?q?=E5=B9=95=20and=20localize=20Hub=20as=20=E5=89=AF=E8=9E=A2?= =?UTF-8?q?=E5=B9=95=E8=BC=94=E5=8A=A9=E4=B8=AD=E5=BF=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - standardize screen terminology across all Traditional Chinese strings to strictly use 主螢幕 and 副螢幕 - eliminate ambiguous references (次螢幕, 底部螢幕, 上方螢幕, 上螢幕, 下方螢幕) - localize Companion Hub to 副螢幕輔助中心 to align with Taiwanese UX conventions and establish clear hierarchy with 副螢幕工具 - escape XML ampersand in Thor settings deadzone dialog hint --- .../ui/src/main/res/values-zh-rTW/strings.xml | 46 +++++++++---------- .../ui/src/main/res/values-zh-rTW/strings.xml | 2 +- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/companion/ui/src/main/res/values-zh-rTW/strings.xml b/companion/ui/src/main/res/values-zh-rTW/strings.xml index 42ff14d79..de964f4ca 100644 --- a/companion/ui/src/main/res/values-zh-rTW/strings.xml +++ b/companion/ui/src/main/res/values-zh-rTW/strings.xml @@ -5,8 +5,8 @@ 搜尋應用程式與遊戲… 找不到應用程式 正在等待螢幕投影授權\u2026 - Megingiard 必須在底部螢幕運行。\n請移動應用程式以繼續。 - 指向底部螢幕的箭頭 + Megingiard 必須在副螢幕運行。\n請移動應用程式以繼續。 + 指向副螢幕的箭頭 開始投影 上一個工具 下一個工具 @@ -395,7 +395,7 @@ 自動填入(讀取螢幕) 正在掃描螢幕… 已自動填入配對資訊! - 無法在螢幕上偵測到完整的配對資訊。請確認上螢幕已開啟無線偵錯選單或「配對裝置」對話視窗。 + 無法在螢幕上偵測到完整的配對資訊。請確認主螢幕已開啟無線偵錯選單或「配對裝置」對話視窗。 請在系統設定中啟用無障礙服務,才能透過讀取螢幕文字自動填入。 Megingiard 現在將推送助手 daemon 執行檔到 /data/local/tmp 並啟動它。Daemon 以 ADB shell 使用者身份運行,直到您重啟設備為止。 推送並啟動 daemon @@ -443,8 +443,8 @@ 此名稱已存在。 重試偵測 設定與放置說明 - 1. 在次螢幕重新開啟 Megingiard,然後點擊「重試」。 - 2. 建議:如果您的啟動器支援,請將 Megingiard 固定在底部螢幕以保持其位置。 + 1. 在副螢幕重新開啟 Megingiard,然後點擊「重試」。 + 2. 建議:如果您的啟動器支援,請將 Megingiard 固定在副螢幕以保持其位置。 3. 焦點模式:由於 Megingiard 僅使用觸控控制,請在 AYN 設定中將焦點模式設為「主螢幕」。 開始 停止 @@ -558,7 +558,7 @@ 刪除步驟 從巨集中移除此步驟? 錄製手把輸入 - 觸碰螢幕上的控制器。輸入將被錄製並即時轉發到上方螢幕。 + 觸碰螢幕上的控制器。輸入將被錄製並即時轉發到主螢幕。 完成 取消 左搖桿 @@ -570,7 +570,7 @@ ST 開始 開始手把錄製 - 此螢幕上將開啟觸控手把覆蓋層。您的觸碰將被錄製為巨集步驟,並透過虛擬手把即時轉發到上方螢幕。 + 此螢幕上將開啟觸控手把覆蓋層。您的觸碰將被錄製為巨集步驟,並透過虛擬手把即時轉發到主螢幕。 不再顯示 手把錄製不可用 無法啟動虛擬手把。錄製未開始。 @@ -653,7 +653,7 @@ 上一個配置 下一個配置 編輯視窗 - 點擊此處控制上方螢幕 + 點擊此處控制主螢幕 拖曳移動 · 輕點點擊 鍵盤 手把 @@ -814,9 +814,9 @@ 隨機化時序 每次執行時為每個步驟的時序新增一個微小的隨機偏移 (±範圍)。這使巨集感覺不那麼機械化,並有助於避免某些應用程式中的自動偵測模式。 螢幕投影設定 — 說明 - 這些設定控制投影的上方螢幕圖像在背景模式下的外觀 — 即當 Megingiard 在按鈕後方背景運行時。所有變更僅套用於當前活動的配置。 + 這些設定控制投影的主螢幕圖像在背景模式下的外觀 — 即當 Megingiard 在按鈕後方背景運行時。所有變更僅套用於當前活動的配置。 調暗 - 透過覆蓋一層黑色蒙版來降低投影圖像的亮度。0% 表示不調暗;90% 幾乎為全黑。使用眼睛圖示可在次螢幕上即時預覽效果而不儲存。 + 透過覆蓋一層黑色蒙版來降低投影圖像的亮度。0% 表示不調暗;90% 幾乎為全黑。使用眼睛圖示可在副螢幕上即時預覽效果而不儲存。 邊緣融合 使投影圖像的邊緣向內淡出,讓投影內容與背景平滑過渡而非生硬切斷。較高的值會產生更強的暈影效果。 將投影視窗中心對齊您在主螢幕上最後觸碰的位置。從下拉選單中選擇一個區塊以啟用,或選擇「關閉」以禁用。 @@ -826,14 +826,14 @@ 套用運動平滑於區塊平移 (關閉、輕微、中等或強烈)。較高的值會使視窗移動在平移過程中更加平滑,但可能會增加極少量的延遲。 將投影區塊區域上的觸控輸入轉換回主螢幕。啟用後,區塊內的觸碰會觸發主螢幕上相應的動作。 預覽 - 在次螢幕上啟用該滑桿值的即時預覽。與次螢幕的控制項互動以確認或取消該值。這讓您無需離開當前頁面即可看到效果。 + 在副螢幕上啟用該滑桿值的即時預覽。與副螢幕的控制項互動以確認或取消該值。這讓您無需離開當前頁面即可看到效果。 投影編輯器 — 說明 - 投影編輯器讓您在投影過程中,於次螢幕上新增、定位、調整大小與設定視窗區塊。點擊螢幕上的區塊即可選取,然後使用下方的工具列按鈕進行操作。 + 投影編輯器讓您在投影過程中,於副螢幕上新增、定位、調整大小與設定視窗區塊。點擊螢幕上的區塊即可選取,然後使用下方的工具列按鈕進行操作。 全域動作 - 在次螢幕的空閒位置建立新的視窗區塊。每個配置最多允許 10 個區塊。新區塊會自動被選取。 + 在副螢幕的空閒位置建立新的視窗區塊。每個配置最多允許 10 個區塊。新區塊會自動被選取。 開啟該配置的環境顯示設定(亮度、邊緣融合、平滑度)。變更僅套用於目前使用中的配置。 所選區塊 - 為所選區塊循環切換三種長寬比模式。「自由」— 無限制,可獨立調整大小。「頂部」— 鎖定長寬比以匹配主螢幕的裁切。「底部」— 鎖定長寬比以匹配次螢幕的區塊。 + 為所選區塊循環切換三種長寬比模式。「自由」— 無限制,可獨立調整大小。「主螢幕」— 鎖定長寬比以匹配主螢幕的裁切。「副螢幕」— 鎖定長寬比以匹配副螢幕的區塊。 形狀 在矩形與圓形之間切換所選區塊的形狀。適用於遊戲小地圖或圓形儀表,圓形裁切外觀更佳。 開啟所選區塊的裁切選擇器,讓您在主螢幕拖曳框架以選擇投影到此區塊的區域。 @@ -874,7 +874,7 @@ 開始或停止螢幕投影擷取。投影進行時,頂端卡片會展開以顯示更多控制項。 凍結 / 解除凍結 凍結投影影像,使其顯示靜態畫面而非即時畫面。適合在您需要仔細閱讀遊戲畫面時暫停。 - 在次螢幕開啟區塊配置編輯器,讓您在投影覆蓋層中調整大小、移動或新增透明區塊視窗。 + 在副螢幕開啟區塊配置編輯器,讓您在投影覆蓋層中調整大小、移動或新增透明區塊視窗。 擷取螢幕截圖 擷取主螢幕的截圖。若未在投影中,則需要特權模式已連接。 MacroPad 控制項 (底端卡片) @@ -882,18 +882,18 @@ 點擊標籤即可切換到目前設定檔中的不同按鈕配置。此處只會顯示在編輯器中標記為啟用的配置。 自動切換 切換動態設定檔自動比對開啟/關閉。開啟時,設定檔與副螢幕檢視將根據當前活動遊戲或模擬器自動切換。關閉時,設定檔自動比對將會暫停。 - 切換至 Hub / 遊戲設定檔 - 在 Companion Hub 儀表板與當前活動遊戲設定檔版面之間手動切換(會停用自動切換)。 + 切換至輔助中心/遊戲設定檔 + 在副螢幕輔助中心與當前活動遊戲設定檔配置之間手動切換(會停用自動切換)。 開啟「編輯設定檔/配置」畫面,您可以在此新增、移動與設定按鈕,管理設定檔與配置,以及建立或編輯巨集。 開啟全域設定視窗以進行 App 細部配置,例如主題、主題重點色、鏡像自動啟動與影音模式設定。 關閉 App 正常關閉 Megingiard,並停止螢幕投影與背景服務。 說明 (此螢幕) 開啟此說明工作表,其中包含快速選單中每個控制項的解釋。 - Companion Hub — 說明 - Companion Hub 畫面是您在副螢幕上的中央控制儀表板,用於管理特定遊戲的巨集控制、查看活動遊戲狀態,以及快速存取副螢幕工具。 + 副螢幕輔助中心 — 說明 + 副螢幕輔助中心是您在副螢幕上的中央控制儀表板,用於管理特定遊戲的巨集控制、查看活動遊戲狀態,以及快速存取副螢幕工具。 活動遊戲與控制設定檔 - 在下方螢幕全螢幕開啟當前活動遊戲的 MacroPad 觸控版面。 + 在副螢幕全螢幕開啟當前活動遊戲的 MacroPad 觸控配置。 開啟設定檔與版面編輯器,以自訂按鈕位置、樣式及巨集設定。 將自訂 MacroPad 控制設定檔連結至當前活動或選取的遊戲,或為其建立專屬設定檔。 副螢幕工具 @@ -1106,13 +1106,13 @@ 預設 顯示 Megingiard Companion Hub - 請在上螢幕選擇遊戲,或在下方啟動副螢幕工具。 + 請在主螢幕選擇遊戲,或在此啟動副螢幕工具。 顯示 副螢幕工具 手把與巨集 滑鼠 / 觸控投影 虛擬鍵盤 - 上螢幕鏡射 + 主螢幕投影 全域設定 說明與教學 當前設定檔 @@ -1140,6 +1140,6 @@ 備份並開啟連結 直接開啟連結 取消 - 啟用時,Megingiard 會每 24 小時靜默檢查一次新版本。若發現更新,您可以備份設定並在上方螢幕的預設瀏覽器中開啟發布頁面。 + 啟用時,Megingiard 會每 24 小時靜默檢查一次新版本。若發現更新,您可以備份設定並在主螢幕的預設瀏覽器中開啟發布頁面。 立即手動觸發對 GitHub Releases 儲存庫的更新檢查。 diff --git a/gamefocus/ui/src/main/res/values-zh-rTW/strings.xml b/gamefocus/ui/src/main/res/values-zh-rTW/strings.xml index 94e7c4376..0473c79d9 100644 --- a/gamefocus/ui/src/main/res/values-zh-rTW/strings.xml +++ b/gamefocus/ui/src/main/res/values-zh-rTW/strings.xml @@ -9,7 +9,7 @@ 取消 主螢幕 - 底部螢幕 + 副螢幕 Android 遊戲 Android 應用程式 所有應用程式 From 3fc95c4d35f754a59ba9d7d0d0c208b03a003749 Mon Sep 17 00:00:00 2001 From: yangeric Date: Sat, 15 Aug 2026 17:25:55 +0800 Subject: [PATCH 17/17] =?UTF-8?q?chore(i18n):=20translate=20idle=20hero=20?= =?UTF-8?q?title=20to=20Megingiard=20=E5=89=AF=E8=9E=A2=E5=B9=95=E8=BC=94?= =?UTF-8?q?=E5=8A=A9=E4=B8=AD=E5=BF=83=20and=20switch=20button=20to=20?= =?UTF-8?q?=E5=88=87=E6=8F=9B=E8=87=B3=E8=BC=94=E5=8A=A9=E4=B8=AD=E5=BF=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- companion/ui/src/main/res/values-zh-rTW/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/companion/ui/src/main/res/values-zh-rTW/strings.xml b/companion/ui/src/main/res/values-zh-rTW/strings.xml index de964f4ca..4a13ef79e 100644 --- a/companion/ui/src/main/res/values-zh-rTW/strings.xml +++ b/companion/ui/src/main/res/values-zh-rTW/strings.xml @@ -1105,7 +1105,7 @@ 新增設定檔 預設 顯示 - Megingiard Companion Hub + Megingiard 副螢幕輔助中心 請在主螢幕選擇遊戲,或在此啟動副螢幕工具。 顯示 副螢幕工具 @@ -1121,7 +1121,7 @@ 全域設定 應用程式偏好設定 螢幕鏡射 - 顯示儀表板 + 切換至輔助中心 切換至遊戲設定檔 自動切換 自動切換:關閉