Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 39 additions & 28 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -733,19 +733,31 @@ jobs:
name: sunshine-macports
path: artifacts/

- name: Fix screen capture permissions
if: ${{ matrix.os_version != 12 }} # macOS-12 is okay
# can be removed if the following is fixed in the runner image
# https://github.com/actions/runner-images/issues/9529
# https://github.com/actions/runner-images/pull/9530
- name: Fix permissions
run: |
# https://apple.stackexchange.com/questions/362865/macos-list-apps-authorized-for-full-disk-access
# https://github.com/actions/runner-images/issues/9529
# https://github.com/actions/runner-images/pull/9530

# permissions for screen capture
values="'kTCCServiceScreenCapture','/opt/off/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159"
# function to execute sql query for each value
function execute_sql_query {
local value=$1
local dbPath=$2

echo "Executing SQL query for value: $value"
sudo sqlite3 "$dbPath" "INSERT OR IGNORE INTO access VALUES($value);"
}

# permissions
declare -a values=(
"'kTCCServiceAccessibility','/opt/off/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,NULL,1592919552"
"'kTCCServiceScreenCapture','/opt/off/opt/runner/provisioner/provisioner',1,2,4,1,NULL,NULL,0,'UNUSED',NULL,0,1687786159"
)
if [[ "${{ matrix.os_version }}" == "14" ]]; then
# TCC access table in Sonoma has extra 4 columns: pid, pid_version, boot_uuid, last_reminded
values="${values},NULL,NULL,'UNUSED',${values##*,}"
for i in "${!values[@]}"; do
values[$i]="${values[$i]},NULL,NULL,'UNUSED',${values[$i]##*,}"
done
fi

# system and user databases
Expand All @@ -754,32 +766,31 @@ jobs:
"$HOME/Library/Application Support/com.apple.TCC/TCC.db"
)

sqlQuery="INSERT OR IGNORE INTO access VALUES($values);"

for dbPath in "${dbPaths[@]}"; do
echo "Column names for $dbPath"
echo "-------------------"
sudo sqlite3 "$dbPath" "PRAGMA table_info(access);"
echo "Current permissions for $dbPath"
echo "-------------------"
sudo sqlite3 "$dbPath" "SELECT * FROM access WHERE service='kTCCServiceScreenCapture';"
sudo sqlite3 "$dbPath" "$sqlQuery"
echo "Updated permissions for $dbPath"
echo "-------------------"
sudo sqlite3 "$dbPath" "SELECT * FROM access WHERE service='kTCCServiceScreenCapture';"
for value in "${values[@]}"; do
for dbPath in "${dbPaths[@]}"; do
echo "Column names for $dbPath"
echo "-------------------"
sudo sqlite3 "$dbPath" "PRAGMA table_info(access);"
echo "Current permissions for $dbPath"
echo "-------------------"
sudo sqlite3 "$dbPath" "SELECT * FROM access WHERE service='kTCCServiceScreenCapture';"
execute_sql_query "$value" "$dbPath"
echo "Updated permissions for $dbPath"
echo "-------------------"
sudo sqlite3 "$dbPath" "SELECT * FROM access WHERE service='kTCCServiceScreenCapture';"
done
done

- name: Run tests
id: test
timeout-minutes: 10
working-directory:
/opt/local/var/macports/build/_Users_runner_work_Sunshine_Sunshine_ports_multimedia_Sunshine/Sunshine/work/build/tests
run: |
sudo port test "Sunshine"

- name: Test Logs
if: always()
run: |
logfile="/opt/local/var/macports/logs/_Users_runner_work_Sunshine_Sunshine_ports_multimedia_Sunshine/Sunshine/main.log"
cat "$logfile"
sudo port install \
doxygen \
graphviz
sudo ./test_sunshine --gtest_color=yes

