Я добавил сигнал ко всем моим 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= 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= 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= 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=
Хорошо, это довольно умно, так как в основном это означает, что он будет работать в любом представлении, которое поддерживает 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 . Технические подробности описаны на сайте.