Универсальные кнопки мыши назад / вперед в OSX вместо M4 / M5?

1808
Archagon

Я озадачен поведением двух боковых кнопок на моей мыши Logitech MX Master. На всех моих других мышах боковые кнопки определяются как общие «кнопка 4» и «кнопка 5». (Я проверил это с помощью Xcode.) OS X, в отличие от Windows, кажется, обрабатывает эти команды как средние щелчки, поэтому, чтобы получить ожидаемое поведение «назад / вперед», вам нужно использовать такой инструмент, как USB Overdrive для сопоставления их на + [и + ]. К сожалению, этот обходной путь работает не во всех приложениях и мигает в строке меню, когда вы его запускаете.

Между тем, боковые кнопки на Master вообще не обнаруживаются касанием моей мыши Xcode, но так или иначе они работают практически в любом приложении с панелью навигации. Я протестировал их в Finder, Safari, Системных настройках и Xcode. Меню не мигает, и курсор мыши должен находиться над областью окна, контролируемой навигационной панелью, что подразумевает отправку какого-либо универсального события «назад / вперед» (в отличие от обычного M4 / M5). Однако я не могу найти документацию такого события, существующего в OS X. Большинство исправлений M4 / M5 вовлекают сопоставление тех кнопок с + [и + ].

Итак, что делает Мастер с этими боковыми кнопками, и как я могу повторить то же поведение на всех моих других мышах?

3

1 ответ на вопрос

2
Archagon

Я добавил сигнал ко всем моим 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 . Технические подробности описаны на сайте.