Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(macos): Mouse input broken in-game #2550

Merged
merged 22 commits into from
Jun 12, 2024
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
ReenigneArcher marked this conversation as resolved.
Show resolved Hide resolved
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
Hazer marked this conversation as resolved.
Show resolved Hide resolved

- 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 @@
#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 {};

Check warning on line 1106 in src/platform/linux/input.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/input.cpp#L1106

Added line #L1106 was not covered by tests
}
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 {};

Check warning on line 1128 in src/platform/linux/input.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/linux/input.cpp#L1128

Added line #L1128 was not covered by tests
}

/**
* @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 @@
}

// 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) {

Check warning on line 422 in src/platform/macos/input.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/macos/input.cpp#L422

Added line #L422 was not covered by tests
CGMouseButton mac_button;
CGEventType event;

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

Check warning on line 426 in src/platform/macos/input.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/macos/input.cpp#L426

Added line #L426 was not covered by tests

switch (button) {
case 1:
Expand All @@ -413,22 +443,24 @@
return;
}

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

Check warning on line 446 in src/platform/macos/input.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/macos/input.cpp#L446

Added line #L446 was not covered by tests

// 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);

Check warning on line 450 in src/platform/macos/input.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/macos/input.cpp#L449-L450

Added lines #L449 - L450 were not covered by tests

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

Check warning on line 453 in src/platform/macos/input.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/macos/input.cpp#L453

Added line #L453 was not covered by tests
}
else {
post_mouse(input, mac_button, event, get_mouse_loc(input), 1);
post_mouse(input, mac_button, event, mouse_position, mouse_position, 1);

Check warning on line 456 in src/platform/macos/input.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/macos/input.cpp#L456

Added line #L456 was not covered by tests
}

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

Check warning on line 459 in src/platform/macos/input.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/macos/input.cpp#L459

Added line #L459 was not covered by tests
}

void
scroll(input_t &input, int high_res_distance) {
scroll(input_t &input, const int high_res_distance) {

Check warning on line 463 in src/platform/macos/input.cpp

View check run for this annotation

Codecov / codecov/patch

src/platform/macos/input.cpp#L463

Added line #L463 was not covered by tests
CGEventRef upEvent = CGEventCreateScrollWheelEvent(
nullptr,
kCGScrollEventUnitLine,
Expand Down Expand Up @@ -509,7 +541,7 @@
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 @@
}

// 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 @@

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
Loading