diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..dd5fb4d --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,40 @@ +# CodeRabbit AI 代码审查配置 +# 文档: https://docs.coderabbit.ai/reference/configuration + +language: "zh-CN" +tone_instructions: "使用简洁的中文进行代码审查,关注实际问题而非风格偏好。" +early_access: true + +reviews: + profile: "chill" + high_level_summary: true + poem: false + sequence_diagrams: false + collapse_walkthrough: true + changed_files_summary: true + path_filters: + - "!build/**" + - "!oh_modules/**" + - "!entry/oh_modules/**" + - "!entry/build/**" + - "!nativelib/build/**" + - "!hvigor/**" + - "!oh-package-lock.json5" + - "!entry/oh-package-lock.json5" + - "!store-assets/**" + - "!nativelib/src/main/cpp/moonlight-common-c/**" + - "!nativelib/src/main/cpp/ohos-openssl/**" + path_instructions: + - path: "entry/src/main/ets/**" + instructions: | + 这是 HarmonyOS ArkTS 前端代码。使用 ArkUI 声明式 UI 框架,@Component/@State/@Prop/@Link 等装饰器。 + 关注线程安全(UI 线程 vs Worker)、内存泄漏(定时器/事件监听未清理)、空指针安全。 + - path: "nativelib/src/main/cpp/**" + instructions: | + C/C++ 原生层代码,通过 N-API 与 ArkTS 交互。关注内存管理、线程安全、JNI/N-API 生命周期。 + auto_review: + enabled: true + drafts: false + +chat: + auto_reply: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6c6b49a..21b6606 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -301,7 +301,7 @@ jobs: ci/patch-sdk.sh ~/ohos-sdk # ── 构建 ── - - name: Build HAP + - name: Build Native run: | set -o pipefail BUILD_MODE="${{ steps.build-mode.outputs.mode }}" @@ -310,58 +310,20 @@ jobs: export JAVA_HOME="${JAVA_HOME_17_X64}" export LD_LIBRARY_PATH="${HOME}/ohos-sdk/toolchains/lib:${HOME}/ohos-sdk/previewer/common/bin:${LD_LIBRARY_PATH:-}" - node hvigorw.js assembleHap \ + node hvigorw.js assembleHar \ --mode module \ + -p module=nativelib@default \ -p product=default \ -p buildMode="${BUILD_MODE}" \ --no-daemon \ --stacktrace \ 2>&1 | tee build-hap.log - find . -name "*.hap" -type f -exec sh -c 'echo " $(du -h "$1" | cut -f1) $1"' _ {} \; - - - name: Build APP (signed) - if: steps.signing.outputs.available == 'true' - run: | - set -o pipefail - BUILD_MODE="${{ steps.build-mode.outputs.mode }}" - export NODE_HOME="$(dirname "$(dirname "$(which node)")")" - export JAVA_HOME="${JAVA_HOME_17_X64}" - - node hvigorw.js assembleApp \ - --mode module \ - -p product=default \ - -p buildMode="${BUILD_MODE}" \ - --no-daemon \ - 2>&1 | tee build-app.log - - find . -name "*.app" -type f -exec sh -c 'echo " $(du -h "$1" | cut -f1) $1"' _ {} \; - # ── 清理 & 上传 ── - name: Cleanup signing files if: always() run: rm -rf .signing/ - - name: Upload HAP artifact - uses: actions/upload-artifact@v4 - if: always() - with: - name: moonlight-hap-${{ steps.version.outputs.version }}-${{ steps.build-mode.outputs.mode }} - path: entry/build/default/outputs/**/*.hap - if-no-files-found: warn - retention-days: 30 - - - name: Upload APP artifact - uses: actions/upload-artifact@v4 - if: steps.signing.outputs.available == 'true' - with: - name: moonlight-app-${{ steps.version.outputs.version }}-${{ steps.build-mode.outputs.mode }} - path: | - entry/build/default/outputs/**/*.app - build/default/outputs/**/*.app - if-no-files-found: warn - retention-days: 30 - - name: Upload build logs on failure uses: actions/upload-artifact@v4 if: failure() @@ -369,35 +331,6 @@ jobs: name: build-logs-${{ github.run_id }} path: | build-hap.log - build-app.log retention-days: 7 - # ── Release ── - release: - name: Create Release - needs: build - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/v') - permissions: - contents: write - - steps: - - name: Download artifacts - uses: actions/download-artifact@v4 - with: - pattern: moonlight-* - path: ./artifacts - merge-multiple: true - - - name: Create GitHub Release - uses: softprops/action-gh-release@v2 - with: - files: | - ./artifacts/**/*.hap - ./artifacts/**/*.app - draft: false - prerelease: ${{ contains(github.ref, 'beta') || contains(github.ref, 'alpha') || contains(github.ref, 'rc') }} - generate_release_notes: true - fail_on_unmatched_files: false - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # Release 由本地手动构建签名后上传 diff --git a/ci/patch-sdk.sh b/ci/patch-sdk.sh index 09fa7c0..879da28 100644 --- a/ci/patch-sdk.sh +++ b/ci/patch-sdk.sh @@ -116,4 +116,27 @@ if [ -n "$DISPLAY_DTS" ] && ! grep -q "getBrightnessInfo" "$DISPLAY_DTS"; then echo " Patched display module" fi +# ─── @kit.ScanKit ─── +[ ! -f "$KIT_CONFIGS/@kit.ScanKit.json" ] && \ + cp "$STUBS_DIR/kit.ScanKit.json" "$KIT_CONFIGS/@kit.ScanKit.json" +cp "$STUBS_DIR/kit.ScanKit.d.ts" "$ETS_API/@kit.ScanKit.d.ts" +cp "$STUBS_DIR/ohos.scan.scanCore.d.ts" "$ETS_API/@ohos.scan.scanCore.d.ts" +cp "$STUBS_DIR/ohos.scan.scanBarcode.d.ts" "$ETS_API/@ohos.scan.scanBarcode.d.ts" +echo " Applied ScanKit stubs" + +# ─── @kit.ShareKit ─── +[ ! -f "$KIT_CONFIGS/@kit.ShareKit.json" ] && \ + cp "$STUBS_DIR/kit.ShareKit.json" "$KIT_CONFIGS/@kit.ShareKit.json" +cp "$STUBS_DIR/kit.ShareKit.d.ts" "$ETS_API/@kit.ShareKit.d.ts" +cp "$STUBS_DIR/ohos.share.systemShare.d.ts" "$ETS_API/@ohos.share.systemShare.d.ts" +echo " Applied ShareKit stubs" + +# ─── DevKeySecret (CI-only placeholder) ─── +DEV_KEY_SECRET="entry/src/main/ets/config/DevKeySecret.ets" +if [ ! -f "$DEV_KEY_SECRET" ]; then + mkdir -p "$(dirname "$DEV_KEY_SECRET")" + cp "${DEV_KEY_SECRET}.example" "$DEV_KEY_SECRET" + echo " Created DevKeySecret from example" +fi + echo "✅ SDK patches applied" diff --git a/ci/sdk-stubs/kit.ScanKit.d.ts b/ci/sdk-stubs/kit.ScanKit.d.ts new file mode 100644 index 0000000..3ae5b5d --- /dev/null +++ b/ci/sdk-stubs/kit.ScanKit.d.ts @@ -0,0 +1,22 @@ +declare module '@kit.ScanKit' { + namespace scanCore { + enum ScanType { + QR_CODE = 0, + BARCODE_TYPE_EAN_13 = 1, + BARCODE_TYPE_EAN_8 = 2, + BARCODE_TYPE_CODE_128 = 3, + } + } + namespace scanBarcode { + interface ScanOptions { + scanTypes?: scanCore.ScanType[]; + enableMultiMode?: boolean; + enableAlbum?: boolean; + } + interface ScanResult { + originalValue?: string; + scanType?: number; + } + function startScanForResult(context: object, options?: ScanOptions): Promise; + } +} diff --git a/ci/sdk-stubs/kit.ScanKit.json b/ci/sdk-stubs/kit.ScanKit.json new file mode 100644 index 0000000..cc519f4 --- /dev/null +++ b/ci/sdk-stubs/kit.ScanKit.json @@ -0,0 +1,12 @@ +{ + "symbols": { + "scanCore": { + "source": "@ohos.scan.scanCore.d.ts", + "bindings": "default" + }, + "scanBarcode": { + "source": "@ohos.scan.scanBarcode.d.ts", + "bindings": "default" + } + } +} diff --git a/ci/sdk-stubs/kit.ShareKit.d.ts b/ci/sdk-stubs/kit.ShareKit.d.ts new file mode 100644 index 0000000..036ea0b --- /dev/null +++ b/ci/sdk-stubs/kit.ShareKit.d.ts @@ -0,0 +1,33 @@ +declare module '@kit.ShareKit' { + namespace systemShare { + enum SelectionMode { + SINGLE = 0, + BATCH = 1, + } + enum SharePreviewMode { + DEFAULT = 0, + DETAIL = 1, + } + interface SharedDataRecord { + utd: string; + content?: string; + uri?: string; + data?: ArrayBuffer; + title?: string; + description?: string; + thumbnail?: object; + } + class SharedData { + constructor(record: SharedDataRecord); + addRecord(record: SharedDataRecord): void; + } + interface ShareControllerOptions { + selectionMode?: SelectionMode; + previewMode?: SharePreviewMode; + } + class ShareController { + constructor(data: SharedData); + show(context: object, options?: ShareControllerOptions): Promise; + } + } +} diff --git a/ci/sdk-stubs/kit.ShareKit.json b/ci/sdk-stubs/kit.ShareKit.json new file mode 100644 index 0000000..de7fab4 --- /dev/null +++ b/ci/sdk-stubs/kit.ShareKit.json @@ -0,0 +1,8 @@ +{ + "symbols": { + "systemShare": { + "source": "@ohos.share.systemShare.d.ts", + "bindings": "default" + } + } +} diff --git a/ci/sdk-stubs/ohos.scan.scanBarcode.d.ts b/ci/sdk-stubs/ohos.scan.scanBarcode.d.ts new file mode 100644 index 0000000..dedd158 --- /dev/null +++ b/ci/sdk-stubs/ohos.scan.scanBarcode.d.ts @@ -0,0 +1,13 @@ +declare namespace scanBarcode { + interface ScanOptions { + scanTypes?: number[]; + enableMultiMode?: boolean; + enableAlbum?: boolean; + } + interface ScanResult { + originalValue?: string; + scanType?: number; + } + function startScanForResult(context: object, options?: ScanOptions): Promise; +} +export default scanBarcode; diff --git a/ci/sdk-stubs/ohos.scan.scanCore.d.ts b/ci/sdk-stubs/ohos.scan.scanCore.d.ts new file mode 100644 index 0000000..59f370e --- /dev/null +++ b/ci/sdk-stubs/ohos.scan.scanCore.d.ts @@ -0,0 +1,9 @@ +declare namespace scanCore { + enum ScanType { + QR_CODE = 0, + BARCODE_TYPE_EAN_13 = 1, + BARCODE_TYPE_EAN_8 = 2, + BARCODE_TYPE_CODE_128 = 3, + } +} +export default scanCore; diff --git a/ci/sdk-stubs/ohos.share.systemShare.d.ts b/ci/sdk-stubs/ohos.share.systemShare.d.ts new file mode 100644 index 0000000..b03265c --- /dev/null +++ b/ci/sdk-stubs/ohos.share.systemShare.d.ts @@ -0,0 +1,32 @@ +declare namespace systemShare { + enum SelectionMode { + SINGLE = 0, + BATCH = 1, + } + enum SharePreviewMode { + DEFAULT = 0, + DETAIL = 1, + } + interface SharedDataRecord { + utd: string; + content?: string; + uri?: string; + data?: ArrayBuffer; + title?: string; + description?: string; + thumbnail?: object; + } + class SharedData { + constructor(record: SharedDataRecord); + addRecord(record: SharedDataRecord): void; + } + interface ShareControllerOptions { + selectionMode?: SelectionMode; + previewMode?: SharePreviewMode; + } + class ShareController { + constructor(data: SharedData); + show(context: object, options?: ShareControllerOptions): Promise; + } +} +export default systemShare; diff --git a/entry/src/main/ets/components/PerformanceOverlay.ets b/entry/src/main/ets/components/PerformanceOverlay.ets index fa66a9d..9ddd923 100644 --- a/entry/src/main/ets/components/PerformanceOverlay.ets +++ b/entry/src/main/ets/components/PerformanceOverlay.ets @@ -22,7 +22,8 @@ import { getMoonPhaseIcon, formatDataRate, getDecoderString, getPacketLossColor, getLatencyColor, getBatteryColor, calculateSnapPosition, findNearestSnapPosition, - getPerformanceItemInfo, getSnapBorderRadius, SnapPosition + getPerformanceItemInfo, getSnapBorderRadius, SnapPosition, + isAprilFools } from './PerformanceOverlayManager'; import { StreamViewModel } from '../viewmodel/StreamViewModel'; import { PreferencesUtil } from '../utils/PreferencesUtil'; @@ -60,6 +61,7 @@ export struct PerformanceOverlay { @State perfBatteryLevel: number = 100; @State perfIsCharging: boolean = false; @State perfUpscaleMode: number = 0; + private aprilFoolsToastShown: boolean = false; // 覆盖层状态 @State overlayX: number = 8; @@ -191,6 +193,26 @@ export struct PerformanceOverlay { this.perfHostLatency = stats.hostLatency; this.perfPacketLoss = stats.packetLoss; + // 🎃 愚人节彩蛋:负延迟串流™ + if (isAprilFools()) { + if (stats.latency > 0) { + this.perfLatency = -(stats.latency * (0.3 + Math.random() * 0.4)); + } + if (stats.networkLatency > 0) { + this.perfNetworkLatency = -Math.ceil(stats.networkLatency * (0.2 + Math.random() * 0.3)); + } + if (stats.hostLatency > 0) { + this.perfHostLatency = -(stats.hostLatency * (0.3 + Math.random() * 0.3)); + } + if (!this.aprilFoolsToastShown) { + this.aprilFoolsToastShown = true; + promptAction.showToast({ + message: '🔮 负延迟串流™ 已激活\n画面将在你操作之前到达', + duration: 5000 + }); + } + } + // 延迟查询超分引擎(解码器初始化后才有值) if (this.perfUpscaleMode === 0) { const mode = session.getActiveUpscaleMode(); @@ -558,11 +580,12 @@ export struct PerformanceOverlay { // 网络延迟行 if (this.enabledItemsList.includes(PerformanceItem.NETWORK_LATENCY)) { Row() { - this.PerfIcon(PerfSymbols.NETWORK, 14, this.perfNetworkLatency > 50 ? '#FFA726' : '#4CAF50') - Text(this.perfNetworkLatency > 0 ? ` ${this.perfNetworkLatency}` : ' N/A') + this.PerfIcon(PerfSymbols.NETWORK, 14, + this.perfNetworkLatency < 0 ? '#64FFDA' : (this.perfNetworkLatency > 50 ? '#FFA726' : '#4CAF50')) + Text(this.perfNetworkLatency !== 0 ? ` ${this.perfNetworkLatency}` : ' N/A') .fontSize(12) .fontWeight(FontWeight.Medium) - .fontColor(this.perfNetworkLatency > 50 ? '#FFA726' : '#4CAF50') + .fontColor(this.perfNetworkLatency < 0 ? '#64FFDA' : (this.perfNetworkLatency > 50 ? '#FFA726' : '#4CAF50')) Text(' ms') .fontSize(11) .fontColor('#CCFFFFFF') @@ -575,6 +598,8 @@ export struct PerformanceOverlay { Row() { if (this.perfLatency >= 20) { Text('🔥').fontSize(13) + } else if (this.perfLatency < 0) { + Text('❄️').fontSize(13) } else { this.PerfIcon(PerfSymbols.DECODE_NORMAL, 14, getLatencyColor(this.perfLatency)) } @@ -592,11 +617,11 @@ export struct PerformanceOverlay { // 主机延迟行 if (this.enabledItemsList.includes(PerformanceItem.HOST_LATENCY)) { Row() { - this.PerfIcon(PerfSymbols.HOST, 14, '#009688') - Text(this.perfHostLatency > 0 ? ` ${this.perfHostLatency.toFixed(1)}` : ' N/A') + this.PerfIcon(PerfSymbols.HOST, 14, this.perfHostLatency < 0 ? '#64FFDA' : '#009688') + Text(this.perfHostLatency !== 0 ? ` ${this.perfHostLatency.toFixed(1)}` : ' N/A') .fontSize(12) .fontWeight(FontWeight.Medium) - .fontColor('#009688') + .fontColor(this.perfHostLatency < 0 ? '#64FFDA' : '#009688') Text(' ms') .fontSize(11) .fontColor('#CCFFFFFF') @@ -689,11 +714,12 @@ export struct PerformanceOverlay { // 网络延迟 if (this.enabledItemsList.includes(PerformanceItem.NETWORK_LATENCY)) { - this.PerfIcon(PerfSymbols.NETWORK, 13, this.perfNetworkLatency > 50 ? '#FFA726' : '#4CAF50') - Text(this.perfNetworkLatency > 0 ? ` ${this.perfNetworkLatency} ms` : ' N/A') + this.PerfIcon(PerfSymbols.NETWORK, 13, + this.perfNetworkLatency < 0 ? '#64FFDA' : (this.perfNetworkLatency > 50 ? '#FFA726' : '#4CAF50')) + Text(this.perfNetworkLatency !== 0 ? ` ${this.perfNetworkLatency} ms` : ' N/A') .fontSize(11) .fontWeight(FontWeight.Medium) - .fontColor(this.perfNetworkLatency > 50 ? '#FFA726' : '#4CAF50') + .fontColor(this.perfNetworkLatency < 0 ? '#64FFDA' : (this.perfNetworkLatency > 50 ? '#FFA726' : '#4CAF50')) if (this.hasNextItem(PerformanceItem.NETWORK_LATENCY)) this.Separator() } @@ -701,6 +727,8 @@ export struct PerformanceOverlay { if (this.enabledItemsList.includes(PerformanceItem.DECODE_LATENCY)) { if (this.perfLatency >= 20) { Text('🥵').fontSize(12) + } else if (this.perfLatency < 0) { + Text('❄️').fontSize(12) } else { this.PerfIcon(PerfSymbols.DECODE_NORMAL, 13, getLatencyColor(this.perfLatency)) } @@ -713,11 +741,11 @@ export struct PerformanceOverlay { // 主机延迟 if (this.enabledItemsList.includes(PerformanceItem.HOST_LATENCY)) { - this.PerfIcon(PerfSymbols.HOST, 13, '#009688') - Text(this.perfHostLatency > 0 ? ` ${this.perfHostLatency.toFixed(1)} ms` : ' N/A') + this.PerfIcon(PerfSymbols.HOST, 13, this.perfHostLatency < 0 ? '#64FFDA' : '#009688') + Text(this.perfHostLatency !== 0 ? ` ${this.perfHostLatency.toFixed(1)} ms` : ' N/A') .fontSize(11) .fontWeight(FontWeight.Medium) - .fontColor('#009688') + .fontColor(this.perfHostLatency < 0 ? '#64FFDA' : '#009688') if (this.hasNextItem(PerformanceItem.HOST_LATENCY)) this.Separator() } diff --git a/entry/src/main/ets/components/PerformanceOverlayManager.ets b/entry/src/main/ets/components/PerformanceOverlayManager.ets index 8b4f6ad..e2b9a00 100644 --- a/entry/src/main/ets/components/PerformanceOverlayManager.ets +++ b/entry/src/main/ets/components/PerformanceOverlayManager.ets @@ -270,11 +270,20 @@ export function getPacketLossColor(loss: number): string { * 获取延迟颜色 */ export function getLatencyColor(ms: number): string { + if (ms < 0) return '#64FFDA'; // 青色 - 负延迟™ if (ms < 10) return '#7D9D7D'; // 绿色 - 优秀 if (ms < 20) return '#D597E3'; // 紫色 - 正常 return '#B57D7D'; // 红色 - 差 } +/** + * 愚人节彩蛋:负延迟串流™ + */ +export function isAprilFools(): boolean { + const now = new Date(); + return now.getMonth() === 3 && now.getDate() === 1; +} + /** * 获取电池颜色 */ diff --git a/entry/src/main/ets/pages/SettingsPageV2.ets b/entry/src/main/ets/pages/SettingsPageV2.ets index 9b40ca5..226de07 100644 --- a/entry/src/main/ets/pages/SettingsPageV2.ets +++ b/entry/src/main/ets/pages/SettingsPageV2.ets @@ -2176,7 +2176,11 @@ struct SettingsPageV2 { title: '版本', subtitle: this.appVersion, type: 'action', - action: () => {} + action: () => { + const pasteData = pasteboard.createData(pasteboard.MIMETYPE_TEXT_PLAIN, this.appVersion); + pasteboard.getSystemPasteboard().setDataSync(pasteData); + this.showToast(`已复制版本号: ${this.appVersion}`); + } }, { title: '更新日志', @@ -3590,7 +3594,7 @@ struct SettingsPageV2 { if (option.value === 'none') { bgUtil.disableBackgroundImage(); } - this.showToast(`背景图片设置为: ${option.title}`); + this.showToast(`背景图片设置为: ${option.title},重启应用后生效`); } }; this.showPicker(config); diff --git a/nativelib/src/main/cpp/game_controller_native.cpp b/nativelib/src/main/cpp/game_controller_native.cpp index fd4b50a..9637ae3 100644 --- a/nativelib/src/main/cpp/game_controller_native.cpp +++ b/nativelib/src/main/cpp/game_controller_native.cpp @@ -359,6 +359,25 @@ static bool TryLoadGameControllerLib() { g_gcLibAvailable = false; return false; } + + // 验证所有按键/轴监听函数是否加载成功 + int missingCount = 0; + #define CHECK_FUNC(name) if (!pfn_##name) { LOGW("GCK: 缺少符号 " #name); missingCount++; } + CHECK_FUNC(OH_GamePad_ButtonA_RegisterButtonInputMonitor) + CHECK_FUNC(OH_GamePad_ButtonB_RegisterButtonInputMonitor) + CHECK_FUNC(OH_GamePad_ButtonX_RegisterButtonInputMonitor) + CHECK_FUNC(OH_GamePad_ButtonY_RegisterButtonInputMonitor) + CHECK_FUNC(OH_GamePad_LeftShoulder_RegisterButtonInputMonitor) + CHECK_FUNC(OH_GamePad_RightShoulder_RegisterButtonInputMonitor) + CHECK_FUNC(OH_GamePad_LeftThumbstick_RegisterAxisInputMonitor) + CHECK_FUNC(OH_GamePad_RightThumbstick_RegisterAxisInputMonitor) + CHECK_FUNC(OH_GamePad_Dpad_RegisterAxisInputMonitor) + CHECK_FUNC(OH_GamePad_LeftTrigger_RegisterAxisInputMonitor) + CHECK_FUNC(OH_GamePad_RightTrigger_RegisterAxisInputMonitor) + #undef CHECK_FUNC + if (missingCount > 0) { + LOGW("Game Controller Kit: %d 个函数符号缺失,监听可能不完整", missingCount); + } #endif LOGI("Game Controller Kit 动态库加载成功"); @@ -770,36 +789,42 @@ static void OnRightTriggerAxis(const struct GamePad_AxisEvent* axisEvent) { // 按键 + 轴监听的注册/注销在 StartMonitor、StopMonitor、Pause、Resume 中重复出现 4 次 // 提取为内部 helper 消除重复 +#if GAME_CONTROLLER_KIT_AVAILABLE + +// 安全调用宏:函数指针非空时才调用(已被 #define 重定向为 pfn_* 指针) +#define SAFE_CALL(func, ...) do { if (func) func(__VA_ARGS__); } while(0) +#define SAFE_CALL0(func) do { if (func) func(); } while(0) + /** * 注册所有按键 + 轴输入监听(不含设备监听) * 调用者需持有 g_mutex 或保证线程安全 */ static void RegisterAllInputMonitors() { // 按键监听 - OH_GamePad_ButtonA_RegisterButtonInputMonitor(OnButtonA); - OH_GamePad_ButtonB_RegisterButtonInputMonitor(OnButtonB); - OH_GamePad_ButtonX_RegisterButtonInputMonitor(OnButtonX); - OH_GamePad_ButtonY_RegisterButtonInputMonitor(OnButtonY); - OH_GamePad_ButtonC_RegisterButtonInputMonitor(OnButtonC); - OH_GamePad_LeftShoulder_RegisterButtonInputMonitor(OnLeftShoulder); - OH_GamePad_RightShoulder_RegisterButtonInputMonitor(OnRightShoulder); - OH_GamePad_LeftTrigger_RegisterButtonInputMonitor(OnLeftTriggerButton); - OH_GamePad_RightTrigger_RegisterButtonInputMonitor(OnRightTriggerButton); - OH_GamePad_LeftThumbstick_RegisterButtonInputMonitor(OnLeftThumbstick); - OH_GamePad_RightThumbstick_RegisterButtonInputMonitor(OnRightThumbstick); - OH_GamePad_ButtonHome_RegisterButtonInputMonitor(OnButtonHome); - OH_GamePad_ButtonMenu_RegisterButtonInputMonitor(OnButtonMenu); - OH_GamePad_Dpad_UpButton_RegisterButtonInputMonitor(OnDpadUp); - OH_GamePad_Dpad_DownButton_RegisterButtonInputMonitor(OnDpadDown); - OH_GamePad_Dpad_LeftButton_RegisterButtonInputMonitor(OnDpadLeft); - OH_GamePad_Dpad_RightButton_RegisterButtonInputMonitor(OnDpadRight); + SAFE_CALL(OH_GamePad_ButtonA_RegisterButtonInputMonitor, OnButtonA); + SAFE_CALL(OH_GamePad_ButtonB_RegisterButtonInputMonitor, OnButtonB); + SAFE_CALL(OH_GamePad_ButtonX_RegisterButtonInputMonitor, OnButtonX); + SAFE_CALL(OH_GamePad_ButtonY_RegisterButtonInputMonitor, OnButtonY); + SAFE_CALL(OH_GamePad_ButtonC_RegisterButtonInputMonitor, OnButtonC); + SAFE_CALL(OH_GamePad_LeftShoulder_RegisterButtonInputMonitor, OnLeftShoulder); + SAFE_CALL(OH_GamePad_RightShoulder_RegisterButtonInputMonitor, OnRightShoulder); + SAFE_CALL(OH_GamePad_LeftTrigger_RegisterButtonInputMonitor, OnLeftTriggerButton); + SAFE_CALL(OH_GamePad_RightTrigger_RegisterButtonInputMonitor, OnRightTriggerButton); + SAFE_CALL(OH_GamePad_LeftThumbstick_RegisterButtonInputMonitor, OnLeftThumbstick); + SAFE_CALL(OH_GamePad_RightThumbstick_RegisterButtonInputMonitor, OnRightThumbstick); + SAFE_CALL(OH_GamePad_ButtonHome_RegisterButtonInputMonitor, OnButtonHome); + SAFE_CALL(OH_GamePad_ButtonMenu_RegisterButtonInputMonitor, OnButtonMenu); + SAFE_CALL(OH_GamePad_Dpad_UpButton_RegisterButtonInputMonitor, OnDpadUp); + SAFE_CALL(OH_GamePad_Dpad_DownButton_RegisterButtonInputMonitor, OnDpadDown); + SAFE_CALL(OH_GamePad_Dpad_LeftButton_RegisterButtonInputMonitor, OnDpadLeft); + SAFE_CALL(OH_GamePad_Dpad_RightButton_RegisterButtonInputMonitor, OnDpadRight); // 轴监听 - OH_GamePad_LeftThumbstick_RegisterAxisInputMonitor(OnLeftThumbstickAxis); - OH_GamePad_RightThumbstick_RegisterAxisInputMonitor(OnRightThumbstickAxis); - OH_GamePad_Dpad_RegisterAxisInputMonitor(OnDpadAxis); - OH_GamePad_LeftTrigger_RegisterAxisInputMonitor(OnLeftTriggerAxis); - OH_GamePad_RightTrigger_RegisterAxisInputMonitor(OnRightTriggerAxis); + SAFE_CALL(OH_GamePad_LeftThumbstick_RegisterAxisInputMonitor, OnLeftThumbstickAxis); + SAFE_CALL(OH_GamePad_RightThumbstick_RegisterAxisInputMonitor, OnRightThumbstickAxis); + SAFE_CALL(OH_GamePad_Dpad_RegisterAxisInputMonitor, OnDpadAxis); + SAFE_CALL(OH_GamePad_LeftTrigger_RegisterAxisInputMonitor, OnLeftTriggerAxis); + SAFE_CALL(OH_GamePad_RightTrigger_RegisterAxisInputMonitor, OnRightTriggerAxis); } /** @@ -808,32 +833,36 @@ static void RegisterAllInputMonitors() { */ static void UnregisterAllInputMonitors() { // 按键监听 - OH_GamePad_ButtonA_UnregisterButtonInputMonitor(); - OH_GamePad_ButtonB_UnregisterButtonInputMonitor(); - OH_GamePad_ButtonX_UnregisterButtonInputMonitor(); - OH_GamePad_ButtonY_UnregisterButtonInputMonitor(); - OH_GamePad_ButtonC_UnregisterButtonInputMonitor(); - OH_GamePad_LeftShoulder_UnregisterButtonInputMonitor(); - OH_GamePad_RightShoulder_UnregisterButtonInputMonitor(); - OH_GamePad_LeftTrigger_UnregisterButtonInputMonitor(); - OH_GamePad_RightTrigger_UnregisterButtonInputMonitor(); - OH_GamePad_LeftThumbstick_UnregisterButtonInputMonitor(); - OH_GamePad_RightThumbstick_UnregisterButtonInputMonitor(); - OH_GamePad_ButtonHome_UnregisterButtonInputMonitor(); - OH_GamePad_ButtonMenu_UnregisterButtonInputMonitor(); - OH_GamePad_Dpad_UpButton_UnregisterButtonInputMonitor(); - OH_GamePad_Dpad_DownButton_UnregisterButtonInputMonitor(); - OH_GamePad_Dpad_LeftButton_UnregisterButtonInputMonitor(); - OH_GamePad_Dpad_RightButton_UnregisterButtonInputMonitor(); + SAFE_CALL0(OH_GamePad_ButtonA_UnregisterButtonInputMonitor); + SAFE_CALL0(OH_GamePad_ButtonB_UnregisterButtonInputMonitor); + SAFE_CALL0(OH_GamePad_ButtonX_UnregisterButtonInputMonitor); + SAFE_CALL0(OH_GamePad_ButtonY_UnregisterButtonInputMonitor); + SAFE_CALL0(OH_GamePad_ButtonC_UnregisterButtonInputMonitor); + SAFE_CALL0(OH_GamePad_LeftShoulder_UnregisterButtonInputMonitor); + SAFE_CALL0(OH_GamePad_RightShoulder_UnregisterButtonInputMonitor); + SAFE_CALL0(OH_GamePad_LeftTrigger_UnregisterButtonInputMonitor); + SAFE_CALL0(OH_GamePad_RightTrigger_UnregisterButtonInputMonitor); + SAFE_CALL0(OH_GamePad_LeftThumbstick_UnregisterButtonInputMonitor); + SAFE_CALL0(OH_GamePad_RightThumbstick_UnregisterButtonInputMonitor); + SAFE_CALL0(OH_GamePad_ButtonHome_UnregisterButtonInputMonitor); + SAFE_CALL0(OH_GamePad_ButtonMenu_UnregisterButtonInputMonitor); + SAFE_CALL0(OH_GamePad_Dpad_UpButton_UnregisterButtonInputMonitor); + SAFE_CALL0(OH_GamePad_Dpad_DownButton_UnregisterButtonInputMonitor); + SAFE_CALL0(OH_GamePad_Dpad_LeftButton_UnregisterButtonInputMonitor); + SAFE_CALL0(OH_GamePad_Dpad_RightButton_UnregisterButtonInputMonitor); // 轴监听 - OH_GamePad_LeftThumbstick_UnregisterAxisInputMonitor(); - OH_GamePad_RightThumbstick_UnregisterAxisInputMonitor(); - OH_GamePad_Dpad_UnregisterAxisInputMonitor(); - OH_GamePad_LeftTrigger_UnregisterAxisInputMonitor(); - OH_GamePad_RightTrigger_UnregisterAxisInputMonitor(); + SAFE_CALL0(OH_GamePad_LeftThumbstick_UnregisterAxisInputMonitor); + SAFE_CALL0(OH_GamePad_RightThumbstick_UnregisterAxisInputMonitor); + SAFE_CALL0(OH_GamePad_Dpad_UnregisterAxisInputMonitor); + SAFE_CALL0(OH_GamePad_LeftTrigger_UnregisterAxisInputMonitor); + SAFE_CALL0(OH_GamePad_RightTrigger_UnregisterAxisInputMonitor); } +#undef SAFE_CALL +#undef SAFE_CALL0 +#endif // GAME_CONTROLLER_KIT_AVAILABLE + // ==================== 公共 API ==================== bool GameController_IsAvailable(void) { @@ -875,13 +904,16 @@ int GameController_Init(void) { #endif } +// Forward declaration - defined after GameController_StopMonitor +static void GameController_StopMonitorUnlocked(); + void GameController_Uninit(void) { std::lock_guard lock(g_mutex); if (!g_initialized) return; if (g_gcLibAvailable) { - GameController_StopMonitor(); + GameController_StopMonitorUnlocked(); } g_deviceStates.clear(); @@ -1005,10 +1037,9 @@ int GameController_StartMonitor(void) { #endif } -void GameController_StopMonitor(void) { +// 无锁版本,供已持有 g_mutex 的调用者使用(如 Uninit) +static void GameController_StopMonitorUnlocked() { #if GAME_CONTROLLER_KIT_AVAILABLE - // 注意: 此函数可能在 Uninit 的锁内被调用,不要再加锁 - if (!g_monitoring) return; if (!g_gcLibAvailable) { @@ -1030,6 +1061,13 @@ void GameController_StopMonitor(void) { #endif } +void GameController_StopMonitor(void) { +#if GAME_CONTROLLER_KIT_AVAILABLE + std::lock_guard lock(g_mutex); + GameController_StopMonitorUnlocked(); +#endif +} + /** * 暂停输入监听(仅按键+轴),保留设备上下线监听 * 用于无手柄场景减少系统轮询干扰,同时保持设备热插拔检测