CGEventTapCreate загадочно ломается из-за событий нажатия клавиш

Я использую CGEventTapCreate для «кражи» медиа-ключей из iTunes во время работы моего приложения. Код внутри обратного вызова, который я передаю CGEventTapCreate, проверяет событие и, если обнаруживает, что это один из мультимедийных ключей, отправляет соответствующее уведомление в центр уведомлений по умолчанию.

Теперь это отлично работает, если я публикую уведомление о событии «нажатие клавиши». Если я сделаю это для событий «key down», в конечном итоге мое приложение перестанет получать события мультимедийных клавиш, и iTunes вступит во владение. Любые идеи о том, что может быть причиной этого? Соответствующая часть кода ниже

enum { 
...
  PlayPauseKeyDown = 0x100A00,
  PlayPauseKeyUp = 0x100B00,
...
};

static CGEventRef event_tap_callback(CGEventTapProxy proxy,
                                     CGEventType type,
                                     CGEventRef event,
                                     void *refcon)
{
  if (!(type == NX_SYSDEFINED) || (type == NX_KEYUP) || (type == NX_KEYDOWN))
      return event;

  NSEvent* keyEvent = [NSEvent eventWithCGEvent: event];
  if (keyEvent.type != NSSystemDefined) return event;

  switch(keyEvent.data1)
  {
    case PlayPauseKeyUp:  // <--- this works reliably
    //case PlayPauseKeyDown:  // <--- this will break eventually
      post_notification(@"PlayPauseMediaKeyPressed", nil, nil);
      return NULL;

    ... and so on ...

person svintus    schedule 03.06.2010    source источник
comment
Кажется, проблема со временем. Заменил post_notification на sleep(1), и вот оно, после нескольких нажатий клавиш iTunes крадет медиа-ключи обратно, если я использую PlayPauseKeyDown. Все еще работает, если я использую PlayPauseKeyUp.   -  person svintus    schedule 03.06.2010


Ответы (3)


Что-то убивает мой кран события, если обратный вызов занимает слишком много времени?

Некоторые люди подозревают, что в Snow Leopard есть ошибка, которая иногда отключает касания событий, даже если они не занимают слишком много времени. Чтобы справиться с этим, вы можете следить за типом события kCGEventTapDisabledByTimeout и реагировать, повторно активируя касание с помощью CGEventTapEnable.

person JWWalker    schedule 04.06.2010
comment
Да, это было оно. Обработка kCGEventTapDisabledByTimeout помогает. - person svintus; 04.06.2010
comment
Спасибо! Это сводило меня с ума! - person Sean Clark Hess; 22.01.2011

Прежде всего, почему ваше первое «если» позволяет проходить событиям key-down и key-up? Ваше второе «если» в любом случае позволяет проходить только системным событиям. Таким образом, для всех событий key-down/-up вы создаете NSEvent, просто опуская событие на одну строку ниже. В этом мало смысла. Перехват событий всегда должен быть максимально быстрым, иначе он замедлит всю обработку событий во всей системе. Ваш обратный вызов не должен вызываться даже для событий нажатия/нажатия клавиши, поскольку системные события не являются событиями нажатия/нажатия клавиши, это системные события. Если бы они были ключевыми событиями, вы бы точно никогда не получили доступ к data1, а вместо этого использовали бы методы «type» и «keyCode», чтобы получить от них соответствующую информацию.

static CGEventRef event_tap_callback(CGEventTapProxy proxy,
                                     CGEventType type,
                                     CGEventRef event,
                                     void *refcon)
{
  NSEvent * sysEvent;

  // No event we care for? return ASAP
  if (type != NX_SYSDEFINED) return event;

  sysEvent = [NSEvent eventWithCGEvent:event];
  // No need to test event type, we know it is NSSystemDefined,
  // becuase that is the same as NX_SYSDEFINED

Также вы не можете определить, является ли это правильным типом события, просто взглянув на данные, вы также должны проверить подтип, который должен быть 8 для этого типа события:

  if ([sysEvent subtype] != 8) return event;

Следующим логическим шагом будет разделение данных на составляющие:

  int data = [sysEvent data1];
  int keyCode = (data & 0xFFFF0000) >> 16;
  int keyFlags = (data & 0xFFFF);
  int keyState = (keyFlags & 0xFF00) >> 8;
  BOOL keyIsRepeat = (keyFlags & 0x1) > 0;

И вы, вероятно, не заботитесь о повторяющихся ключевых событиях (то есть, когда я держу клавишу нажатой, и она продолжает отправлять одно и то же событие снова и снова).

  // You probably won't care for repeating events
  if (keyIsRepeat) return event;

Наконец, вам не нужно определять какую-либо собственную константу, система имеет готовые константы для этих ключей:

  // Analyze the key
  switch (keyCode) {
    case NX_KEYTYPE_PLAY:
      // Play/Pause key
      if (keyState == 0x0A) {
        // Key down
        // ...do your stuff here...
        return NULL;
      } else if (keyState == 0x0B) {
        // Key Up
        // ...do your stuff here...
        return NULL;
      }
      // If neither down nor up, we don't know
      // what it is and better ignore it
      break;


    case NX_KEYTYPE_FAST:
      // (Fast) Forward
      break;

    case NX_KEYTYPE_REWIND:
       // Rewind key
       break;
  }

  // If we get here, we have not handled
  // the event and want system to handle it
  return event;
}

И если это все еще не работает, мой следующий вопрос будет о том, как выглядит ваша функция post_notification, и вы также видите описанную проблему, если вы не вызываете там post_notification, а просто делаете вызов NSLog о событии, которое вы только что видели?

person Mecki    schedule 03.06.2010
comment
Спасибо, Меки! Я исправил свой код, но проблема не устранена. Если я помещу post_notification в if (keyState == 0x0A) ..., iTunes в конечном итоге возьмет на себя мультимедийные ключи, если я поставлю его в if (keyState == 0x0B), все будет работать нормально. post_notification просто вызывает postNotificationName: ... стандартного NSNotificationCenter. Теперь, если я заменю post_notification на sleep(1), проблема возникнет намного быстрее. Что-то убивает мой кран события, если обратный вызов занимает слишком много времени? Является ли отправка уведомления слишком медленной для вызова из обратного вызова? - person svintus; 04.06.2010

В вашем обработчике проверьте следующий тип и просто повторно включите прослушиватель.

if (type == kCGEventTapDisabledByTimeout) {
    NSLog(@"Event Taps Disabled! Re-enabling");
            CGEventTapEnable(eventTap, true);
    return event;
}
person David Karlsson    schedule 13.03.2015