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