Skip to content

Commit 099d6ab

Browse files
Harden macOS capture setup and cursor moves
Updates CI on macOS to fully configure screen recording by writing replayd approval data, restarting replayd, and clarifying step messaging. In QtTrayMenu, adds a bounded cursor-position wait loop with polling and timeout so mouse move/restore checks are more reliable instead of relying on a single immediate position read.
1 parent 23c129f commit 099d6ab

2 files changed

Lines changed: 35 additions & 5 deletions

File tree

.github/workflows/ci.yml

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ jobs:
112112
if: runner.os == 'macOS'
113113
run: |
114114
dependencies=(
115+
"cliclick"
115116
"cmake"
116117
"doxygen"
117118
"graphviz"
@@ -123,11 +124,13 @@ jobs:
123124
)
124125
brew install "${dependencies[@]}"
125126
126-
- name: Fix macOS screen recording permissions
127+
- name: Configure macOS screen recording
127128
if: runner.os == 'macOS'
128129
run: |
129130
set -euo pipefail
130131
132+
clickTool="$(command -v cliclick)"
133+
131134
configure_system_tccdb() {
132135
local values=$1
133136
local dbPath="/Library/Application Support/com.apple.TCC/TCC.db"
@@ -144,19 +147,29 @@ jobs:
144147
145148
systemValuesArray=(
146149
"'kTCCServiceScreenCapture','/bin/bash',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1599831148"
150+
"'kTCCServicePostEvent','/bin/bash',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1599831148"
151+
"'kTCCServicePostEvent','$clickTool',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1599831148"
147152
)
148153
for values in "${systemValuesArray[@]}"; do
149154
configure_system_tccdb "$values,NULL,NULL,'UNUSED',${values##*,}"
150155
done
151156
152157
userValuesArray=(
153158
"'kTCCServiceScreenCapture','/bin/bash',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993"
159+
"'kTCCServicePostEvent','/bin/bash',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993"
160+
"'kTCCServicePostEvent','$clickTool',1,2,0,1,NULL,NULL,NULL,'UNUSED',NULL,0,1583997993"
154161
)
155162
for values in "${userValuesArray[@]}"; do
156163
configure_user_tccdb "$values,NULL,NULL,'UNUSED',${values##*,}"
157164
done
158165
159-
echo "macOS TCC permissions configured."
166+
preflightScreenshot="$RUNNER_TEMP/screen-capture-preflight.png"
167+
screencapture -x "$preflightScreenshot"
168+
sleep 1
169+
"$clickTool" kp:return
170+
sleep 1
171+
172+
echo "macOS screen recording configured."
160173
161174
- name: Setup Dependencies Windows
162175
if: runner.os == 'Windows'

src/QtTrayMenu.cpp

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
* @brief Definitions for Qt tray menu implemenation
44
*/
55
// standard includes
6+
#include <chrono>
67
#include <filesystem>
8+
#include <thread>
79

810
// qt includes
911
#include <QApplication>
@@ -22,12 +24,28 @@
2224

2325
namespace {
2426
constexpr int DEFAULT_PANEL_THICKNESS = 24;
27+
constexpr int CURSOR_POSITION_POLL_INTERVAL_MS = 10;
28+
constexpr int CURSOR_POSITION_TIMEOUT_MS = 500;
2529
constexpr int CURSOR_POSITION_TOLERANCE = 2;
2630

2731
bool positionsAreClose(const QPoint &first, const QPoint &second) {
2832
return (first - second).manhattanLength() <= CURSOR_POSITION_TOLERANCE;
2933
}
3034

35+
bool waitForCursorPosition(const QPoint &targetPosition, const QRect &targetGeometry = {}) {
36+
const auto deadline = std::chrono::steady_clock::now() + std::chrono::milliseconds(CURSOR_POSITION_TIMEOUT_MS);
37+
do {
38+
const QPoint currentPosition = QCursor::pos();
39+
if (targetGeometry.isValid() ? targetGeometry.contains(currentPosition) : positionsAreClose(currentPosition, targetPosition)) {
40+
return true;
41+
}
42+
std::this_thread::sleep_for(std::chrono::milliseconds(CURSOR_POSITION_POLL_INTERVAL_MS));
43+
} while (std::chrono::steady_clock::now() < deadline);
44+
45+
const QPoint currentPosition = QCursor::pos();
46+
return targetGeometry.isValid() ? targetGeometry.contains(currentPosition) : positionsAreClose(currentPosition, targetPosition);
47+
}
48+
3149
bool fallbackTrayIconPosition(QPoint *position) {
3250
const QScreen *screen = QGuiApplication::primaryScreen();
3351
if (screen == nullptr) {
@@ -405,8 +423,7 @@ bool QtTrayMenu::positionMouseOverIcon() {
405423
mousePositionSaved = true;
406424
}
407425
QCursor::setPos(targetPosition);
408-
const QPoint currentPosition = QCursor::pos();
409-
const bool positioned = iconGeometry.isValid() ? iconGeometry.contains(currentPosition) : positionsAreClose(currentPosition, targetPosition);
426+
const bool positioned = waitForCursorPosition(targetPosition, iconGeometry);
410427
if (!positioned) {
411428
qWarning("QtTrayMenu: could not position the mouse over the tray icon");
412429
}
@@ -419,8 +436,8 @@ bool QtTrayMenu::restoreMousePosition() {
419436
}
420437

421438
QCursor::setPos(savedMousePosition);
439+
const bool restored = waitForCursorPosition(savedMousePosition);
422440
mousePositionSaved = false;
423-
const bool restored = positionsAreClose(QCursor::pos(), savedMousePosition);
424441
if (!restored) {
425442
qWarning("QtTrayMenu: could not restore the saved mouse position");
426443
}

0 commit comments

Comments
 (0)