Я добавил сигнал ко всем моим событиям NSWindow
. Оказывается ... Мастер моделирует события удара!
NSEvent: type=Swipe loc=(252,60) time=5443.9 flags=0x100 win=0x100b091f0 winNum=2014 ctxt=0x0 phase: 1 axis:0 amount=0.000 velocity={0, 0}
NSEvent: type=Swipe loc=(252,60) time=5443.9 flags=0x100 win=0x100b091f0 winNum=2014 ctxt=0x0 phase: 8 axis:0 amount=0.000 velocity={0, 0}
NSEvent: type=Swipe loc=(252,60) time=5445.7 flags=0x100 win=0x100b091f0 winNum=2014 ctxt=0x0 phase: 1 axis:0 amount=0.000 velocity={0, 0}
NSEvent: type=Swipe loc=(252,60) time=5445.7 flags=0x100 win=0x100b091f0 winNum=2014 ctxt=0x0 phase: 8 axis:0 amount=0.000 velocity={0, 0}
Хорошо, это довольно умно, поскольку в основном это означает, что он будет работать в любом представлении, которое поддерживает селектор swipeWithEvent:
. Я понятия не имею, почему это не поведение по умолчанию для боковых кнопок! Теперь я должен выяснить, как добавить эту функциональность другим моим мышам. Я не думаю, что USB Overdrive может сделать что-то подобное ... если только у AppleScript нет способа имитировать жесты.
ОБНОВЛЕНИЕ: мне удалось воспроизвести эти события, используя функции моделирования жестов, разработанные natevw , https://github.com/calftrail/Touch. Возможно, все еще нужно исправить, но это работает! Заключительным шагом будет создание постоянно работающего приложения, которое использует события M4 и M5 и выплевывает эти жесты.
TLInfoSwipeDirection dir = kTLInfoSwipeLeft;
NSDictionary* swipeInfo1 = [NSDictionary dictionaryWithObjectsAndKeys:
@(kTLInfoSubtypeSwipe), kTLInfoKeyGestureSubtype,
@(1), kTLInfoKeyGesturePhase,
nil];
NSDictionary* swipeInfo2 = [NSDictionary dictionaryWithObjectsAndKeys:
@(kTLInfoSubtypeSwipe), kTLInfoKeyGestureSubtype,
@(dir), kTLInfoKeySwipeDirection,
@(4), kTLInfoKeyGesturePhase,
nil];
CGEventRef event1 = tl_CGEventCreateFromGesture((__bridge CFDictionaryRef)(swipeInfo1), (__bridge CFArrayRef)@[]);
CGEventRef event2 = tl_CGEventCreateFromGesture((__bridge CFDictionaryRef)(swipeInfo2), (__bridge CFArrayRef)@[]);
CGEventPost(kCGHIDEventTap, event1);
CGEventPost(kCGHIDEventTap, event2);
// not sure if necessary under ARC
CFRelease(event1);
CFRelease(event2);
ОБНОВЛЕНИЕ 2: Вот грубый рабочий эскиз View Controller, который глобально захватывает M4 и M5 и испускает пролистывания.
static void SBFFakeSwipe(TLInfoSwipeDirection dir) {
NSDictionary* swipeInfo1 = [NSDictionary dictionaryWithObjectsAndKeys:
@(kTLInfoSubtypeSwipe), kTLInfoKeyGestureSubtype,
@(1), kTLInfoKeyGesturePhase,
nil];
NSDictionary* swipeInfo2 = [NSDictionary dictionaryWithObjectsAndKeys:
@(kTLInfoSubtypeSwipe), kTLInfoKeyGestureSubtype,
@(dir), kTLInfoKeySwipeDirection,
@(4), kTLInfoKeyGesturePhase,
nil];
CGEventRef event1 = tl_CGEventCreateFromGesture((__bridge CFDictionaryRef)(swipeInfo1), (__bridge CFArrayRef)@[]);
CGEventRef event2 = tl_CGEventCreateFromGesture((__bridge CFDictionaryRef)(swipeInfo2), (__bridge CFArrayRef)@[]);
CGEventPost(kCGHIDEventTap, event1);
CGEventPost(kCGHIDEventTap, event2);
CFRelease(event1);
CFRelease(event2);
}
static CGEventRef KeyDownCallback(CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) {
int64_t number = CGEventGetIntegerValueField(event, kCGMouseEventButtonNumber);
BOOL down = (CGEventGetType(event) == kCGEventOtherMouseDown);
if (number == 3) {
if (down) {
SBFFakeSwipe(kTLInfoSwipeLeft);
}
return NULL;
}
else if (number == 4) {
if (down) {
SBFFakeSwipe(kTLInfoSwipeRight);
}
return NULL;
}
else {
return event;
}
}
@implementation ViewController
-(void) viewDidLoad {
[super viewDidLoad];
NSDictionary* options = @{ (__bridge id)kAXTrustedCheckOptionPrompt: @YES };
BOOL accessibilityEnabled = AXIsProcessTrustedWithOptions((CFDictionaryRef)options);
assert(accessibilityEnabled);
CFMachPortRef eventTap = CGEventTapCreate(kCGHIDEventTap,
kCGHeadInsertEventTap,
kCGEventTapOptionDefault,
CGEventMaskBit(kCGEventOtherMouseUp)|CGEventMaskBit(kCGEventOtherMouseDown),
&KeyDownCallback,
NULL);
assert(eventTap != NULL);
CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(NULL, eventTap, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);
CFRelease(runLoopSource);
CGEventTapEnable(eventTap, true);
//CFRelease(eventTap); -- needs to be done on dealloc, I think
}
@end
ОБНОВЛЕНИЕ 3: я выпустил приложение строки меню с открытым исходным кодом, которое копирует поведение Мастера для всех сторонних мышей. Это называется SensibleSideButtons. Технические подробности описаны на сайте.