{ CocoaKeyInput

  Copyright (C) 2019 Sammarco Francesco

  This source is free software; you can redistribute it and/or modify it under the terms of the
  GNU General Public License as published by the Free Software Foundation; either version 2 of the
  License, or (at your option) any later version.

  This code 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.

  A copy of the GNU General Public License is available on the World Wide Web at
  <http://www.gnu.org/copyleft/gpl.html>. You can also obtain it by writing to the Free Software
  Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1335, USA.
}
unit CocoaKeyInput;

{$mode objfpc}{$H+}

interface

uses
  MacOSAll,
  Classes, SysUtils, Controls, Forms,
  Cocoa_Extra, CGEventSource, CGEvent, CGEventTypes, CGRemoteOperation, CFBase,
  KeyInputIntf;

type

  { TCocoaKeyInput }

  TCocoaKeyInput = class(TKeyInput)
  protected
    procedure DoDown(Key: Word); override;
    procedure DoUp(Key: Word); override;
  end;

function InitializeKeyInput: TKeyInput;


implementation

uses
  LCLType;

function InitializeKeyInput: TKeyInput;
begin
  Result := TCocoaKeyInput.Create;
end;

function VKToMacCode(AKey: Word): Word;
begin
  case AKey of
    VK_A : Result :=  kVK_ANSI_A;
    VK_S : Result :=  kVK_ANSI_S;
    VK_D : Result :=  kVK_ANSI_D;
    VK_F : Result :=  kVK_ANSI_F;
    VK_H : Result :=  kVK_ANSI_H;
    VK_G : Result :=  kVK_ANSI_G;
    VK_Z : Result :=  kVK_ANSI_Z;
    VK_X : Result :=  kVK_ANSI_X;
    VK_C : Result :=  kVK_ANSI_C;
    VK_V : Result :=  kVK_ANSI_V;

    //kVK_ISO_Section   // ISO keyboard only

    VK_B : Result := kVK_ANSI_B;
    VK_Q : Result := kVK_ANSI_Q;
    VK_W : Result := kVK_ANSI_W;
    VK_E : Result := kVK_ANSI_E;
    VK_R : Result := kVK_ANSI_R;
    VK_Y : Result := kVK_ANSI_Y;
    VK_T : Result := kVK_ANSI_T;
    VK_1 : Result := kVK_ANSI_1;
    VK_2 : Result := kVK_ANSI_2;
    VK_3 : Result := kVK_ANSI_3;
    VK_4 : Result := kVK_ANSI_4;
    VK_6 : Result := kVK_ANSI_6;
    VK_5 : Result := kVK_ANSI_5;
    VK_LCL_EQUAL          : Result := kVK_ANSI_Equal; // aka VK_EQUAL = 187 = $BB;
    VK_9                  : Result := kVK_ANSI_9;
    VK_7                  : Result := kVK_ANSI_7;
    VK_OEM_MINUS          : Result := kVK_ANSI_Minus;
    VK_8                  : Result := kVK_ANSI_8;
    VK_0                  : Result := kVK_ANSI_0;
    VK_OEM_6              : Result := kVK_ANSI_RightBracket;
    VK_O                  : Result := kVK_ANSI_O;
    VK_U                  : Result := kVK_ANSI_U;
    VK_LCL_OPEN_BRAKET    : Result := kVK_ANSI_LeftBracket;
    VK_I                  : Result := kVK_ANSI_I;
    VK_P                  : Result := kVK_ANSI_P;

    VK_RETURN             : Result := kVK_Return;

    VK_L                  : Result := kVK_ANSI_L;
    VK_J                  : Result := kVK_ANSI_J;
    VK_LCL_QUOTE          : Result := kVK_ANSI_Quote;
    VK_K                  : Result := kVK_ANSI_K;
    VK_LCL_SEMI_COMMA     : Result := kVK_ANSI_Semicolon;
    VK_LCL_BACKSLASH      : Result := kVK_ANSI_Backslash;
    VK_LCL_COMMA          : Result := kVK_ANSI_Comma;
    VK_LCL_SLASH          : Result := kVK_ANSI_Slash;
    VK_N                  : Result := kVK_ANSI_N;
    VK_M                  : Result := kVK_ANSI_M;
    VK_LCL_POINT          : Result := kVK_ANSI_Period;

    VK_TAB                : Result := kVK_Tab;
    VK_SPACE              : Result := kVK_Space;

    VK_LCL_TILDE          : Result := kVK_ANSI_Grave;

    VK_BACK               : Result := kVK_Delete;
    VK_DELETE             : Result := kVK_Delete; //Added from Francesco Sammarco

    VK_ESCAPE             : Result := kVK_Escape;
    VK_LWIN               : Result := kVK_Command;
    // todo: Application.ExtendedKeysSupport must be true!

    VK_SHIFT             : Result := kVK_Shift;   //Added from Francesco Sammarco
    VK_MENU              : Result := kVK_Option;  //Added from Francesco Sammarco
    VK_CONTROL           : Result := kVK_Control; //Added from Francesco Sammarco


    VK_LSHIFT             : Result := kVK_Shift;
    VK_CAPITAL            : Result := kVK_CapsLock;
    VK_LMENU              : Result := kVK_Option;
    VK_LCONTROL           : Result := kVK_Control;
    VK_RSHIFT             : Result := kVK_RightShift;
    VK_RMENU              : Result := kVK_RightOption;
    VK_RCONTROL           : Result := kVK_RightControl;
    //kVK_Function          : Result := VK_; todo:
    VK_F17                : Result := kVK_F17;

    VK_DECIMAL              : Result := kVK_ANSI_KeypadDecimal;
    VK_MULTIPLY             : Result := kVK_ANSI_KeypadMultiply;
    VK_ADD                  : Result := kVK_ANSI_KeypadPlus;
    VK_NUMLOCK              : Result := kVK_ANSI_KeypadClear;

    VK_VOLUME_UP            : Result := kVK_VolumeUp;
    VK_VOLUME_DOWN          : Result := kVK_VolumeDown;
    VK_VOLUME_MUTE          : Result := kVK_Mute;

    VK_DIVIDE               : Result := kVK_ANSI_KeypadDivide;
    //VK_RETURN               : Result := kVK_ANSI_KeypadEnter;  //Commented from Francesco Sammarco (duplicate VK_RETURN)
    VK_SUBTRACT             : Result := kVK_ANSI_KeypadMinus;

    VK_F18         : Result := kVK_F18;
    VK_F19         : Result := kVK_F19;

    //kVK_ANSI_KeypadEquals : Result := VK_;
    VK_NUMPAD0      : Result := kVK_ANSI_Keypad0;
    VK_NUMPAD1      : Result := kVK_ANSI_Keypad1;
    VK_NUMPAD2      : Result := kVK_ANSI_Keypad2;
    VK_NUMPAD3      : Result := kVK_ANSI_Keypad3;
    VK_NUMPAD4      : Result := kVK_ANSI_Keypad4;
    VK_NUMPAD5      : Result := kVK_ANSI_Keypad5;
    VK_NUMPAD6      : Result := kVK_ANSI_Keypad6;
    VK_NUMPAD7      : Result := kVK_ANSI_Keypad7;

    VK_F20  : Result := kVK_F20;

    VK_NUMPAD8 : Result := kVK_ANSI_Keypad8;
    VK_NUMPAD9 : Result := kVK_ANSI_Keypad9;

    //kVK_JIS_Yen                   = $5D;
    //kVK_JIS_Underscore            = $5E;
    //kVK_JIS_KeypadComma           = $5F;

    VK_F5           : Result := kVK_F5;
    VK_F6           : Result := kVK_F6;
    VK_F7           : Result := kVK_F7;
    VK_F3           : Result := kVK_F3;
    VK_F8           : Result := kVK_F8;
    VK_F9           : Result := kVK_F9;

    //kVK_JIS_Eisu                  = $66;
    VK_KANA         : Result := kVK_JIS_Kana;

    VK_F11          : Result := kVK_F11;

    VK_SNAPSHOT           : Result := kVK_F13;
    VK_F16                : Result := kVK_F16;
    VK_SCROLL             : Result := kVK_F14;
    VK_F10                : Result := kVK_F10;

    VK_APPS               : Result := kVK_SubMenu;

    VK_F12                : Result := kVK_F12;
    VK_PAUSE              : Result := kVK_F15;
    VK_HELP               : Result := kVK_Help; //VK_INSERT; // todo!
    VK_HOME               : Result := kVK_Home;
    VK_PRIOR              : Result := kVK_PageUp;
    //VK_DELETE             : Result := kVK_ForwardDelete; // Commented from Francesco Sammarco
    VK_F4                 : Result := kVK_F4;
    VK_END                : Result := kVK_End;
    VK_F2                 : Result := kVK_F2;
    VK_NEXT               : Result := kVK_PageDown;
    VK_F1                 : Result := kVK_F1;
    VK_LEFT               : Result := kVK_LeftArrow;
    VK_RIGHT              : Result := kVK_RightArrow;
    VK_DOWN               : Result := kVK_DownArrow;
    VK_UP                 : Result := kVK_UpArrow;

  else
    Result := VK_UNKNOWN;
  end;
