Botões universais para trás / para frente no OSX em vez de M4 / M5?

4

Estou perplexo com o comportamento dos dois botões laterais no meu mouse Logitech MX Master. Em todos os meus outros mouses, os botões laterais são detectados como "botão 4" e "botão 5" genéricos. (Eu verifiquei isso usando o Xcode.) O OS X, ao contrário do Windows, parece tratar esses comandos como cliques do meio, então para obter o comportamento de retorno / avanço que você pode esperar, você precisa usar uma ferramenta como USB Overdrive para mapear eles para + [ e + ] . Infelizmente, essa solução alternativa não funciona em todos os aplicativos e pisca na barra de menus quando você a aciona.

Enquanto isso, os botões laterais do Master não são detectados pelo meu mouse do Xcode, mas de alguma forma funcionam em praticamente qualquer app com uma barra de navegação. Eu os testei no Finder, Safari, Preferências do Sistema e Xcode. Não há nenhum menu piscando e o cursor do mouse tem que estar sobre a área da janela controlada pela barra de navegação, o que implica que há algum tipo de evento back / forward universal sendo enviado (em oposição ao usual M4 / M5). No entanto, não consigo encontrar documentação de tal evento existente no OS X. A maioria das correções M4 / M5 envolve mapear esses botões para + [ e + ] .

Então, o que o Mestre está fazendo com esses botões laterais e como posso replicar o mesmo comportamento em todos os meus outros mouses?

    
por Archagon 05.06.2017 / 03:40

1 resposta

5

Eu adicionei um toque em todos meus NSWindow eventos. Acontece que ... o Mestre está simulando eventos de furto!

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}

OK, isso é muito inteligente, já que basicamente significa que funcionará em qualquer visualização que suporte o seletor swipeWithEvent: . Eu não tenho idéia porque este não é o comportamento padrão para os botões laterais! Agora tenho que descobrir como adicionar essa funcionalidade aos meus outros mouses. Eu não acho que o USB Overdrive possa fazer algo assim ... a menos que o AppleScript tenha uma maneira de simular gestos.

UPDATE: consegui replicar esses eventos usando a engenharia reversa do natevw funções de simulação de gestos, link . Talvez ainda precise ser consertado um pouco, mas funciona! O passo final será criar um aplicativo que funcione sempre e que coma eventos M4 e M5 e gere esses gestos.

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);

UPDATE 2: Aqui está um esboço de trabalho de um Controlador de Visão que captura globalmente M4 e M5 e emite furtos.

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

UPDATE 3: eu lancei um aplicativo de barra de menu de código aberto que replica o comportamento do mestre para todos os mouses de terceiros. É chamado de SensibleSideButtons . Os detalhes técnicos estão descritos no site.

    
por 05.06.2017 / 03:59