fix(macos): scale remote scroll input using macOS scroll speed (#5133)
This commit is contained in:
@@ -3,7 +3,10 @@
|
|||||||
* @brief Definitions for macOS input handling.
|
* @brief Definitions for macOS input handling.
|
||||||
*/
|
*/
|
||||||
// standard includes
|
// standard includes
|
||||||
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdint>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
@@ -30,6 +33,10 @@ constexpr std::chrono::milliseconds MULTICLICK_DELAY_MS(500);
|
|||||||
namespace platf {
|
namespace platf {
|
||||||
using namespace std::literals;
|
using namespace std::literals;
|
||||||
|
|
||||||
|
constexpr int WHEEL_DELTA = 120;
|
||||||
|
constexpr double DEFAULT_SCROLLWHEEL_SCALING = 0.3125;
|
||||||
|
constexpr int DEFAULT_SCROLL_LINES_PER_DETENT = 5;
|
||||||
|
|
||||||
struct macos_input_t {
|
struct macos_input_t {
|
||||||
public:
|
public:
|
||||||
CGDirectDisplayID display {};
|
CGDirectDisplayID display {};
|
||||||
@@ -42,6 +49,8 @@ namespace platf {
|
|||||||
|
|
||||||
// mouse related stuff
|
// mouse related stuff
|
||||||
CGEventRef mouse_event {}; // mouse event source
|
CGEventRef mouse_event {}; // mouse event source
|
||||||
|
double scrollwheel_scaling {DEFAULT_SCROLLWHEEL_SCALING};
|
||||||
|
int scroll_lines_per_detent {DEFAULT_SCROLL_LINES_PER_DETENT};
|
||||||
bool mouse_down[3] {}; // mouse button status
|
bool mouse_down[3] {}; // mouse button status
|
||||||
std::chrono::steady_clock::steady_clock::time_point last_mouse_event[3][2]; // timestamp of last mouse events
|
std::chrono::steady_clock::steady_clock::time_point last_mouse_event[3][2]; // timestamp of last mouse events
|
||||||
};
|
};
|
||||||
@@ -485,20 +494,63 @@ const KeyCodeMap kKeyCodesMap[] = {
|
|||||||
macos_input->last_mouse_event[mac_button][release] = now;
|
macos_input->last_mouse_event[mac_button][release] = now;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int get_scroll_lines_per_detent(double &scrollwheel_scaling) {
|
||||||
|
double scale = DEFAULT_SCROLLWHEEL_SCALING;
|
||||||
|
const auto value = CFPreferencesCopyValue(CFSTR("com.apple.scrollwheel.scaling"), kCFPreferencesAnyApplication, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
|
||||||
|
if (value) {
|
||||||
|
if (CFGetTypeID(value) == CFNumberGetTypeID()) {
|
||||||
|
CFNumberGetValue(static_cast<CFNumberRef>(value), kCFNumberDoubleType, &scale);
|
||||||
|
} else if (CFGetTypeID(value) == CFStringGetTypeID()) {
|
||||||
|
scale = CFStringGetDoubleValue(static_cast<CFStringRef>(value));
|
||||||
|
}
|
||||||
|
CFRelease(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!std::isfinite(scale)) {
|
||||||
|
scale = DEFAULT_SCROLLWHEEL_SCALING;
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollwheel_scaling = scale;
|
||||||
|
|
||||||
|
// com.apple.scrollwheel.scaling stores the Mouse scroll speed slider position, not
|
||||||
|
// the scroll multiplier itself. The slider is 0..1 and Apple's default is 0.3125,
|
||||||
|
// so anchor 0 at one line per wheel detent and 0.3125 at five lines.
|
||||||
|
const auto scroll_scale = std::clamp(scale, 0.0, 1.0);
|
||||||
|
constexpr double lines_per_scroll_scale = (DEFAULT_SCROLL_LINES_PER_DETENT - 1.0) / DEFAULT_SCROLLWHEEL_SCALING;
|
||||||
|
|
||||||
|
return std::max(1, static_cast<int>(std::ceil(1.0 + scroll_scale * lines_per_scroll_scale)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int scroll_pixels(const macos_input_t *macos_input, const int high_res_distance) {
|
||||||
|
const auto source_pixels_per_line = CGEventSourceGetPixelsPerLine(macos_input->source);
|
||||||
|
const auto pixels_per_line = source_pixels_per_line > 0 ? static_cast<int>(source_pixels_per_line + 0.5) : 10;
|
||||||
|
const auto scaled_pixels = static_cast<int64_t>(high_res_distance) * std::max(1, pixels_per_line) * std::max(1, macos_input->scroll_lines_per_detent);
|
||||||
|
|
||||||
|
return static_cast<int>(scaled_pixels / WHEEL_DELTA);
|
||||||
|
}
|
||||||
|
|
||||||
|
void post_scroll(input_t &input, const int wheelY, const int wheelX) {
|
||||||
|
if (wheelY == 0 && wheelX == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto macos_input = static_cast<macos_input_t *>(input.get());
|
||||||
|
CGEventRef event = CGEventCreateScrollWheelEvent(macos_input->source, kCGScrollEventUnitPixel, 2, wheelY, wheelX);
|
||||||
|
if (!event) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CGEventSetIntegerValueField(event, kCGScrollWheelEventIsContinuous, 1);
|
||||||
|
CGEventPost(kCGHIDEventTap, event);
|
||||||
|
CFRelease(event);
|
||||||
|
}
|
||||||
|
|
||||||
void scroll(input_t &input, const int high_res_distance) {
|
void scroll(input_t &input, const int high_res_distance) {
|
||||||
int wheelY = high_res_distance / 120;
|
post_scroll(input, scroll_pixels(static_cast<macos_input_t *>(input.get()), high_res_distance), 0);
|
||||||
int wheelX = 0;
|
|
||||||
CGEventRef upEvent = CGEventCreateScrollWheelEvent(nullptr, kCGScrollEventUnitLine, 2, wheelY, wheelX);
|
|
||||||
CGEventPost(kCGHIDEventTap, upEvent);
|
|
||||||
CFRelease(upEvent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void hscroll(input_t &input, int high_res_distance) {
|
void hscroll(input_t &input, int high_res_distance) {
|
||||||
int wheelY = 0;
|
post_scroll(input, 0, scroll_pixels(static_cast<macos_input_t *>(input.get()), high_res_distance));
|
||||||
int wheelX = high_res_distance / 120;
|
|
||||||
CGEventRef upEvent = CGEventCreateScrollWheelEvent(nullptr, kCGScrollEventUnitLine, 2, wheelY, wheelX);
|
|
||||||
CGEventPost(kCGHIDEventTap, upEvent);
|
|
||||||
CFRelease(upEvent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -592,6 +644,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
|||||||
|
|
||||||
macos_input->source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
|
macos_input->source = CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
|
||||||
macos_input->keyboard_source = CGEventSourceCreate(kCGEventSourceStatePrivate);
|
macos_input->keyboard_source = CGEventSourceCreate(kCGEventSourceStatePrivate);
|
||||||
|
macos_input->scroll_lines_per_detent = get_scroll_lines_per_detent(macos_input->scrollwheel_scaling);
|
||||||
|
|
||||||
macos_input->kb_flags = 0;
|
macos_input->kb_flags = 0;
|
||||||
|
|
||||||
@@ -600,6 +653,7 @@ const KeyCodeMap kKeyCodesMap[] = {
|
|||||||
macos_input->mouse_down[1] = false;
|
macos_input->mouse_down[1] = false;
|
||||||
macos_input->mouse_down[2] = false;
|
macos_input->mouse_down[2] = false;
|
||||||
|
|
||||||
|
BOOST_LOG(debug) << "macOS scroll speed: com.apple.scrollwheel.scaling="sv << macos_input->scrollwheel_scaling << ", lines per detent="sv << macos_input->scroll_lines_per_detent << ", pixels per line="sv << CGEventSourceGetPixelsPerLine(macos_input->source);
|
||||||
BOOST_LOG(debug) << "Display "sv << macos_input->display << ", pixel dimension: " << CGDisplayPixelsWide(macos_input->display) << "x"sv << CGDisplayPixelsHigh(macos_input->display);
|
BOOST_LOG(debug) << "Display "sv << macos_input->display << ", pixel dimension: " << CGDisplayPixelsWide(macos_input->display) << "x"sv << CGDisplayPixelsHigh(macos_input->display);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
Reference in New Issue
Block a user