diff --git a/Resources/Info.plist b/Resources/Info.plist index ba335119..c2badb5d 100644 --- a/Resources/Info.plist +++ b/Resources/Info.plist @@ -28,6 +28,8 @@ NSMicrophoneUsageDescription A program running within cmux would like to use your microphone. + NSCameraUsageDescription + A program running within cmux would like to use your camera. NSPrincipalClass NSApplication NSServices diff --git a/Resources/InfoPlist.xcstrings b/Resources/InfoPlist.xcstrings index 00297777..baeee708 100644 --- a/Resources/InfoPlist.xcstrings +++ b/Resources/InfoPlist.xcstrings @@ -2,6 +2,23 @@ "sourceLanguage": "en", "version": "1.0", "strings": { + "NSCameraUsageDescription": { + "extractionState": "manual", + "localizations": { + "en": { + "stringUnit": { + "state": "translated", + "value": "A program running within cmux would like to use your camera." + } + }, + "ja": { + "stringUnit": { + "state": "translated", + "value": "cmux 内で実行中のプログラムがカメラの使用を求めています。" + } + } + } + }, "NSMicrophoneUsageDescription": { "extractionState": "manual", "localizations": { diff --git a/Sources/Panels/BrowserPanel.swift b/Sources/Panels/BrowserPanel.swift index 29e12036..81eaf525 100644 --- a/Sources/Panels/BrowserPanel.swift +++ b/Sources/Panels/BrowserPanel.swift @@ -1533,6 +1533,7 @@ final class BrowserPanel: Panel, ObservableObject { private static func makeWebView() -> CmuxWebView { let config = WKWebViewConfiguration() config.processPool = BrowserPanel.sharedProcessPool + config.mediaTypesRequiringUserActionForPlayback = [] // Ensure browser cookies/storage persist across navigations and launches. // This reduces repeated consent/bot-challenge flows on sites like Google. config.websiteDataStore = .default() @@ -3652,6 +3653,16 @@ private class BrowserUIDelegate: NSObject, WKUIDelegate { } } + func webView( + _ webView: WKWebView, + requestMediaCapturePermissionFor origin: WKSecurityOrigin, + initiatedByFrame frame: WKFrameInfo, + type: WKMediaCaptureType, + decisionHandler: @escaping (WKPermissionDecision) -> Void + ) { + decisionHandler(.prompt) + } + func webView( _ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, diff --git a/cmux.entitlements b/cmux.entitlements index ec456f35..09e191a5 100644 --- a/cmux.entitlements +++ b/cmux.entitlements @@ -8,6 +8,8 @@ com.apple.security.cs.allow-jit + com.apple.security.device.camera + com.apple.security.device.audio-input com.apple.security.automation.apple-events diff --git a/tests/test_microphone_access_metadata.py b/tests/test_microphone_access_metadata.py index 595aa542..69045141 100644 --- a/tests/test_microphone_access_metadata.py +++ b/tests/test_microphone_access_metadata.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -"""Regression test: cmux advertises and allows microphone access.""" +"""Regression test: cmux advertises media-capture access metadata.""" from __future__ import annotations @@ -36,6 +36,7 @@ def main() -> int: entitlements = load_plist(repo_root / "cmux.entitlements", failures) mic_usage = info.get("NSMicrophoneUsageDescription") + camera_usage = info.get("NSCameraUsageDescription") if not isinstance(mic_usage, str) or not mic_usage.strip(): failures.append( "Resources/Info.plist must define a non-empty NSMicrophoneUsageDescription" @@ -50,13 +51,27 @@ def main() -> int: "cmux.entitlements must set com.apple.security.device.audio-input to true" ) + if not isinstance(camera_usage, str) or not camera_usage.strip(): + failures.append( + "Resources/Info.plist must define a non-empty NSCameraUsageDescription" + ) + elif camera_usage.strip() != "A program running within cmux would like to use your camera.": + failures.append( + "Resources/Info.plist NSCameraUsageDescription should match the Ghostty-style wording" + ) + + if entitlements.get("com.apple.security.device.camera") is not True: + failures.append( + "cmux.entitlements must set com.apple.security.device.camera to true" + ) + if failures: - print("FAIL: microphone access metadata regression(s) detected") + print("FAIL: media-capture metadata regression(s) detected") for failure in failures: print(f"- {failure}") return 1 - print("PASS: microphone usage description and entitlement are present") + print("PASS: microphone/camera usage descriptions and entitlements are present") return 0