From cca03f130fcc9c85dc5e607723824c9cf67d6e33 Mon Sep 17 00:00:00 2001
From: "Cathy J. Fitzpatrick" <cathy@cathyjf.com>
Date: Sat, 2 Nov 2024 05:42:02 -0700
Subject: [PATCH] fix(macos): prevent indefinite hanging if screen capture is
 not granted

Currently, if Sunshine has not yet been granted the screen capture
permission, the program simply hangs indefinitely on startup when it
attempts to probe for usable encoders. This is not desirable because
it prevents testing and using all of the parts of Sunshine that do not
require the screen capture permission.

With this patch, the encoder probing will simply fail instead of
hanging indefinitely if Sunshine does not yet have the screen capture
permission.

Note that Sunshine already prints out an error message telling the user
that the screen capture permission is needed. The bug is that Sunshine
currently indefinitely hangs shortly after printing that message.
---
 src/platform/macos/display.mm |  8 ++++++++
 src/platform/macos/misc.h     |  4 ++++
 src/platform/macos/misc.mm    | 13 +++++++++++++
 3 files changed, 25 insertions(+)

diff --git a/src/platform/macos/display.mm b/src/platform/macos/display.mm
index 093e8c17f2b..5220c68db72 100644
--- a/src/platform/macos/display.mm
+++ b/src/platform/macos/display.mm
@@ -5,6 +5,7 @@
 #include "src/platform/common.h"
 #include "src/platform/macos/av_img_t.h"
 #include "src/platform/macos/av_video.h"
+#include "src/platform/macos/misc.h"
 #include "src/platform/macos/nv12_zero_device.h"
 
 #include "src/config.h"
@@ -100,6 +101,13 @@
 
     int
     dummy_img(img_t *img) override {
+      if (!platf::is_screen_capture_allowed()) {
+        // If we don't have the screen capture permission, this function will hang
+        // indefinitely without doing anything useful. Exit instead to avoid this.
+        // A non-zero return value indicates failure to the calling function.
+        return 1;
+      }
+
       auto signal = [av_capture capture:^(CMSampleBufferRef sampleBuffer) {
         auto new_sample_buffer = std::make_shared<av_sample_buf_t>(sampleBuffer);
         auto new_pixel_buffer = std::make_shared<av_pixel_buf_t>(new_sample_buffer->buf);
diff --git a/src/platform/macos/misc.h b/src/platform/macos/misc.h
index ba0c9507b9b..30b1c06f2e8 100644
--- a/src/platform/macos/misc.h
+++ b/src/platform/macos/misc.h
@@ -8,6 +8,10 @@
 
 #include <CoreGraphics/CoreGraphics.h>
 
+namespace platf {
+  bool is_screen_capture_allowed();
+}
+
 namespace dyn {
   typedef void (*apiproc)();
 
diff --git a/src/platform/macos/misc.mm b/src/platform/macos/misc.mm
index 5366fdb238e..31f0cc4f450 100644
--- a/src/platform/macos/misc.mm
+++ b/src/platform/macos/misc.mm
@@ -42,6 +42,17 @@
   CGRequestScreenCaptureAccess(void) __attribute__((weak_import));
 #endif
 
+  namespace {
+    /// @brief This variable will be set to true if and only if we determine that this
+    ///        process has been granted the screen capture permission.
+    auto screen_capture_allowed = std::atomic<bool>{ false };
+  }  ///< This anonymous namespace prevents access to its contents outside of this file.
+
+  /// Return whether screen capture is allowed for this process.
+  bool is_screen_capture_allowed() {
+    return screen_capture_allowed;
+  }
+
   std::unique_ptr<deinit_t>
   init() {
     // This will generate a warning about CGPreflightScreenCaptureAccess and
@@ -68,6 +79,8 @@
       return nullptr;
     }
 #pragma clang diagnostic pop
+    // Record that we determined that we have the screen capture permission.
+    screen_capture_allowed = true;
     return std::make_unique<deinit_t>();
   }