You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

keyboardshortcut.js 7.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. /* global APP, $ */
  2. import Logger from 'jitsi-meet-logger';
  3. import {
  4. ACTION_SHORTCUT_PRESSED as PRESSED,
  5. ACTION_SHORTCUT_RELEASED as RELEASED,
  6. createShortcutEvent,
  7. sendAnalytics
  8. } from '../../react/features/analytics';
  9. import { toggleDialog } from '../../react/features/base/dialog';
  10. import { clickOnVideo } from '../../react/features/filmstrip/actions';
  11. import { KeyboardShortcutsDialog }
  12. from '../../react/features/keyboard-shortcuts';
  13. import { SpeakerStats } from '../../react/features/speaker-stats';
  14. const logger = Logger.getLogger(__filename);
  15. /**
  16. * Map of shortcuts. When a shortcut is registered it enters the mapping.
  17. * @type {Map}
  18. */
  19. const _shortcuts = new Map();
  20. /**
  21. * Map of registered keyboard keys and translation keys describing the
  22. * action performed by the key.
  23. * @type {Map}
  24. */
  25. const _shortcutsHelp = new Map();
  26. /**
  27. * True if the keyboard shortcuts are enabled and false if not.
  28. * @type {boolean}
  29. */
  30. let enabled = true;
  31. /**
  32. * Maps keycode to character, id of popover for given function and function.
  33. */
  34. const KeyboardShortcut = {
  35. init() {
  36. this._initGlobalShortcuts();
  37. window.onkeyup = e => {
  38. if (!enabled) {
  39. return;
  40. }
  41. const key = this._getKeyboardKey(e).toUpperCase();
  42. const num = parseInt(key, 10);
  43. if (!($(':focus').is('input[type=text]')
  44. || $(':focus').is('input[type=password]')
  45. || $(':focus').is('textarea'))) {
  46. if (_shortcuts.has(key)) {
  47. _shortcuts.get(key).function(e);
  48. } else if (!isNaN(num) && num >= 0 && num <= 9) {
  49. APP.store.dispatch(clickOnVideo(num));
  50. }
  51. }
  52. };
  53. window.onkeydown = e => {
  54. if (!enabled) {
  55. return;
  56. }
  57. if (!($(':focus').is('input[type=text]')
  58. || $(':focus').is('input[type=password]')
  59. || $(':focus').is('textarea'))) {
  60. if (this._getKeyboardKey(e).toUpperCase() === ' ') {
  61. if (APP.conference.isLocalAudioMuted()) {
  62. sendAnalytics(createShortcutEvent(
  63. 'push.to.talk',
  64. PRESSED));
  65. logger.log('Talk shortcut pressed');
  66. APP.conference.muteAudio(false);
  67. }
  68. }
  69. }
  70. };
  71. },
  72. /**
  73. * Enables/Disables the keyboard shortcuts.
  74. * @param {boolean} value - the new value.
  75. */
  76. enable(value) {
  77. enabled = value;
  78. },
  79. /**
  80. * Opens the {@KeyboardShortcutsDialog} dialog.
  81. *
  82. * @returns {void}
  83. */
  84. openDialog() {
  85. APP.store.dispatch(toggleDialog(KeyboardShortcutsDialog, {
  86. shortcutDescriptions: _shortcutsHelp
  87. }));
  88. },
  89. /**
  90. * Registers a new shortcut.
  91. *
  92. * @param shortcutChar the shortcut character triggering the action
  93. * @param shortcutAttr the "shortcut" html element attribute mapping an
  94. * element to this shortcut and used to show the shortcut character on the
  95. * element tooltip
  96. * @param exec the function to be executed when the shortcut is pressed
  97. * @param helpDescription the description of the shortcut that would appear
  98. * in the help menu
  99. */
  100. registerShortcut(// eslint-disable-line max-params
  101. shortcutChar,
  102. shortcutAttr,
  103. exec,
  104. helpDescription) {
  105. _shortcuts.set(shortcutChar, {
  106. character: shortcutChar,
  107. function: exec,
  108. shortcutAttr
  109. });
  110. if (helpDescription) {
  111. this._addShortcutToHelp(shortcutChar, helpDescription);
  112. }
  113. },
  114. /**
  115. * Unregisters a shortcut.
  116. *
  117. * @param shortcutChar unregisters the given shortcut, which means it will
  118. * no longer be usable
  119. */
  120. unregisterShortcut(shortcutChar) {
  121. _shortcuts.delete(shortcutChar);
  122. _shortcutsHelp.delete(shortcutChar);
  123. },
  124. /**
  125. * @param e a KeyboardEvent
  126. * @returns {string} e.key or something close if not supported
  127. */
  128. _getKeyboardKey(e) {
  129. // If e.key is a string, then it is assumed it already plainly states
  130. // the key pressed. This may not be true in all cases, such as with Edge
  131. // and "?", when the browser cannot properly map a key press event to a
  132. // keyboard key. To be safe, when a key is "Unidentified" it must be
  133. // further analyzed by jitsi to a key using e.which.
  134. if (typeof e.key === 'string' && e.key !== 'Unidentified') {
  135. return e.key;
  136. }
  137. if (e.type === 'keypress'
  138. && ((e.which >= 32 && e.which <= 126)
  139. || (e.which >= 160 && e.which <= 255))) {
  140. return String.fromCharCode(e.which);
  141. }
  142. // try to fallback (0-9A-Za-z and QWERTY keyboard)
  143. switch (e.which) {
  144. case 27:
  145. return 'Escape';
  146. case 191:
  147. return e.shiftKey ? '?' : '/';
  148. }
  149. if (e.shiftKey || e.type === 'keypress') {
  150. return String.fromCharCode(e.which);
  151. }
  152. return String.fromCharCode(e.which).toLowerCase();
  153. },
  154. /**
  155. * Adds the given shortcut to the help dialog.
  156. *
  157. * @param shortcutChar the shortcut character
  158. * @param shortcutDescriptionKey the description of the shortcut
  159. * @private
  160. */
  161. _addShortcutToHelp(shortcutChar, shortcutDescriptionKey) {
  162. _shortcutsHelp.set(shortcutChar, shortcutDescriptionKey);
  163. },
  164. /**
  165. * Initialise global shortcuts.
  166. * Global shortcuts are shortcuts for features that don't have a button or
  167. * link associated with the action. In other words they represent actions
  168. * triggered _only_ with a shortcut.
  169. */
  170. _initGlobalShortcuts() {
  171. this.registerShortcut('?', null, () => {
  172. sendAnalytics(createShortcutEvent('help'));
  173. this.openDialog();
  174. }, 'keyboardShortcuts.toggleShortcuts');
  175. // register SPACE shortcut in two steps to insure visibility of help
  176. // message
  177. this.registerShortcut(' ', null, () => {
  178. sendAnalytics(createShortcutEvent('push.to.talk', RELEASED));
  179. logger.log('Talk shortcut released');
  180. APP.conference.muteAudio(true);
  181. });
  182. this._addShortcutToHelp('SPACE', 'keyboardShortcuts.pushToTalk');
  183. this.registerShortcut('T', null, () => {
  184. sendAnalytics(createShortcutEvent('speaker.stats'));
  185. APP.store.dispatch(toggleDialog(SpeakerStats, {
  186. conference: APP.conference
  187. }));
  188. }, 'keyboardShortcuts.showSpeakerStats');
  189. /**
  190. * FIXME: Currently focus keys are directly implemented below in
  191. * onkeyup. They should be moved to the SmallVideo instead.
  192. */
  193. this._addShortcutToHelp('0', 'keyboardShortcuts.focusLocal');
  194. this._addShortcutToHelp('1-9', 'keyboardShortcuts.focusRemote');
  195. }
  196. };
  197. export default KeyboardShortcut;