4

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

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

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

1 ответ1

3

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

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .