/*
 * Deskflow -- mouse and keyboard sharing utility
 * Copyright (C) 2012-2016 Symless Ltd.
 * Copyright (C) 2004 Chris Schoeneman
 *
 * This package is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * found in the file LICENSE that should have accompanied this file.
 *
 * This package is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "platform/OSXKeyState.h"
#include "arch/Arch.h"
#include "base/Log.h"
#include "platform/OSXMediaKeySupport.h"
#include "platform/OSXUchrKeyResource.h"

#include <Carbon/Carbon.h>
#include <IOKit/hidsystem/IOHIDLib.h>

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"

// Note that some virtual keys codes appear more than once.  The
// first instance of a virtual key code maps to the KeyID that we
// want to generate for that code.  The others are for mapping
// different KeyIDs to a single key code.
static const UInt32 s_shiftVK = kVK_Shift;
static const UInt32 s_controlVK = kVK_Control;
static const UInt32 s_altVK = kVK_Option;
static const UInt32 s_superVK = kVK_Command;
static const UInt32 s_capsLockVK = kVK_CapsLock;
static const UInt32 s_numLockVK = kVK_ANSI_KeypadClear; // 71

static const UInt32 s_brightnessUp = 144;
static const UInt32 s_brightnessDown = 145;
static const UInt32 s_missionControlVK = 160;
static const UInt32 s_launchpadVK = 131;

static const UInt32 s_osxNumLock = 1 << 16;

struct KeyEntry {
public:
  KeyID m_keyID;
  UInt32 m_virtualKey;
};
static const KeyEntry s_controlKeys[] = {
    // cursor keys.  if we don't do this we'll may still get these from
    // the keyboard resource but they may not correspond to the arrow
    // keys.
    {kKeyLeft, kVK_LeftArrow},
    {kKeyRight, kVK_RightArrow},
    {kKeyUp, kVK_UpArrow},
    {kKeyDown, kVK_DownArrow},
    {kKeyHome, kVK_Home},
    {kKeyEnd, kVK_End},
    {kKeyPageUp, kVK_PageUp},
    {kKeyPageDown, kVK_PageDown},
    {kKeyInsert, kVK_Help}, // Mac Keyboards have 'Help' on 'Insert'

    // function keys
    {kKeyF1, kVK_F1},
    {kKeyF2, kVK_F2},
    {kKeyF3, kVK_F3},
    {kKeyF4, kVK_F4},
    {kKeyF5, kVK_F5},
    {kKeyF6, kVK_F6},
    {kKeyF7, kVK_F7},
    {kKeyF8, kVK_F8},
    {kKeyF9, kVK_F9},
    {kKeyF10, kVK_F10},
    {kKeyF11, kVK_F11},
    {kKeyF12, kVK_F12},
    {kKeyF13, kVK_F13},
    {kKeyF14, kVK_F14},
    {kKeyF15, kVK_F15},
    {kKeyF16, kVK_F16},

    {kKeyKP_0, kVK_ANSI_Keypad0},
    {kKeyKP_1, kVK_ANSI_Keypad1},
    {kKeyKP_2, kVK_ANSI_Keypad2},
    {kKeyKP_3, kVK_ANSI_Keypad3},
    {kKeyKP_4, kVK_ANSI_Keypad4},
    {kKeyKP_5, kVK_ANSI_Keypad5},
    {kKeyKP_6, kVK_ANSI_Keypad6},
    {kKeyKP_7, kVK_ANSI_Keypad7},
    {kKeyKP_8, kVK_ANSI_Keypad8},
    {kKeyKP_9, kVK_ANSI_Keypad9},
    {kKeyKP_Decimal, kVK_ANSI_KeypadDecimal},
    {kKeyKP_Equal, kVK_ANSI_KeypadEquals},
    {kKeyKP_Multiply, kVK_ANSI_KeypadMultiply},
    {kKeyKP_Add, kVK_ANSI_KeypadPlus},
    {kKeyKP_Divide, kVK_ANSI_KeypadDivide},
    {kKeyKP_Subtract, kVK_ANSI_KeypadMinus},
    {kKeyKP_Enter, kVK_ANSI_KeypadEnter},

    // virtual key 110 is fn+enter and i have no idea what that's supposed
    // to map to.  also the enter key with numlock on is a modifier but i
    // don't know which.

    // modifier keys.  OS X doesn't seem to support right handed versions
    // of modifier keys so we map them to the left handed versions.
    {kKeyShift_L, s_shiftVK},
    {kKeyShift_R, s_shiftVK}, // 60
    {kKeyControl_L, s_controlVK},
    {kKeyControl_R, s_controlVK}, // 62
    {kKeyAlt_L, s_altVK},
    {kKeyAlt_R, s_altVK},
    {kKeySuper_L, s_superVK},
    {kKeySuper_R, s_superVK}, // 61
    {kKeyMeta_L, s_superVK},
    {kKeyMeta_R, s_superVK}, // 61

    // toggle modifiers
    {kKeyNumLock, s_numLockVK},
    {kKeyCapsLock, s_capsLockVK},

    // for Apple Pro JIS Keyboard, map Kana (IME activate) to Henkan (show next
    // IME conversion), and
    // Eisu (IME deactivate) to Zenkaku (IME activation toggle) on Windows
    // Japanese keyboard (OADG109A)
    {kKeyHenkan, kVK_JIS_Kana},
    {kKeyZenkaku, kVK_JIS_Eisu},

    {kKeyMissionControl, s_missionControlVK},
    {kKeyLaunchpad, s_launchpadVK},
    {kKeyBrightnessUp, s_brightnessUp},
    {kKeyBrightnessDown, s_brightnessDown}};

namespace {

io_connect_t getService(io_iterator_t iter) {
  io_connect_t service = 0;
  auto nextIterator = IOIteratorNext(iter);

  if (nextIterator) {
    IOServiceOpen(
        nextIterator, mach_task_self(), kIOHIDParamConnectType, &service);
    IOObjectRelease(nextIterator);
  }

  return service;
}

io_connect_t getEventDriver() {
  static io_connect_t sEventDrvrRef = 0;

  if (!sEventDrvrRef) {
    // Get master device port
    mach_port_t masterPort = 0;
    if (!IOMasterPort(bootstrap_port, &masterPort)) {
      io_iterator_t iter = 0;
      auto dict = IOServiceMatching(kIOHIDSystemClass);

      if (!IOServiceGetMatchingServices(masterPort, dict, &iter)) {
        sEventDrvrRef = getService(iter);
      } else {
        LOG((CLOG_WARN, "io service not found"));
      }

      IOObjectRelease(iter);
    } else {
      LOG((CLOG_WARN, "couldn't get io master port"));
    }
  }

  return sEventDrvrRef;
}

bool isModifier(UInt8 virtualKey) {
  static std::set<UInt8> modifiers{
      s_shiftVK, s_superVK, s_altVK, s_controlVK, s_capsLockVK};

  return (modifiers.find(virtualKey) != modifiers.end());
}

} // namespace

//
// OSXKeyState
//

OSXKeyState::OSXKeyState(
    IEventQueue *events, std::vector<String> layouts, bool isLangSyncEnabled)
    : KeyState(events, std::move(layouts), isLangSyncEnabled) {
  init();
}

OSXKeyState::OSXKeyState(
    IEventQueue *events, deskflow::KeyMap &keyMap, std::vector<String> layouts,
    bool isLangSyncEnabled)
    : KeyState(events, keyMap, std::move(layouts), isLangSyncEnabled) {
  init();
}

OSXKeyState::~OSXKeyState() {}

void OSXKeyState::init() {
  m_deadKeyState = 0;
  m_shiftPressed = false;
  m_controlPressed = false;
  m_altPressed = false;
  m_superPressed = false;
  m_capsPressed = false;

  // build virtual key map
  for (size_t i = 0; i < sizeof(s_controlKeys) / sizeof(s_controlKeys[0]);
       ++i) {

    m_virtualKeyMap[s_controlKeys[i].m_virtualKey] = s_controlKeys[i].m_keyID;
  }
}

KeyModifierMask OSXKeyState::mapModifiersFromOSX(UInt32 mask) const {
  KeyModifierMask outMask = 0;
  if ((mask & kCGEventFlagMaskShift) != 0) {
    outMask |= KeyModifierShift;
  }
  if ((mask & kCGEventFlagMaskControl) != 0) {
    outMask |= KeyModifierControl;
  }
  if ((mask & kCGEventFlagMaskAlternate) != 0) {
    outMask |= KeyModifierAlt;
  }
  if ((mask & kCGEventFlagMaskCommand) != 0) {
    outMask |= KeyModifierSuper;
  }
  if ((mask & kCGEventFlagMaskAlphaShift) != 0) {
    outMask |= KeyModifierCapsLock;
  }
  if ((mask & kCGEventFlagMaskNumericPad) != 0) {
    outMask |= KeyModifierNumLock;
  }

  LOG((CLOG_DEBUG1 "mask=%04x outMask=%04x", mask, outMask));
  return outMask;
}

KeyModifierMask OSXKeyState::mapModifiersToCarbon(UInt32 mask) const {
  KeyModifierMask outMask = 0;
  if ((mask & kCGEventFlagMaskShift) != 0) {
    outMask |= shiftKey;
  }
  if ((mask & kCGEventFlagMaskControl) != 0) {
    outMask |= controlKey;
  }
  if ((mask & kCGEventFlagMaskCommand) != 0) {
    outMask |= cmdKey;
  }
  if ((mask & kCGEventFlagMaskAlternate) != 0) {
    outMask |= optionKey;
  }
  if ((mask & kCGEventFlagMaskAlphaShift) != 0) {
    outMask |= alphaLock;
  }
  if ((mask & kCGEventFlagMaskNumericPad) != 0) {
    outMask |= s_osxNumLock;
  }

  return outMask;
}

KeyButton OSXKeyState::mapKeyFromEvent(
    KeyIDs &ids, KeyModifierMask *maskOut, CGEventRef event) const {
  ids.clear();

  // map modifier key
  if (maskOut != NULL) {
    KeyModifierMask activeMask = getActiveModifiers();
    activeMask &= ~KeyModifierAltGr;
    *maskOut = activeMask;
  }

  // get virtual key
  UInt32 vkCode = CGEventGetIntegerValueField(event, kCGKeyboardEventKeycode);

  // handle up events
  UInt32 eventKind = CGEventGetType(event);
  if (eventKind == kCGEventKeyUp) {
    // the id isn't used.  we just need the same button we used on
    // the key press.  note that we don't use or reset the dead key
    // state;  up events should not affect the dead key state.
    ids.push_back(kKeyNone);
    return mapVirtualKeyToKeyButton(vkCode);
  }

  // check for special keys
  VirtualKeyMap::const_iterator i = m_virtualKeyMap.find(vkCode);
  if (i != m_virtualKeyMap.end()) {
    m_deadKeyState = 0;
    ids.push_back(i->second);
    return mapVirtualKeyToKeyButton(vkCode);
  }

  // get keyboard info
  AutoTISInputSourceRef currentKeyboardLayout(
      TISCopyCurrentKeyboardLayoutInputSource(), CFRelease);

  if (!currentKeyboardLayout) {
    return kKeyNone;
  }

  // get the event modifiers and remove the command and control
  // keys.  note if we used them though.
  // UCKeyTranslate expects old-style Carbon modifiers, so convert.
  UInt32 modifiers;
  modifiers = mapModifiersToCarbon(CGEventGetFlags(event));
  static const UInt32 s_commandModifiers =
      cmdKey | controlKey | rightControlKey;
  bool isCommand = ((modifiers & s_commandModifiers) != 0);
  modifiers &= ~s_commandModifiers;

  // if we've used a command key then we want the glyph produced without
  // the option key (i.e. the base glyph).
  // if (isCommand) {
  modifiers &= ~optionKey;
  //}

  // choose action
  UInt16 action;
  if (eventKind == kCGEventKeyDown) {
    action = kUCKeyActionDown;
  } else if (
      CGEventGetIntegerValueField(event, kCGKeyboardEventAutorepeat) == 1) {
    action = kUCKeyActionAutoKey;
  } else {
    return 0;
  }

  // translate via uchr resource
  CFDataRef ref = (CFDataRef)TISGetInputSourceProperty(
      currentKeyboardLayout.get(), kTISPropertyUnicodeKeyLayoutData);
  const UCKeyboardLayout *layout =
      (const UCKeyboardLayout *)CFDataGetBytePtr(ref);
  const bool layoutValid = (layout != NULL);

  if (layoutValid) {
    // translate key
    UniCharCount count;
    UniChar chars[2];
    LOG((CLOG_DEBUG2 "modifiers: %08x", modifiers & 0xffu));
    OSStatus status = UCKeyTranslate(
        layout, vkCode & 0xffu, action, (modifiers >> 8) & 0xffu,
        LMGetKbdType(), 0, &m_deadKeyState, sizeof(chars) / sizeof(chars[0]),
        &count, chars);

    // get the characters
    if (status == 0) {
      if (count != 0 || m_deadKeyState == 0) {
        m_deadKeyState = 0;
        for (UniCharCount i = 0; i < count; ++i) {
          ids.push_back(IOSXKeyResource::unicharToKeyID(chars[i]));
        }
        adjustAltGrModifier(ids, maskOut, isCommand);
        return mapVirtualKeyToKeyButton(vkCode);
      }
      return 0;
    }
  }

  return 0;
}

bool OSXKeyState::fakeCtrlAltDel() {
  // pass keys through unchanged
  return false;
}

bool OSXKeyState::fakeMediaKey(KeyID id) { return fakeNativeMediaKey(id); }

CGEventFlags OSXKeyState::getModifierStateAsOSXFlags() const {
  CGEventFlags modifiers = 0;

  if (m_shiftPressed) {
    modifiers |= kCGEventFlagMaskShift;
  }

  if (m_controlPressed) {
    modifiers |= kCGEventFlagMaskControl;
  }

  if (m_altPressed) {
    modifiers |= kCGEventFlagMaskAlternate;
  }

  if (m_superPressed) {
    modifiers |= kCGEventFlagMaskCommand;
  }

  if (m_capsPressed) {
    modifiers |= kCGEventFlagMaskAlphaShift;
  }

  return modifiers;
}

KeyModifierMask OSXKeyState::pollActiveModifiers() const {
  // falsely assumed that the mask returned by GetCurrentKeyModifiers()
  // was the same as a CGEventFlags (which is what mapModifiersFromOSX
  // expects). patch by Marc
  UInt32 mask = GetCurrentKeyModifiers();
  KeyModifierMask outMask = 0;

  if ((mask & shiftKey) != 0) {
    outMask |= KeyModifierShift;
  }
  if ((mask & controlKey) != 0) {
    outMask |= KeyModifierControl;
  }
  if ((mask & optionKey) != 0) {
    outMask |= KeyModifierAlt;
  }
  if ((mask & cmdKey) != 0) {
    outMask |= KeyModifierSuper;
  }
  if ((mask & alphaLock) != 0) {
    outMask |= KeyModifierCapsLock;
  }
  if ((mask & s_osxNumLock) != 0) {
    outMask |= KeyModifierNumLock;
  }

  LOG((CLOG_DEBUG1 "mask=%04x outMask=%04x", mask, outMask));
  return outMask;
}

SInt32 OSXKeyState::pollActiveGroup() const {
  AutoTISInputSourceRef keyboardLayout(
      TISCopyCurrentKeyboardLayoutInputSource(), CFRelease);
  CFDataRef id = (CFDataRef)TISGetInputSourceProperty(
      keyboardLayout.get(), kTISPropertyInputSourceID);

  GroupMap::const_iterator i = m_groupMap.find(id);
  if (i != m_groupMap.end()) {
    return i->second;
  }

  LOG((CLOG_WARN "can't get the active group, use the first group instead"));

  return 0;
}

void OSXKeyState::pollPressedKeys(KeyButtonSet &pressedKeys) const {
  ::KeyMap km;
  GetKeys(km);
  const UInt8 *m = reinterpret_cast<const UInt8 *>(km);
  for (UInt32 i = 0; i < 16; ++i) {
    for (UInt32 j = 0; j < 8; ++j) {
      if ((m[i] & (1u << j)) != 0) {
        pressedKeys.insert(mapVirtualKeyToKeyButton(8 * i + j));
      }
    }
  }
}

void OSXKeyState::getKeyMap(deskflow::KeyMap &keyMap) {
  // update keyboard groups
  SInt32 numGroups{0};
  if (getGroups(m_groups)) {
    m_groupMap.clear();
    numGroups = CFArrayGetCount(m_groups.get());
    for (SInt32 g = 0; g < numGroups; ++g) {
      TISInputSourceRef keyboardLayout =
          (TISInputSourceRef)CFArrayGetValueAtIndex(m_groups.get(), g);
      CFDataRef id = (CFDataRef)TISGetInputSourceProperty(
          keyboardLayout, kTISPropertyInputSourceID);
      m_groupMap[id] = g;
    }
  }

  UInt32 keyboardType = LMGetKbdType();
  for (SInt32 g = 0; g < numGroups; ++g) {
    // add special keys
    getKeyMapForSpecialKeys(keyMap, g);

    const void *resource;
    bool layoutValid = false;

    // add regular keys
    // try uchr resource first
    TISInputSourceRef keyboardLayout =
        (TISInputSourceRef)CFArrayGetValueAtIndex(m_groups.get(), g);
    CFDataRef resourceRef = (CFDataRef)TISGetInputSourceProperty(
        keyboardLayout, kTISPropertyUnicodeKeyLayoutData);

    layoutValid = resourceRef != NULL;
    if (layoutValid)
      resource = CFDataGetBytePtr(resourceRef);

    if (layoutValid) {
      OSXUchrKeyResource uchr(resource, keyboardType);
      if (uchr.isValid()) {
        LOG((CLOG_DEBUG1 "using uchr resource for group %d", g));
        getKeyMap(keyMap, g, uchr);
        continue;
      }
    }

    LOG((CLOG_DEBUG1 "no keyboard resource for group %d", g));
  }
}

CGEventFlags OSXKeyState::getDeviceDependedFlags() const {
  CGEventFlags modifiers = 0;

  if (m_shiftPressed) {
    modifiers |= NX_DEVICELSHIFTKEYMASK;
  }

  if (m_controlPressed) {
    modifiers |= NX_DEVICELCTLKEYMASK;
  }

  if (m_altPressed) {
    modifiers |= NX_DEVICELALTKEYMASK;
  }

  if (m_superPressed) {
    modifiers |= NX_DEVICELCMDKEYMASK;
  }

  return modifiers;
}

CGEventFlags OSXKeyState::getKeyboardEventFlags() const {
  // set the event flags for special keys
  // http://tinyurl.com/pxl742y
  CGEventFlags modifiers = getModifierStateAsOSXFlags();

  if (!m_capsPressed) {
    modifiers |= getDeviceDependedFlags();
  }

  return modifiers;
}

void OSXKeyState::setKeyboardModifiers(CGKeyCode virtualKey, bool keyDown) {
  switch (virtualKey) {
  case s_shiftVK:
    m_shiftPressed = keyDown;
    break;
  case s_controlVK:
    m_controlPressed = keyDown;
    break;
  case s_altVK:
    m_altPressed = keyDown;
    break;
  case s_superVK:
    m_superPressed = keyDown;
    break;
  case s_capsLockVK:
    m_capsPressed = keyDown;
    break;
  default:
    LOG((CLOG_DEBUG1 "the key is not a modifier"));
    break;
  }
}

kern_return_t OSXKeyState::postHIDVirtualKey(UInt8 virtualKey, bool postDown) {
  NXEventData event;
  bzero(&event, sizeof(NXEventData));
  auto driver = getEventDriver();
  kern_return_t result = KERN_FAILURE;

  if (driver) {
    if (isModifier(virtualKey)) {
      result = IOHIDPostEvent(
          driver, NX_FLAGSCHANGED, {0, 0}, &event, kNXEventDataVersion,
          getKeyboardEventFlags(), true);
    } else {
      event.key.keyCode = virtualKey;
      const auto eventType = postDown ? NX_KEYDOWN : NX_KEYUP;
      result = IOHIDPostEvent(
          driver, eventType, {0, 0}, &event, kNXEventDataVersion, 0, false);
    }
  }

  return result;
}

void OSXKeyState::postKeyboardKey(CGKeyCode virtualKey, bool keyDown) {
  CGEventRef event = CGEventCreateKeyboardEvent(nullptr, virtualKey, keyDown);
  if (event) {
    CGEventSetFlags(event, getKeyboardEventFlags());
    CGEventPost(kCGHIDEventTap, event);
    CFRelease(event);
  } else {
    LOG((CLOG_CRIT "unable to create keyboard event for keystroke"));
  }
}

void OSXKeyState::fakeKey(const Keystroke &keystroke) {
  switch (keystroke.m_type) {
  case Keystroke::kButton: {
    bool keyDown = keystroke.m_data.m_button.m_press;
    UInt32 client = keystroke.m_data.m_button.m_client;
    KeyButton button = keystroke.m_data.m_button.m_button;
    CGKeyCode virtualKey = mapKeyButtonToVirtualKey(button);

    LOG(
        (CLOG_DEBUG1
         "  button=0x%04x virtualKey=0x%04x keyDown=%s client=0x%04x",
         button, virtualKey, keyDown ? "down" : "up", client));

    setKeyboardModifiers(virtualKey, keyDown);
    if (postHIDVirtualKey(virtualKey, keyDown) != KERN_SUCCESS) {
      LOG((CLOG_WARN, "fail to post hid event"));
      postKeyboardKey(virtualKey, keyDown);
    }

    break;
  }

  case Keystroke::kGroup: {
    SInt32 group = keystroke.m_data.m_group.m_group;
    if (!keystroke.m_data.m_group.m_restore) {
      if (keystroke.m_data.m_group.m_absolute) {
        LOG((CLOG_DEBUG1 "  group %d", group));
        setGroup(group);
      } else {
        LOG((CLOG_DEBUG1 "  group %+d", group));
        setGroup(getEffectiveGroup(pollActiveGroup(), group));
      }

      if (pollActiveGroup() != group) {
        LOG((CLOG_WARN "failed to set new keyboard layout"));
      }
    }
    break;
  }
  }
}

void OSXKeyState::getKeyMapForSpecialKeys(
    deskflow::KeyMap &keyMap, SInt32 group) const {
  // special keys are insensitive to modifers and none are dead keys
  deskflow::KeyMap::KeyItem item;
  for (size_t i = 0; i < sizeof(s_controlKeys) / sizeof(s_controlKeys[0]);
       ++i) {
    const KeyEntry &entry = s_controlKeys[i];
    item.m_id = entry.m_keyID;
    item.m_group = group;
    item.m_button = mapVirtualKeyToKeyButton(entry.m_virtualKey);
    item.m_required = 0;
    item.m_sensitive = 0;
    item.m_dead = false;
    item.m_client = 0;
    deskflow::KeyMap::initModifierKey(item);
    keyMap.addKeyEntry(item);

    if (item.m_lock) {
      // all locking keys are half duplex on OS X
      keyMap.addHalfDuplexButton(item.m_button);
    }
  }

  // note:  we don't special case the number pad keys.  querying the
  // mac keyboard returns the non-keypad version of those keys but
  // a KeyState always provides a mapping from keypad keys to
  // non-keypad keys so we'll be able to generate the characters
  // anyway.
}

bool OSXKeyState::getKeyMap(
    deskflow::KeyMap &keyMap, SInt32 group, const IOSXKeyResource &r) const {
  if (!r.isValid()) {
    return false;
  }

  // space for all possible modifier combinations
  std::vector<bool> modifiers(r.getNumModifierCombinations());

  // make space for the keys that any single button can synthesize
  std::vector<std::pair<KeyID, bool>> buttonKeys(r.getNumTables());

  // iterate over each button
  deskflow::KeyMap::KeyItem item;
  for (UInt32 i = 0; i < r.getNumButtons(); ++i) {
    item.m_button = mapVirtualKeyToKeyButton(i);

    // the KeyIDs we've already handled
    std::set<KeyID> keys;

    // convert the entry in each table for this button to a KeyID
    for (UInt32 j = 0; j < r.getNumTables(); ++j) {
      buttonKeys[j].first = r.getKey(j, i);
      buttonKeys[j].second = deskflow::KeyMap::isDeadKey(buttonKeys[j].first);
    }

    // iterate over each character table
    for (UInt32 j = 0; j < r.getNumTables(); ++j) {
      // get the KeyID for the button/table
      KeyID id = buttonKeys[j].first;
      if (id == kKeyNone) {
        continue;
      }

      // if we've already handled the KeyID in the table then
      // move on to the next table
      if (keys.count(id) > 0) {
        continue;
      }
      keys.insert(id);

      // prepare item.  the client state is 1 for dead keys.
      item.m_id = id;
      item.m_group = group;
      item.m_dead = buttonKeys[j].second;
      item.m_client = buttonKeys[j].second ? 1 : 0;
      deskflow::KeyMap::initModifierKey(item);
      if (item.m_lock) {
        // all locking keys are half duplex on OS X
        keyMap.addHalfDuplexButton(i);
      }

      // collect the tables that map to the same KeyID.  we know it
      // can't be any earlier tables because of the check above.
      std::set<UInt8> tables;
      tables.insert(static_cast<UInt8>(j));
      for (UInt32 k = j + 1; k < r.getNumTables(); ++k) {
        if (buttonKeys[k].first == id) {
          tables.insert(static_cast<UInt8>(k));
        }
      }

      // collect the modifier combinations that map to any of the
      // tables we just collected
      for (UInt32 k = 0; k < r.getNumModifierCombinations(); ++k) {
        modifiers[k] = (tables.count(r.getTableForModifier(k)) > 0);
      }

      // figure out which modifiers the key is sensitive to.  the
      // key is insensitive to a modifier if for every modifier mask
      // with the modifier bit unset in the modifiers we also find
      // the same mask with the bit set.
      //
      // we ignore a few modifiers that we know aren't important
      // for generating characters.  in fact, we want to ignore any
      // characters generated by the control key.  we don't map
      // those and instead expect the control modifier plus a key.
      UInt32 sensitive = 0;
      for (UInt32 k = 0; (1u << k) < r.getNumModifierCombinations(); ++k) {
        UInt32 bit = (1u << k);
        if ((bit << 8) == cmdKey || (bit << 8) == controlKey ||
            (bit << 8) == rightControlKey) {
          continue;
        }
        for (UInt32 m = 0; m < r.getNumModifierCombinations(); ++m) {
          if (modifiers[m] != modifiers[m ^ bit]) {
            sensitive |= bit;
            break;
          }
        }
      }

      // find each required modifier mask.  the key can be synthesized
      // using any of the masks.
      std::set<UInt32> required;
      for (UInt32 k = 0; k < r.getNumModifierCombinations(); ++k) {
        if ((k & sensitive) == k && modifiers[k & sensitive]) {
          required.insert(k);
        }
      }

      // now add a key entry for each key/required modifier pair.
      item.m_sensitive = mapModifiersFromOSX(sensitive << 16);
      for (std::set<UInt32>::iterator k = required.begin(); k != required.end();
           ++k) {
        item.m_required = mapModifiersFromOSX(*k << 16);
        keyMap.addKeyEntry(item);
      }
    }
  }

  return true;
}

bool OSXKeyState::mapDeskflowHotKeyToMac(
    KeyID key, KeyModifierMask mask, UInt32 &macVirtualKey,
    UInt32 &macModifierMask) const {
  // look up button for key
  KeyButton button = getButton(key, pollActiveGroup());
  if (button == 0 && key != kKeyNone) {
    return false;
  }
  macVirtualKey = mapKeyButtonToVirtualKey(button);

  // calculate modifier mask
  macModifierMask = 0;
  if ((mask & KeyModifierShift) != 0) {
    macModifierMask |= shiftKey;
  }
  if ((mask & KeyModifierControl) != 0) {
    macModifierMask |= controlKey;
  }
  if ((mask & KeyModifierAlt) != 0) {
    macModifierMask |= cmdKey;
  }
  if ((mask & KeyModifierSuper) != 0) {
    macModifierMask |= optionKey;
  }
  if ((mask & KeyModifierCapsLock) != 0) {
    macModifierMask |= alphaLock;
  }
  if ((mask & KeyModifierNumLock) != 0) {
    macModifierMask |= s_osxNumLock;
  }

  return true;
}

void OSXKeyState::handleModifierKeys(
    void *target, KeyModifierMask oldMask, KeyModifierMask newMask) {
  // compute changed modifiers
  KeyModifierMask changed = (oldMask ^ newMask);

  // synthesize changed modifier keys
  if ((changed & KeyModifierShift) != 0) {
    handleModifierKey(
        target, s_shiftVK, kKeyShift_L, (newMask & KeyModifierShift) != 0,
        newMask);
  }
  if ((changed & KeyModifierControl) != 0) {
    handleModifierKey(
        target, s_controlVK, kKeyControl_L, (newMask & KeyModifierControl) != 0,
        newMask);
  }
  if ((changed & KeyModifierAlt) != 0) {
    handleModifierKey(
        target, s_altVK, kKeyAlt_L, (newMask & KeyModifierAlt) != 0, newMask);
  }
  if ((changed & KeyModifierSuper) != 0) {
    handleModifierKey(
        target, s_superVK, kKeySuper_L, (newMask & KeyModifierSuper) != 0,
        newMask);
  }
  if ((changed & KeyModifierCapsLock) != 0) {
    handleModifierKey(
        target, s_capsLockVK, kKeyCapsLock,
        (newMask & KeyModifierCapsLock) != 0, newMask);
  }
  if ((changed & KeyModifierNumLock) != 0) {
    handleModifierKey(
        target, s_numLockVK, kKeyNumLock, (newMask & KeyModifierNumLock) != 0,
        newMask);
  }
}

void OSXKeyState::handleModifierKey(
    void *target, UInt32 virtualKey, KeyID id, bool down,
    KeyModifierMask newMask) {
  KeyButton button = mapVirtualKeyToKeyButton(virtualKey);
  onKey(button, down, newMask);
  sendKeyEvent(target, down, false, id, newMask, 0, button);
}

bool OSXKeyState::getGroups(AutoCFArray &groups) const {
  // get number of layouts
  CFStringRef keys[] = {kTISPropertyInputSourceCategory};
  CFStringRef values[] = {kTISCategoryKeyboardInputSource};
  AutoCFDictionary dict(
      CFDictionaryCreate(
          NULL, (const void **)keys, (const void **)values, 1, NULL, NULL),
      CFRelease);
  AutoCFArray kbds(TISCreateInputSourceList(dict.get(), false), CFRelease);

  if (CFArrayGetCount(kbds.get()) > 0) {
    groups = std::move(kbds);
  } else {
    LOG((CLOG_DEBUG1 "can't get keyboard layouts"));
    return false;
  }

  return true;
}

void OSXKeyState::setGroup(SInt32 group) {
  TISInputSourceRef keyboardLayout =
      (TISInputSourceRef)CFArrayGetValueAtIndex(m_groups.get(), group);
  if (!keyboardLayout) {
    LOG((CLOG_WARN "nedeed keyboard layout is null"));
    return;
  }
  auto canBeSetted = (CFBooleanRef)TISGetInputSourceProperty(
      TISCopyCurrentKeyboardInputSource(),
      kTISPropertyInputSourceIsEnableCapable);
  if (!canBeSetted) {
    LOG((CLOG_WARN
         "nedeed keyboard layout is disabled for programmatically selection"));
    return;
  }

  if (TISSelectInputSource(keyboardLayout) != noErr) {
    LOG((CLOG_WARN "failed to set nedeed keyboard layout"));
  }

  LOG((CLOG_DEBUG1 "keyboard layout change to %d", group));

  // A minimal delay is needed after a group change because the
  // keyboard key event often happens immediately after.
  // Language (TIS) and event (CG) systems are not in the mutual
  // event queue and without a delay the subsequent key press
  // event could be applied before the keyboard layout would
  // actually be changed.
  ARCH->sleep(.01);
}

void OSXKeyState::adjustAltGrModifier(
    const KeyIDs &ids, KeyModifierMask *mask, bool isCommand) const {
  if (!isCommand) {
    for (KeyIDs::const_iterator i = ids.begin(); i != ids.end(); ++i) {
      KeyID id = *i;
      if (id != kKeyNone && ((id < 0xe000u || id > 0xefffu) ||
                             (id >= kKeyKP_Equal && id <= kKeyKP_9))) {
        *mask |= KeyModifierAltGr;
        return;
      }
    }
  }
}

KeyButton OSXKeyState::mapVirtualKeyToKeyButton(UInt32 keyCode) {
  // 'A' maps to 0 so shift every id
  return static_cast<KeyButton>(keyCode + KeyButtonOffset);
}

UInt32 OSXKeyState::mapKeyButtonToVirtualKey(KeyButton keyButton) {
  return static_cast<UInt32>(keyButton - KeyButtonOffset);
}
