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.2KB

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