Skip to content

Commit

Permalink
feat(macos): permissions manager
Browse files Browse the repository at this point in the history
  • Loading branch information
Hazer committed Dec 25, 2024
1 parent dc9dfff commit 00cfddc
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 109 deletions.
2 changes: 2 additions & 0 deletions cmake/compile_definitions/macos.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ set(PLATFORM_TARGET_FILES
"${CMAKE_SOURCE_DIR}/src/platform/macos/misc.h"
"${CMAKE_SOURCE_DIR}/src/platform/macos/nv12_zero_device.cpp"
"${CMAKE_SOURCE_DIR}/src/platform/macos/nv12_zero_device.h"
"${CMAKE_SOURCE_DIR}/src/platform/macos/permissions_manager.mm"
"${CMAKE_SOURCE_DIR}/src/platform/macos/permissions_manager.h"
"${CMAKE_SOURCE_DIR}/src/platform/macos/publish.cpp"
"${CMAKE_SOURCE_DIR}/third-party/TPCircularBuffer/TPCircularBuffer.c"
"${CMAKE_SOURCE_DIR}/third-party/TPCircularBuffer/TPCircularBuffer.h"
Expand Down
2 changes: 1 addition & 1 deletion src/platform/macos/display.mm
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@

int
dummy_img(img_t *img) override {
if (!platf::is_screen_capture_allowed()) {
if (!platf::permissions_manager.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.
Expand Down
37 changes: 5 additions & 32 deletions src/platform/macos/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <mach/mach.h>

#include "misc.h"
#include "permissions_manager.h"

#include "src/display_device.h"
#include "src/logging.h"
Expand Down Expand Up @@ -229,11 +230,6 @@ const KeyCodeMap kKeyCodesMap[] = {
};
// clang-format on

/**
* Used to avoid spamming permission requests when the user receives an input event
*/
bool accessibility_permission_requested;

int
keysym(int keycode) {
KeyCodeMap key_map {};
Expand All @@ -250,35 +246,13 @@ const KeyCodeMap kKeyCodesMap[] = {
return temp_map->mac_keycode;
}

std::string
default_accessibility_log_msg() {
return "Accessibility permission is not enabled,"
" please enable sunshine in "
"[System Settings > Privacy & Security > Privacy > Accessibility]"
", then please restart Sunshine for it to take effect";
}

void
print_accessibility_status(const bool is_keyboard_event, const bool release) {
if (!release) return;

if (!has_accessibility_permission()) {
if (!accessibility_permission_requested) {
accessibility_permission_requested = true;
request_accessibility_permission();
}
BOOST_LOG(info) << "Received " << (is_keyboard_event ? "keyboard" : "mouse") << " event but "
<< default_accessibility_log_msg();
}
}

void
keyboard_update(input_t &input, uint16_t modcode, bool release, uint8_t flags) {
auto key = keysym(modcode);

BOOST_LOG(debug) << "got keycode: 0x"sv << std::hex << modcode << ", translated to: 0x" << std::hex << key << ", release:" << release;

print_accessibility_status(true, release);
permissions_manager.print_accessibility_status(true, release);

if (key < 0) {
return;
Expand Down Expand Up @@ -470,7 +444,7 @@ const KeyCodeMap kKeyCodesMap[] = {
return;
}

print_accessibility_status(false, release);
permissions_manager.print_accessibility_status(false, release);

macos_input->mouse_down[mac_button] = !release;

Expand Down Expand Up @@ -572,9 +546,8 @@ const KeyCodeMap kKeyCodesMap[] = {

const auto macos_input = static_cast<macos_input_t *>(result.get());

accessibility_permission_requested = false;
if (request_accessibility_permission()) {
BOOST_LOG(info) << default_accessibility_log_msg() << ", to allow mouse clicks and keyboard inputs.";
if (permissions_manager.request_accessibility_permission()) {
BOOST_LOG(info) << PermissionsManager::default_accessibility_log_msg() << ", to allow mouse clicks and keyboard inputs.";
}

// Default to main display
Expand Down
19 changes: 3 additions & 16 deletions src/platform/macos/misc.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,14 @@
*/
#pragma once

#include "permissions_manager.h"

#include <vector>

#include <CoreGraphics/CoreGraphics.h>

namespace platf {
bool
is_screen_capture_allowed();

/**
* Prompts the user for Accessibility permission
* @return returns true if requested permission, false if already has permission
*/
bool
request_accessibility_permission();

/**
* Checks for Accessibility permission
* @return returns true if sunshine has Accessibility permission enabled
*/
bool
has_accessibility_permission();
static auto permissions_manager = PermissionsManager();
}

namespace dyn {
Expand Down
61 changes: 1 addition & 60 deletions src/platform/macos/misc.mm
Original file line number Diff line number Diff line change
Expand Up @@ -34,55 +34,11 @@

namespace platf {

// Even though the following two functions are available starting in macOS 10.15, they weren't
// actually in the Mac SDK until Xcode 12.2, the first to include the SDK for macOS 11
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 110000 // __MAC_11_0
// If they're not in the SDK then we can use our own function definitions.
// Need to use weak import so that this will link in macOS 10.14 and earlier
extern "C" bool
CGPreflightScreenCaptureAccess(void) __attribute__((weak_import));
extern "C" bool
CGRequestScreenCaptureAccess(void) __attribute__((weak_import));
#endif

namespace {
auto screen_capture_allowed = std::atomic<bool> { false };
} // namespace

// 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
// CGRequestScreenCaptureAccess being unavailable before macOS 10.15, but
// we have a guard to prevent it from being called on those earlier systems.
// Unfortunately the supported way to silence this warning, using @available,
// produces linker errors for __isPlatformVersionAtLeast, so we have to use
// a different method.
// We also ignore "tautological-pointer-compare" because when compiling with
// Xcode 12.2 and later, these functions are not weakly linked and will never
// be null, and therefore generate this warning. Since we are weakly linking
// when compiling with earlier Xcode versions, the check for null is
// necessary, and so we ignore the warning.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
#pragma clang diagnostic ignored "-Wtautological-pointer-compare"
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:((NSOperatingSystemVersion) { 10, 15, 0 })] &&
// Double check that these weakly-linked symbols have been loaded:
CGPreflightScreenCaptureAccess != nullptr && CGRequestScreenCaptureAccess != nullptr &&
!CGPreflightScreenCaptureAccess()) {
BOOST_LOG(error) << "No screen capture permission!"sv;
BOOST_LOG(error) << "Please activate it in 'System Preferences' -> 'Privacy' -> 'Screen Recording'"sv;
CGRequestScreenCaptureAccess();
if (permissions_manager.request_screen_capture_permission()) {
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>();
}

Expand Down Expand Up @@ -569,21 +525,6 @@ operator bool() override {
return std::make_unique<macos_high_precision_timer>();
}

bool
request_accessibility_permission() {
NSDictionary* options = @{static_cast<id>(kAXTrustedCheckOptionPrompt): @YES};
return !AXIsProcessTrustedWithOptions(static_cast<CFDictionaryRef>(options));
}

bool
has_accessibility_permission() {
NSDictionary* options = @{static_cast<id>(kAXTrustedCheckOptionPrompt): @NO};
// We use kAXTrustedCheckOptionPrompt == NO here,
// instead of using XIsProcessTrusted(),
// because this will update the accessibility list with sunshine current path
return AXIsProcessTrustedWithOptions(static_cast<CFDictionaryRef>(options));
}

} // namespace platf

namespace dyn {
Expand Down
67 changes: 67 additions & 0 deletions src/platform/macos/permissions_manager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* @file src/platform/macos/permissions_manager.h
* @brief Handles macOS platform permissions.
*/
#pragma once

#include <Carbon/Carbon.h>

#include <atomic>
#include <string>

namespace platf {
class PermissionsManager {
public:
static std::string
default_accessibility_log_msg() {
return "Accessibility permission is not enabled,"
" please enable sunshine in "
"[System Settings > Privacy & Security > Privacy > Accessibility]"
", then please restart Sunshine for it to take effect";
}

PermissionsManager() = default;

bool
is_screen_capture_allowed();

bool
request_screen_capture_permission();

/**
* Checks for Accessibility permission
* @return returns true if sunshine has Accessibility permission enabled
*/
bool
has_accessibility_permission();

/**
* Checks for Accessibility permission
* @return returns true if sunshine has Accessibility permission enabled
*/
bool
has_accessibility_permission_cached();

/**
* Prompts the user for Accessibility permission
* @return returns true if requested permission, false if already has permission
*/
bool
request_accessibility_permission();

/**
* Prompts the user for Accessibility permission
* @return returns true if requested permission, false if already has permission
*/
bool
request_accessibility_permission_once();

/**
* Prints the accessibility status based on the input event type and release status
* @param is_keyboard_event indicates if the event is a keyboard event
* @param release indicates if the event is a release event
*/
void
print_accessibility_status(const bool is_keyboard_event, const bool release);
};
} // namespace platf
119 changes: 119 additions & 0 deletions src/platform/macos/permissions_manager.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* @file src/platform/macos/permissions_manager.mm
* @brief Handles macOS platform permissions.
*/

#include "permissions_manager.h"

#include <Foundation/Foundation.h>

#include "src/logging.h"

namespace platf {
// Even though the following two functions are available starting in macOS 10.15, they weren't
// actually in the Mac SDK until Xcode 12.2, the first to include the SDK for macOS 11
#if __MAC_OS_X_VERSION_MAX_ALLOWED < 110000 // __MAC_11_0
// If they're not in the SDK then we can use our own function definitions.
// Need to use weak import so that this will link in macOS 10.14 and earlier
extern "C" bool
CGPreflightScreenCaptureAccess(void) __attribute__((weak_import));
extern "C" bool
CGRequestScreenCaptureAccess(void) __attribute__((weak_import));
#endif

namespace {
auto
screen_capture_allowed = std::atomic { false };
/**
* Used to avoid spamming permission requests when the user receives an input event
*/
bool
accessibility_permission_requested = std::atomic { false };
bool
has_accessibility = std::atomic { false };
} // namespace

// Return whether screen capture is allowed for this process.
bool
PermissionsManager::is_screen_capture_allowed() {
return screen_capture_allowed;
}

bool
PermissionsManager::request_screen_capture_permission() {
// This will generate a warning about CGPreflightScreenCaptureAccess and
// CGRequestScreenCaptureAccess being unavailable before macOS 10.15, but
// we have a guard to prevent it from being called on those earlier systems.
// Unfortunately the supported way to silence this warning, using @available,
// produces linker errors for __isPlatformVersionAtLeast, so we have to use
// a different method.
// We also ignore "tautological-pointer-compare" because when compiling with
// Xcode 12.2 and later, these functions are not weakly linked and will never
// be null, and therefore generate this warning. Since we are weakly linking
// when compiling with earlier Xcode versions, the check for null is
// necessary, and so we ignore the warning.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
#pragma clang diagnostic ignored "-Wtautological-pointer-compare"
if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:((NSOperatingSystemVersion) { 10, 15, 0 })] &&
// Double check that these weakly-linked symbols have been loaded:
CGPreflightScreenCaptureAccess != nullptr && CGRequestScreenCaptureAccess != nullptr &&
!CGPreflightScreenCaptureAccess()) {
BOOST_LOG(error) << "No screen capture permission!";
BOOST_LOG(error) << "Please activate it in 'System Preferences' -> 'Privacy' -> 'Screen Recording'";
CGRequestScreenCaptureAccess();
return true;
}
#pragma clang diagnostic pop
// Record that we determined that we have the screen capture permission.
screen_capture_allowed = true;
return false;
}

bool
PermissionsManager::has_accessibility_permission() {
NSDictionary *options = @{static_cast<id>(kAXTrustedCheckOptionPrompt): @NO};
// We use kAXTrustedCheckOptionPrompt == NO here,
// instead of using XIsProcessTrusted(),
// because this will update the accessibility list with sunshine current path
return AXIsProcessTrustedWithOptions(static_cast<CFDictionaryRef>(options));
}

bool
PermissionsManager::has_accessibility_permission_cached() {
if (has_accessibility) return true;
if (accessibility_permission_requested) return has_accessibility;
has_accessibility = has_accessibility_permission();
return has_accessibility;
}

bool
PermissionsManager::request_accessibility_permission() {
if (!has_accessibility_permission()) {
NSDictionary *options = @{static_cast<id>(kAXTrustedCheckOptionPrompt): @YES};
return !AXIsProcessTrustedWithOptions(static_cast<CFDictionaryRef>(options));
}
return false;
}

bool
PermissionsManager::request_accessibility_permission_once() {
if (!accessibility_permission_requested) {
accessibility_permission_requested = true;
return request_accessibility_permission();
}
return false;
}

void
PermissionsManager::print_accessibility_status(const bool is_keyboard_event, const bool release) {
if (!release) return;

if (!has_accessibility_permission_cached()) {
request_accessibility_permission_once();
BOOST_LOG(info) << "Received " << (is_keyboard_event ? "keyboard" : "mouse") << " event but "
<< default_accessibility_log_msg();
}
}

} // namespace platf

0 comments on commit 00cfddc

Please sign in to comment.