end;

procedure SendKeyInput(Key: Word; Down: Boolean);
var
  EventSourceRef  : CGEventSourceRef;
  KeyDownRef, KeyUpRef: CGEventRef;
begin

  EventSourceRef := CGEventSourceCreate(kCGEventSourceStateHIDSystemState);
  if EventSourceRef = nil then RaiseLastOSError;
  try

    if Down then
    begin
      KeyDownRef := CGEventCreateKeyboardEvent(EventSourceRef, VKToMacCode(Key), 1); //1 = key down
      if KeyDownRef = nil then RaiseLastOSError;
      CGEventSetFlags(KeyDownRef, CGEventGetFlags(KeyDownRef)); //combine flags
      CGEventPost(kCGHIDEventTap, KeyDownRef);
    end else begin
      KeyUpRef := CGEventCreateKeyboardEvent(EventSourceRef, VKToMacCode(Key), 0);  //0 = key up
      if KeyUpRef = nil then RaiseLastOSError;
      CGEventSetFlags(KeyDownRef, CGEventGetFlags(KeyDownRef)); //combine flags
      CGEventPost(kCGHIDEventTap, KeyUpRef);
    end;

  finally
    if Down then
       if KeyDownRef <> nil then CFRelease(KeyDownRef)
    else
        if KeyUpRef <> nil then CFRelease(KeyUpRef);
    CFRelease(EventSourceRef);
  end;

end;

{ TCocoaKeyInput }
procedure TCocoaKeyInput.DoDown(Key: Word);
begin
  SendKeyInput(Key, True);
end;

procedure TCocoaKeyInput.DoUp(Key: Word);
begin
  SendKeyInput(Key, False);
end;

end.

