BrowserInput.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. #if UNITY_EDITOR_OSX || UNITY_STANDALONE_OSX
  2. #define ZF_OSX
  3. #endif
  4. #if UNITY_EDITOR_LINX || UNITY_STANDALONE_LINUX
  5. #define ZF_LINUX
  6. #endif
  7. using System.Collections.Generic;
  8. using System.Runtime.InteropServices;
  9. using UnityEngine;
  10. namespace ZenFulcrum.EmbeddedBrowser {
  11. /** Helper class for reading data from an IUIHandler, converting it, and feeding it to the native backend. */
  12. internal class BrowserInput {
  13. private readonly Browser browser;
  14. public BrowserInput(Browser browser) {
  15. this.browser = browser;
  16. }
  17. private bool kbWasFocused = false;
  18. private bool mouseWasFocused = false;
  19. public void HandleInput() {
  20. browser.UIHandler.InputUpdate();
  21. bool focusChanged = false;
  22. if (browser.UIHandler.MouseHasFocus || mouseWasFocused) {
  23. HandleMouseInput();
  24. }
  25. if (browser.UIHandler.MouseHasFocus != mouseWasFocused) {
  26. browser.UIHandler.BrowserCursor.HasMouse = browser.UIHandler.MouseHasFocus;
  27. focusChanged = true;
  28. }
  29. mouseWasFocused = browser.UIHandler.MouseHasFocus;
  30. Input.imeCompositionMode = IMECompositionMode.On;
  31. if (kbWasFocused != browser.UIHandler.KeyboardHasFocus) focusChanged = true;
  32. if (browser.UIHandler.KeyboardHasFocus) {
  33. if (!kbWasFocused) {
  34. BrowserNative.zfb_setFocused(browser.browserId, kbWasFocused = true);
  35. }
  36. HandleKeyInput();
  37. } else {
  38. if (kbWasFocused) {
  39. BrowserNative.zfb_setFocused(browser.browserId, kbWasFocused = false);
  40. }
  41. }
  42. if (focusChanged) {
  43. browser._RaiseFocusEvent(browser.UIHandler.MouseHasFocus, browser.UIHandler.KeyboardHasFocus);
  44. }
  45. }
  46. private static HashSet<KeyCode> keysToReleaseOnFocusLoss = new HashSet<KeyCode>();
  47. public List<Event> extraEventsToInject = new List<Event>();
  48. private MouseButton prevButtons = 0;
  49. private Vector2 prevPos;
  50. private class ButtonHistory {
  51. public float lastPressTime;
  52. public int repeatCount;
  53. public Vector3 lastPosition;
  54. public void ButtonPress(Vector3 mousePos, IBrowserUI uiHandler, Vector2 browserSize) {
  55. var now = Time.realtimeSinceStartup;
  56. if (now - lastPressTime > uiHandler.InputSettings.multiclickSpeed) {
  57. //too long ago? forget the past
  58. repeatCount = 0;
  59. }
  60. if (repeatCount > 0) {
  61. //close enough to be a multiclick?
  62. var p1 = Vector2.Scale(mousePos, browserSize);
  63. var p2 = Vector2.Scale(lastPosition, browserSize);
  64. if (Vector2.Distance(p1, p2) > uiHandler.InputSettings.multiclickTolerance) {
  65. repeatCount = 0;
  66. }
  67. }
  68. repeatCount++;
  69. lastPressTime = now;
  70. lastPosition = mousePos;
  71. }
  72. }
  73. private readonly ButtonHistory leftClickHistory = new ButtonHistory();
  74. private void HandleMouseInput() {
  75. var handler = browser.UIHandler;
  76. var mousePos = handler.MousePosition;
  77. var currentButtons = handler.MouseButtons;
  78. var mouseScroll = handler.MouseScroll;
  79. if (mousePos != prevPos) {
  80. BrowserNative.zfb_mouseMove(browser.browserId, mousePos.x, 1 - mousePos.y);
  81. }
  82. FeedScrolling(mouseScroll, handler.InputSettings.scrollSpeed);
  83. var leftChange = (prevButtons & MouseButton.Left) != (currentButtons & MouseButton.Left);
  84. var leftDown = (currentButtons & MouseButton.Left) == MouseButton.Left;
  85. var middleChange = (prevButtons & MouseButton.Middle) != (currentButtons & MouseButton.Middle);
  86. var middleDown = (currentButtons & MouseButton.Middle) == MouseButton.Middle;
  87. var rightChange = (prevButtons & MouseButton.Right) != (currentButtons & MouseButton.Right);
  88. var rightDown = (currentButtons & MouseButton.Right) == MouseButton.Right;
  89. if (leftChange) {
  90. if (leftDown) leftClickHistory.ButtonPress(mousePos, handler, browser.Size);
  91. BrowserNative.zfb_mouseButton(
  92. browser.browserId, BrowserNative.MouseButton.MBT_LEFT, leftDown,
  93. leftDown ? leftClickHistory.repeatCount : 0
  94. );
  95. }
  96. if (middleChange) {
  97. //no double-clicks, to be consistent with other browsers
  98. BrowserNative.zfb_mouseButton(
  99. browser.browserId, BrowserNative.MouseButton.MBT_MIDDLE, middleDown, 1
  100. );
  101. }
  102. if (rightChange) {
  103. //no double-clicks, to be consistent with other browsers
  104. BrowserNative.zfb_mouseButton(
  105. browser.browserId, BrowserNative.MouseButton.MBT_RIGHT, rightDown, 1
  106. );
  107. }
  108. prevPos = mousePos;
  109. prevButtons = currentButtons;
  110. }
  111. private Vector2 accumulatedScroll;
  112. private float lastScrollEvent;
  113. /// <summary>
  114. /// How often (in sec) we can send scroll events to the browser without it choking up.
  115. /// The right number seems to depend on how hard the page is to render, so there's not a perfect number.
  116. /// Hopefully this one works well, though.
  117. /// </summary>
  118. private const float maxScrollEventRate = 1 / 15f;
  119. /// <summary>
  120. /// Feeds scroll events to the browser.
  121. /// In particular, it will clump together scrolling "floods" into fewer larger scrolls
  122. /// to prevent the backend from getting choked up and taking forever to execute the requests.
  123. /// </summary>
  124. /// <param name="mouseScroll"></param>
  125. private void FeedScrolling(Vector2 mouseScroll, float scrollSpeed) {
  126. accumulatedScroll += mouseScroll * scrollSpeed;
  127. if (accumulatedScroll.sqrMagnitude != 0 && Time.realtimeSinceStartup > lastScrollEvent + maxScrollEventRate) {
  128. //Debug.Log("Do scroll: " + accumulatedScroll);
  129. //The backend seems to have trouble coping with horizontal AND vertical scroll. So only do one at a time.
  130. //(And if we do both at once, vertical appears to get priority and horizontal gets ignored.)
  131. if (Mathf.Abs(accumulatedScroll.x) > Mathf.Abs(accumulatedScroll.y)) {
  132. BrowserNative.zfb_mouseScroll(browser.browserId, (int)accumulatedScroll.x, 0);
  133. accumulatedScroll.x = 0;
  134. accumulatedScroll.y = Mathf.Round(accumulatedScroll.y * .5f);//reduce the thing we weren't doing so it's less likely to accumulate strange
  135. } else {
  136. BrowserNative.zfb_mouseScroll(browser.browserId, 0, (int)accumulatedScroll.y);
  137. accumulatedScroll.x = Mathf.Round(accumulatedScroll.x * .5f);
  138. accumulatedScroll.y = 0;
  139. }
  140. lastScrollEvent = Time.realtimeSinceStartup;
  141. }
  142. }
  143. private void HandleKeyInput() {
  144. var keyEvents = browser.UIHandler.KeyEvents;
  145. if (keyEvents.Count > 0) HandleKeyInput(keyEvents);
  146. if (extraEventsToInject.Count > 0) {
  147. HandleKeyInput(extraEventsToInject);
  148. extraEventsToInject.Clear();
  149. }
  150. }
  151. private void HandleKeyInput(List<Event> keyEvents) {
  152. #if ZF_OSX
  153. ReconstructInputs(keyEvents);
  154. #endif
  155. foreach (var ev in keyEvents) {
  156. var keyCode = KeyMappings.GetWindowsKeyCode(ev);
  157. if (ev.character == '\n') ev.character = '\r';//'cuz that's what Chromium expects
  158. if (ev.character == 0) {
  159. if (ev.type == EventType.KeyDown) keysToReleaseOnFocusLoss.Add(ev.keyCode);
  160. else keysToReleaseOnFocusLoss.Remove(ev.keyCode);
  161. }
  162. // if (false) {
  163. // if (ev.character != 0) Debug.Log("k >>> " + ev.character);
  164. // else if (ev.type == EventType.KeyUp) Debug.Log("k ^^^ " + ev.keyCode);
  165. // else if (ev.type == EventType.KeyDown) Debug.Log("k vvv " + ev.keyCode);
  166. // }
  167. FireCommands(ev);
  168. if (ev.character != 0 && ev.type == EventType.KeyDown) {
  169. #if ZF_LINUX
  170. //It seems, on Linux, we don't get keydown, keypress, keyup, we just get a keypress, keyup.
  171. //So, fire the keydown just before the keypress.
  172. BrowserNative.zfb_keyEvent(browser.browserId, true, keyCode);
  173. //Thanks for being consistent, Unity.
  174. #endif
  175. BrowserNative.zfb_characterEvent(browser.browserId, ev.character, keyCode);
  176. } else {
  177. BrowserNative.zfb_keyEvent(browser.browserId, ev.type == EventType.KeyDown, keyCode);
  178. }
  179. }
  180. }
  181. public void HandleFocusLoss() {
  182. foreach (var keyCode in keysToReleaseOnFocusLoss) {
  183. //Debug.Log("Key " + keyCode + " is held, release");
  184. var wCode = KeyMappings.GetWindowsKeyCode(new Event() { keyCode = keyCode });
  185. BrowserNative.zfb_keyEvent(browser.browserId, false, wCode);
  186. }
  187. keysToReleaseOnFocusLoss.Clear();
  188. }
  189. #if ZF_OSX
  190. /** Used by ReconstructInputs */
  191. protected HashSet<KeyCode> pressedKeys = new HashSet<KeyCode>();
  192. /**
  193. * OS X + Unity has issues.
  194. *
  195. * Mac editor: If I press cmd+A: The "keydown A" event doesn't get sent,
  196. * though we do get a keypress A and a keyup A.
  197. * Mac player: We get duplicate keyUPs normally. If cmd is down we get duplicate keyDOWNs instead.
  198. */
  199. protected void ReconstructInputs(List<Event> keyEvents) {
  200. for (int i = 0; i < keyEvents.Count; ++i) {//int loop, not iterator, we mutate during iteration
  201. var ev = keyEvents[i];
  202. if (ev.type == EventType.KeyDown && ev.character == 0) {
  203. pressedKeys.Add(ev.keyCode);
  204. //Repeated keydown events sent in the same frame are always bogus (but valid if in different
  205. //frames for held key repeats)
  206. //Remove duplicated key down events in this tick.
  207. for (int j = i + 1; j < keyEvents.Count; ++j) {
  208. if (keyEvents[j].Equals(ev)) keyEvents.RemoveAt(j--);
  209. }
  210. } else if (ev.type == EventType.KeyDown) {
  211. //key down with character.
  212. //...did the key actually get pressed, though?
  213. if (ev.keyCode != KeyCode.None && !pressedKeys.Contains(ev.keyCode)) {
  214. //no. insert a keydown before the press
  215. var downEv = new Event(ev) {
  216. type = EventType.KeyDown,
  217. character = (char)0
  218. };
  219. keyEvents.Insert(i++, downEv);
  220. pressedKeys.Add(ev.keyCode);
  221. }
  222. } else if (ev.type == EventType.KeyUp) {
  223. if (!pressedKeys.Contains(ev.keyCode)) {
  224. //Ignore duplicate key up events
  225. keyEvents.RemoveAt(i--);
  226. }
  227. pressedKeys.Remove(ev.keyCode);
  228. }
  229. }
  230. }
  231. #endif
  232. /**
  233. * OS X + Unity has issues.
  234. * Commands won't be run if the command is not in the application menu.
  235. * Here we trap keystrokes and manually fire the relevant events in the browser.
  236. *
  237. * Also, ctrl+A stopped working with CEF at some point on Windows.
  238. */
  239. protected void FireCommands(Event ev) {
  240. #if ZF_OSX
  241. if (ev.type != EventType.KeyDown || ev.character != 0 || !ev.command) return;
  242. switch (ev.keyCode) {
  243. case KeyCode.C:
  244. browser.SendFrameCommand(BrowserNative.FrameCommand.Copy);
  245. break;
  246. case KeyCode.X:
  247. browser.SendFrameCommand(BrowserNative.FrameCommand.Cut);
  248. break;
  249. case KeyCode.V:
  250. browser.SendFrameCommand(BrowserNative.FrameCommand.Paste);
  251. break;
  252. case KeyCode.A:
  253. browser.SendFrameCommand(BrowserNative.FrameCommand.SelectAll);
  254. break;
  255. case KeyCode.Z:
  256. if (ev.shift) browser.SendFrameCommand(BrowserNative.FrameCommand.Redo);
  257. else browser.SendFrameCommand(BrowserNative.FrameCommand.Undo);
  258. break;
  259. case KeyCode.Y:
  260. //I, for one, prefer Y for redo, but shift+Z is more idiomatic on OS X
  261. //Support both.
  262. browser.SendFrameCommand(BrowserNative.FrameCommand.Redo);
  263. break;
  264. }
  265. #else
  266. //mmm, yeah. I guess Unity doesn't send us the keydown on a ctrl+a keystroke anymore.
  267. if (ev.type != EventType.KeyUp || !ev.control) return;
  268. switch (ev.keyCode) {
  269. case KeyCode.A:
  270. browser.SendFrameCommand(BrowserNative.FrameCommand.SelectAll);
  271. break;
  272. }
  273. #endif
  274. }
  275. }
  276. }