- name: Generate gcov report
# any except canceled or skipped
Expand Down
2 changes: 1 addition & 1 deletion packaging/macos/Portfile
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,4 @@ test.run yes
test.dir ${build.dir}/tests
test.target ""
test.cmd ./test_sunshine
test.args --gtest_color=yes
test.args --gtest_color=yes --gtest_filter=-*HIDTest.*:-*DeathTest.*
12 changes: 12 additions & 0 deletions src/platform/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,18 @@ namespace platf {

input_t
input();
/**
* @brief Gets the current mouse position on screen
* @param input The input_t instance to use.
* @return util::point_t (x, y)
*
* EXAMPLES:
* ```cpp
* auto [x, y] = get_mouse_loc(input);
* ```
*/
util::point_t
get_mouse_loc(input_t &input);
void
move_mouse(input_t &input, int deltaX, int deltaY);
void
Expand Down
30 changes: 30 additions & 0 deletions src/platform/linux/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,36 @@ namespace platf {
#endif
}

util::point_t
get_mouse_loc(input_t &input) {
#ifdef SUNSHINE_BUILD_X11
Display *xdisplay = ((input_raw_t *) input.get())->display;
if (!xdisplay) {
return util::point_t {};
}
Window root, root_return, child_return;
root = DefaultRootWindow(xdisplay);
int root_x, root_y;
int win_x, win_y;
unsigned int mask_return;

if (XQueryPointer(xdisplay, root, &root_return, &child_return, &root_x, &root_y, &win_x, &win_y, &mask_return)) {
BOOST_LOG(debug)
<< "Pointer is at:"sv << std::endl
<< " x: " << root_x << std::endl
<< " y: " << root_y << std::endl;

return util::point_t { (double) root_x, (double) root_y };
}
else {
BOOST_LOG(debug) << "Unable to query x11 pointer"sv << std::endl;
}
#else
BOOST_LOG(debug) << "Unable to query wayland pointer"sv << std::endl;
#endif
return util::point_t {};
}

/**
* @brief Absolute mouse move.
* @param input The input_t instance to use.
Expand Down
124 changes: 78 additions & 46 deletions src/platform/macos/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
* @file src/platform/macos/input.cpp
* @brief todo
*/
#include "src/input.h"

#import <Carbon/Carbon.h>
#include <chrono>
#include <mach/mach.h>
Expand All @@ -10,6 +12,11 @@
#include "src/platform/common.h"
#include "src/utility.h"

#include <ApplicationServices/ApplicationServices.h>
#include <CoreFoundation/CoreFoundation.h>
#include <iostream>
#include <thread>

/**
* @brief Delay for a double click, in milliseconds.
* @todo Make this configurable.
Expand Down Expand Up @@ -315,85 +322,108 @@ const KeyCodeMap kKeyCodesMap[] = {
}

// returns current mouse location:
inline CGPoint
util::point_t
get_mouse_loc(input_t &input) {
return CGEventGetLocation(((macos_input_t *) input.get())->mouse_event);
// Creating a new event every time to avoid any reuse risk
const auto macos_input = static_cast<macos_input_t *>(input.get());
const auto snapshot_event = CGEventCreate(macos_input->source);
const auto current = CGEventGetLocation(snapshot_event);
CFRelease(snapshot_event);
return util::point_t {
current.x,
current.y
};
}

void
post_mouse(input_t &input, CGMouseButton button, CGEventType type, CGPoint location, int click_count) {
BOOST_LOG(debug) << "mouse_event: "sv << button << ", type: "sv << type << ", location:"sv << location.x << ":"sv << location.y << " click_count: "sv << click_count;

auto macos_input = (macos_input_t *) input.get();
auto display = macos_input->display;
auto event = macos_input->mouse_event;
post_mouse(
input_t &input,
const CGMouseButton button,
const CGEventType type,
const util::point_t raw_location,
const util::point_t previous_location,
const int click_count) {
BOOST_LOG(debug) << "mouse_event: "sv << button << ", type: "sv << type << ", location:"sv << raw_location.x << ":"sv << raw_location.y << " click_count: "sv << click_count;

const auto macos_input = static_cast<macos_input_t *>(input.get());
const auto display = macos_input->display;
const auto event = macos_input->mouse_event;

// get display bounds for current display
CGRect display_bounds = CGDisplayBounds(display);
const CGRect display_bounds = CGDisplayBounds(display);

// limit mouse to current display bounds
location.x = std::clamp(location.x, display_bounds.origin.x, display_bounds.origin.x + display_bounds.size.width - 1);
location.y = std::clamp(location.y, display_bounds.origin.y, display_bounds.origin.y + display_bounds.size.height - 1);
const auto location = CGPoint {
std::clamp(raw_location.x, display_bounds.origin.x, display_bounds.origin.x + display_bounds.size.width - 1),
std::clamp(raw_location.y, display_bounds.origin.y, display_bounds.origin.y + display_bounds.size.height - 1)
};

CGEventSetType(event, type);
CGEventSetLocation(event, location);
CGEventSetIntegerValueField(event, kCGMouseEventButtonNumber, button);
CGEventSetIntegerValueField(event, kCGMouseEventClickState, click_count);

CGEventPost(kCGHIDEventTap, event);
// Include deltas so some 3D applications can consume changes (game cameras, etc)
const double deltaX = raw_location.x - previous_location.x;
const double deltaY = raw_location.y - previous_location.y;
CGEventSetDoubleValueField(event, kCGMouseEventDeltaX, deltaX);
CGEventSetDoubleValueField(event, kCGMouseEventDeltaY, deltaY);

// For why this is here, see:
// https://stackoverflow.com/questions/15194409/simulated-mouseevent-not-working-properly-osx
CGWarpMouseCursorPosition(location);
CGEventPost(kCGHIDEventTap, event);
}

inline CGEventType
event_type_mouse(input_t &input) {
auto macos_input = ((macos_input_t *) input.get());
const auto macos_input = static_cast<macos_input_t *>(input.get());

if (macos_input->mouse_down[0]) {
return kCGEventLeftMouseDragged;
}
else if (macos_input->mouse_down[1]) {
if (macos_input->mouse_down[1]) {
return kCGEventOtherMouseDragged;
}
else if (macos_input->mouse_down[2]) {
if (macos_input->mouse_down[2]) {
return kCGEventRightMouseDragged;
}
else {
return kCGEventMouseMoved;
}
return kCGEventMouseMoved;
}

void
move_mouse(input_t &input, int deltaX, int deltaY) {
auto current = get_mouse_loc(input);

CGPoint location = CGPointMake(current.x + deltaX, current.y + deltaY);

post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, 0);
move_mouse(
input_t &input,
const int deltaX,
const int deltaY) {
const auto current = get_mouse_loc(input);

const auto location = util::point_t { current.x + deltaX, current.y + deltaY };
post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, current, 0);
}

void
abs_mouse(input_t &input, const touch_port_t &touch_port, float x, float y) {
auto macos_input = static_cast<macos_input_t *>(input.get());
auto scaling = macos_input->displayScaling;
auto display = macos_input->display;

CGPoint location = CGPointMake(x * scaling, y * scaling);
abs_mouse(
input_t &input,
const touch_port_t &touch_port,
const float x,
const float y) {
const auto macos_input = static_cast<macos_input_t *>(input.get());
const auto scaling = macos_input->displayScaling;
const auto display = macos_input->display;

auto location = util::point_t { x * scaling, y * scaling };
CGRect display_bounds = CGDisplayBounds(display);
// in order to get the correct mouse location for capturing display , we need to add the display bounds to the location
location.x += display_bounds.origin.x;
location.y += display_bounds.origin.y;
post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, 0);

post_mouse(input, kCGMouseButtonLeft, event_type_mouse(input), location, get_mouse_loc(input), 0);
}

void
button_mouse(input_t &input, int button, bool release) {
button_mouse(input_t &input, const int button, const bool release) {
CGMouseButton mac_button;
CGEventType event;

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

switch (button) {
case 1:
Expand All @@ -413,22 +443,24 @@ const KeyCodeMap kKeyCodesMap[] = {
return;
}

mouse->mouse_down[mac_button] = !release;
macos_input->mouse_down[mac_button] = !release;

// if the last mouse down was less than MULTICLICK_DELAY_MS, we send a double click event
auto now = std::chrono::steady_clock::now();
if (now < mouse->last_mouse_event[mac_button][release] + MULTICLICK_DELAY_MS) {
post_mouse(input, mac_button, event, get_mouse_loc(input), 2);
const auto now = std::chrono::steady_clock::now();
const auto mouse_position = get_mouse_loc(input);

if (now < macos_input->last_mouse_event[mac_button][release] + MULTICLICK_DELAY_MS) {
post_mouse(input, mac_button, event, mouse_position, mouse_position, 2);
}
else {
post_mouse(input, mac_button, event, get_mouse_loc(input), 1);
post_mouse(input, mac_button, event, mouse_position, mouse_position, 1);
}

mouse->last_mouse_event[mac_button][release] = now;
macos_input->last_mouse_event[mac_button][release] = now;
}

void
scroll(input_t &input, int high_res_distance) {
scroll(input_t &input, const int high_res_distance) {
CGEventRef upEvent = CGEventCreateScrollWheelEvent(
nullptr,
kCGScrollEventUnitLine,
Expand Down Expand Up @@ -509,7 +541,7 @@ const KeyCodeMap kKeyCodesMap[] = {
input() {
input_t result { new macos_input_t() };

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

// Default to main display
macos_input->display = CGMainDisplayID();
Expand All @@ -534,7 +566,7 @@ const KeyCodeMap kKeyCodesMap[] = {
}

// Input coordinates are based on the virtual resolution not the physical, so we need the scaling factor
CGDisplayModeRef mode = CGDisplayCopyDisplayMode(macos_input->display);
const CGDisplayModeRef mode = CGDisplayCopyDisplayMode(macos_input->display);
macos_input->displayScaling = ((CGFloat) CGDisplayPixelsWide(macos_input->display)) / ((CGFloat) CGDisplayModeGetPixelWidth(mode));
CFRelease(mode);

Expand All @@ -555,7 +587,7 @@ const KeyCodeMap kKeyCodesMap[] = {

void
freeInput(void *p) {
auto *input = (macos_input_t *) p;
const auto *input = static_cast<macos_input_t *>(p);

CFRelease(input->source);
CFRelease(input->kb_event);
Expand Down
Loading