UnityView+Keyboard.mm 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. #import "UnityView.h"
  2. #include "UI/Keyboard.h"
  3. #include <sys/time.h>
  4. #include <map>
  5. #include <vector>
  6. static NSArray* keyboardCommands = nil;
  7. extern "C" int UnityGetAppleTVRemoteAllowExitToMenu();
  8. extern "C" void UnitySetAppleTVRemoteAllowExitToMenu(int val);
  9. @implementation UnityView (Keyboard)
  10. // Keyboard shortcuts don't provide events for key up
  11. // Keyboard shortcut callbacks are called with 0.4 (first time) and 0.1 (following times) seconds interval while pressing the key
  12. // Below we implement key expiration mechanism where key up event is generated if shortcut callback
  13. // is not called for specific key for more than <kKeyTimeoutInSeconds>
  14. typedef std::map<int, double> KeyMap;
  15. static const double kKeyTimeoutInSeconds = 0.5;
  16. static KeyMap& GetKeyMap()
  17. {
  18. static KeyMap s_Map;
  19. return s_Map;
  20. }
  21. static double GetTimeInSeconds()
  22. {
  23. timeval now;
  24. gettimeofday(&now, NULL);
  25. return now.tv_sec + now.tv_usec / 1000000.0;
  26. }
  27. - (void)createKeyboard
  28. {
  29. // only English keyboard layout is supported
  30. NSString* baseLayout = @"1234567890-=qwertyuiop[]asdfghjkl;'\\`zxcvbnm,./!@#$%^&*()_+{}:\"|<>?~ \t\r\b\\";
  31. NSString* numpadLayout = @"1234567890-=*+/.\r";
  32. NSString* upperCaseLetters = @"QWERTYUIOPASDFGHJKLZXCVBNM";
  33. NSString* shortcutCharacters = @"axcv";
  34. size_t sizeOfKeyboardCommands = baseLayout.length + numpadLayout.length + upperCaseLetters.length + 11;
  35. NSMutableArray* commands = [NSMutableArray arrayWithCapacity: sizeOfKeyboardCommands];
  36. void (^addKey)(NSString *keyName, UIKeyModifierFlags modifierFlags) = ^(NSString *keyName, UIKeyModifierFlags modifierFlags)
  37. {
  38. UIKeyCommand* command = [UIKeyCommand keyCommandWithInput: keyName modifierFlags: modifierFlags action: @selector(handleCommand:)];
  39. if (@available(iOS 15.0, tvOS 15.0, *))
  40. command.wantsPriorityOverSystemBehavior = YES;
  41. [commands addObject: command];
  42. };
  43. for (NSInteger i = 0; i < baseLayout.length; ++i)
  44. {
  45. NSString* input = [baseLayout substringWithRange: NSMakeRange(i, 1)];
  46. addKey(input, kNilOptions);
  47. }
  48. for (NSInteger i = 0; i < shortcutCharacters.length; ++i)
  49. {
  50. NSString* input = [shortcutCharacters substringWithRange: NSMakeRange(i, 1)];
  51. [commands addObject: [UIKeyCommand keyCommandWithInput: input modifierFlags: UIKeyModifierCommand action: @selector(handleCommand:)]];
  52. }
  53. for (NSInteger i = 0; i < numpadLayout.length; ++i)
  54. {
  55. NSString* input = [numpadLayout substringWithRange: NSMakeRange(i, 1)];
  56. addKey(input, UIKeyModifierNumericPad);
  57. }
  58. for (NSInteger i = 0; i < upperCaseLetters.length; ++i)
  59. {
  60. NSString* input = [upperCaseLetters substringWithRange: NSMakeRange(i, 1)];
  61. addKey(input, UIKeyModifierShift);
  62. }
  63. // pageUp, pageDown
  64. addKey(UIKeyInputPageUp, kNilOptions);
  65. addKey(UIKeyInputPageDown, kNilOptions);
  66. // up, down, left, right, esc
  67. addKey(UIKeyInputUpArrow, kNilOptions);
  68. addKey(UIKeyInputDownArrow, kNilOptions);
  69. addKey(UIKeyInputLeftArrow, kNilOptions);
  70. addKey(UIKeyInputRightArrow, kNilOptions);
  71. addKey(UIKeyInputEscape, kNilOptions);
  72. // caps Lock, shift, control, option, command
  73. addKey(@"", UIKeyModifierAlphaShift);
  74. addKey(@"", UIKeyModifierShift);
  75. addKey(@"", UIKeyModifierControl);
  76. addKey(@"", UIKeyModifierAlternate);
  77. addKey(@"", UIKeyModifierCommand);
  78. keyboardCommands = commands.copy;
  79. }
  80. - (NSArray*)keyCommands
  81. {
  82. //keyCommands take control of buttons over UITextView, that's why need to return nil if text input field is active or we have an external keyboard attached AND a first responder
  83. if ([[KeyboardDelegate Instance] status] == Visible || ([[KeyboardDelegate Instance] hasExternalKeyboard] && [self hasFirstResponderInHeirachy: UnityGetGLView()]))
  84. return nil;
  85. if (keyboardCommands == nil)
  86. {
  87. [self createKeyboard];
  88. }
  89. return keyboardCommands;
  90. }
  91. - (bool)hasFirstResponderInHeirachy:(UIView*)view
  92. {
  93. if (view.isFirstResponder)
  94. return true;
  95. for (UIView* subview in view.subviews)
  96. {
  97. if ([self hasFirstResponderInHeirachy: subview])
  98. return true;
  99. }
  100. return false;
  101. }
  102. - (bool)isValidCodeForButton:(int)code
  103. {
  104. return (code > 0 && code < 128);
  105. }
  106. - (void)handleCommand:(UIKeyCommand *)command
  107. {
  108. NSString* input = command.input;
  109. UIKeyModifierFlags modifierFlags = command.modifierFlags;
  110. char inputChar = ([input length] == 1) ? [input characterAtIndex: 0] : 0;
  111. int code = (int)inputChar; // ASCII code
  112. if (![self isValidCodeForButton: code])
  113. {
  114. code = 0;
  115. }
  116. if ((modifierFlags & UIKeyModifierAlphaShift) != 0)
  117. code = UnityStringToKey("caps lock");
  118. if ((modifierFlags & UIKeyModifierShift) != 0)
  119. code = UnityStringToKey("left shift");
  120. if ((modifierFlags & UIKeyModifierControl) != 0)
  121. code = UnityStringToKey("left ctrl");
  122. if ((modifierFlags & UIKeyModifierAlternate) != 0)
  123. code = UnityStringToKey("left alt");
  124. if ((modifierFlags & UIKeyModifierCommand) != 0)
  125. code = UnityStringToKey("left cmd");
  126. if ((modifierFlags & UIKeyModifierNumericPad) != 0)
  127. {
  128. switch (inputChar)
  129. {
  130. case '0':
  131. code = UnityStringToKey("[0]");
  132. break;
  133. case '1':
  134. code = UnityStringToKey("[1]");
  135. break;
  136. case '2':
  137. code = UnityStringToKey("[2]");
  138. break;
  139. case '3':
  140. code = UnityStringToKey("[3]");
  141. break;
  142. case '4':
  143. code = UnityStringToKey("[4]");
  144. break;
  145. case '5':
  146. code = UnityStringToKey("[5]");
  147. break;
  148. case '6':
  149. code = UnityStringToKey("[6]");
  150. break;
  151. case '7':
  152. code = UnityStringToKey("[7]");
  153. break;
  154. case '8':
  155. code = UnityStringToKey("[8]");
  156. break;
  157. case '9':
  158. code = UnityStringToKey("[9]");
  159. break;
  160. case '-':
  161. code = UnityStringToKey("[-]");
  162. break;
  163. case '=':
  164. code = UnityStringToKey("equals");
  165. break;
  166. case '*':
  167. code = UnityStringToKey("[*]");
  168. break;
  169. case '+':
  170. code = UnityStringToKey("[+]");
  171. break;
  172. case '/':
  173. code = UnityStringToKey("[/]");
  174. break;
  175. case '.':
  176. code = UnityStringToKey("[.]");
  177. break;
  178. case '\r':
  179. code = UnityStringToKey("enter");
  180. break;
  181. default:
  182. break;
  183. }
  184. }
  185. if (input == UIKeyInputUpArrow)
  186. code = UnityStringToKey("up");
  187. else if (input == UIKeyInputDownArrow)
  188. code = UnityStringToKey("down");
  189. else if (input == UIKeyInputRightArrow)
  190. code = UnityStringToKey("right");
  191. else if (input == UIKeyInputLeftArrow)
  192. code = UnityStringToKey("left");
  193. else if (input == UIKeyInputEscape)
  194. code = UnityStringToKey("escape");
  195. else if ([input isEqualToString: @"UIKeyInputPageUp"])
  196. code = UnityStringToKey("page up");
  197. else if ([input isEqualToString: @"UIKeyInputPageDown"])
  198. code = UnityStringToKey("page down");
  199. UnitySendKeyboardCommand(command, code);
  200. KeyMap::iterator item = GetKeyMap().find(code);
  201. if (item == GetKeyMap().end())
  202. {
  203. // New key is down, register it and its time
  204. UnitySetKeyboardKeyState(code, true);
  205. GetKeyMap()[code] = GetTimeInSeconds();
  206. }
  207. else
  208. {
  209. // Still holding the key, update its time
  210. item->second = GetTimeInSeconds();
  211. }
  212. }
  213. - (void)processKeyboard
  214. {
  215. KeyMap& map = GetKeyMap();
  216. if (map.size() == 0)
  217. return;
  218. std::vector<int> keysToUnpress;
  219. double nowTime = GetTimeInSeconds();
  220. for (KeyMap::iterator item = map.begin();
  221. item != map.end();
  222. item++)
  223. {
  224. // Key has expired, register it for key up event
  225. if (nowTime - item->second > kKeyTimeoutInSeconds)
  226. keysToUnpress.push_back(item->first);
  227. }
  228. for (std::vector<int>::iterator item = keysToUnpress.begin();
  229. item != keysToUnpress.end();
  230. item++)
  231. {
  232. map.erase(*item);
  233. UnitySetKeyboardKeyState(*item, false);
  234. }
  235. }
  236. @end