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.

actions.js 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. // @flow
  2. import { openDialog } from '../base/dialog';
  3. import { JitsiConferenceEvents } from '../base/lib-jitsi-meet';
  4. import { getParticipantDisplayName, getPinnedParticipant, pinParticipant } from '../base/participants';
  5. import { getLocalVideoTrack } from '../base/tracks';
  6. import { showNotification } from '../notifications';
  7. import {
  8. CAPTURE_EVENTS,
  9. REMOTE_CONTROL_ACTIVE,
  10. SET_REQUESTED_PARTICIPANT,
  11. SET_CONTROLLER,
  12. SET_RECEIVER_ENABLED,
  13. SET_RECEIVER_TRANSPORT,
  14. SET_CONTROLLED_PARTICIPANT
  15. } from './actionTypes';
  16. import { RemoteControlAuthorizationDialog } from './components';
  17. import {
  18. DISCO_REMOTE_CONTROL_FEATURE,
  19. EVENTS,
  20. REMOTE_CONTROL_MESSAGE_NAME,
  21. PERMISSIONS_ACTIONS,
  22. REQUESTS
  23. } from './constants';
  24. import {
  25. getKey,
  26. getModifiers,
  27. getRemoteConrolEventCaptureArea,
  28. isRemoteControlEnabled,
  29. sendRemoteControlEndpointMessage
  30. } from './functions';
  31. import logger from './logger';
  32. /**
  33. * Listeners.
  34. */
  35. let permissionsReplyListener, receiverEndpointMessageListener, stopListener;
  36. declare var APP: Object;
  37. declare var $: Function;
  38. /**
  39. * Signals that the remote control authorization dialog should be displayed.
  40. *
  41. * @param {string} participantId - The id of the participant who is requesting
  42. * the authorization.
  43. * @returns {{
  44. * type: OPEN_DIALOG,
  45. * component: {RemoteControlAuthorizationDialog},
  46. * componentProps: {
  47. * participantId: {string}
  48. * }
  49. * }}
  50. * @public
  51. */
  52. export function openRemoteControlAuthorizationDialog(participantId: string) {
  53. return openDialog(RemoteControlAuthorizationDialog, { participantId });
  54. }
  55. /**
  56. * Sets the remote control active property.
  57. *
  58. * @param {boolean} active - The new value for the active property.
  59. * @returns {Function}
  60. */
  61. export function setRemoteControlActive(active: boolean) {
  62. return (dispatch: Function, getState: Function) => {
  63. const state = getState();
  64. const { active: oldActive } = state['features/remote-control'];
  65. const { conference } = state['features/base/conference'];
  66. if (active !== oldActive) {
  67. dispatch({
  68. type: REMOTE_CONTROL_ACTIVE,
  69. active
  70. });
  71. conference.setLocalParticipantProperty('remoteControlSessionStatus', active);
  72. }
  73. };
  74. }
  75. /**
  76. * Requests permissions from the remote control receiver side.
  77. *
  78. * @param {string} userId - The user id of the participant that will be
  79. * requested.
  80. * @returns {Function}
  81. */
  82. export function requestRemoteControl(userId: string) {
  83. return (dispatch: Function, getState: Function) => {
  84. const state = getState();
  85. const enabled = isRemoteControlEnabled(state);
  86. if (!enabled) {
  87. return Promise.reject(new Error('Remote control is disabled!'));
  88. }
  89. dispatch(setRemoteControlActive(true));
  90. logger.log(`Requsting remote control permissions from: ${userId}`);
  91. const { conference } = state['features/base/conference'];
  92. permissionsReplyListener = (participant, event) => {
  93. dispatch(processPermissionRequestReply(participant.getId(), event));
  94. };
  95. conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, permissionsReplyListener);
  96. dispatch({
  97. type: SET_REQUESTED_PARTICIPANT,
  98. requestedParticipant: userId
  99. });
  100. if (!sendRemoteControlEndpointMessage(
  101. conference,
  102. userId,
  103. {
  104. type: EVENTS.permissions,
  105. action: PERMISSIONS_ACTIONS.request
  106. })) {
  107. dispatch(clearRequest());
  108. }
  109. };
  110. }
  111. /**
  112. * Handles permission request replies on the controller side.
  113. *
  114. * @param {string} participantId - The participant that sent the request.
  115. * @param {EndpointMessage} event - The permission request event.
  116. * @returns {Function}
  117. */
  118. export function processPermissionRequestReply(participantId: string, event: Object) {
  119. return (dispatch: Function, getState: Function) => {
  120. const state = getState();
  121. const { action, name, type } = event;
  122. const { requestedParticipant } = state['features/remote-control'].controller;
  123. if (isRemoteControlEnabled(state) && name === REMOTE_CONTROL_MESSAGE_NAME && type === EVENTS.permissions
  124. && participantId === requestedParticipant) {
  125. let descriptionKey, permissionGranted = false;
  126. switch (action) {
  127. case PERMISSIONS_ACTIONS.grant: {
  128. dispatch({
  129. type: SET_CONTROLLED_PARTICIPANT,
  130. controlled: participantId
  131. });
  132. logger.log('Remote control permissions granted!', participantId);
  133. logger.log('Starting remote control controller.');
  134. const { conference } = state['features/base/conference'];
  135. stopListener = (participant, stopEvent) => {
  136. dispatch(handleRemoteControlStoppedEvent(participant.getId(), stopEvent));
  137. };
  138. conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, stopListener);
  139. dispatch(resume());
  140. permissionGranted = true;
  141. descriptionKey = 'dialog.remoteControlAllowedMessage';
  142. break;
  143. }
  144. case PERMISSIONS_ACTIONS.deny:
  145. logger.log('Remote control permissions denied!', participantId);
  146. descriptionKey = 'dialog.remoteControlDeniedMessage';
  147. break;
  148. case PERMISSIONS_ACTIONS.error:
  149. logger.error('Error occurred on receiver side');
  150. descriptionKey = 'dialog.remoteControlErrorMessage';
  151. break;
  152. default:
  153. logger.error('Unknown reply received!');
  154. descriptionKey = 'dialog.remoteControlErrorMessage';
  155. }
  156. dispatch(clearRequest());
  157. if (!permissionGranted) {
  158. dispatch(setRemoteControlActive(false));
  159. }
  160. dispatch(showNotification({
  161. descriptionArguments: { user: getParticipantDisplayName(state, participantId) },
  162. descriptionKey,
  163. titleKey: 'dialog.remoteControlTitle'
  164. }));
  165. if (permissionGranted) {
  166. // the remote control permissions has been granted
  167. // pin the controlled participant
  168. const pinnedParticipant = getPinnedParticipant(state);
  169. const pinnedId = pinnedParticipant?.id;
  170. if (pinnedId !== participantId) {
  171. dispatch(pinParticipant(participantId));
  172. }
  173. }
  174. } else {
  175. // different message type or another user -> ignoring the message
  176. }
  177. };
  178. }
  179. /**
  180. * Handles remote control stopped.
  181. *
  182. * @param {string} participantId - The ID of the participant that has sent the event.
  183. * @param {EndpointMessage} event - EndpointMessage event from the data channels.
  184. * @property {string} type - The function process only events with name REMOTE_CONTROL_MESSAGE_NAME.
  185. * @returns {void}
  186. */
  187. export function handleRemoteControlStoppedEvent(participantId: Object, event: Object) {
  188. return (dispatch: Function, getState: Function) => {
  189. const state = getState();
  190. const { name, type } = event;
  191. const { controlled } = state['features/remote-control'].controller;
  192. if (isRemoteControlEnabled(state) && name === REMOTE_CONTROL_MESSAGE_NAME && type === EVENTS.stop
  193. && participantId === controlled) {
  194. dispatch(stopController());
  195. }
  196. };
  197. }
  198. /**
  199. * Stops processing the mouse and keyboard events. Removes added listeners.
  200. * Enables the keyboard shortcuts. Displays dialog to notify the user that remote control session has ended.
  201. *
  202. * @param {boolean} notifyRemoteParty - If true a endpoint message to the controlled participant will be sent.
  203. * @returns {void}
  204. */
  205. export function stopController(notifyRemoteParty: boolean = false) {
  206. return (dispatch: Function, getState: Function) => {
  207. const state = getState();
  208. const { controlled } = state['features/remote-control'].controller;
  209. if (!controlled) {
  210. return;
  211. }
  212. const { conference } = state['features/base/conference'];
  213. if (notifyRemoteParty) {
  214. sendRemoteControlEndpointMessage(conference, controlled, {
  215. type: EVENTS.stop
  216. });
  217. }
  218. logger.log('Stopping remote control controller.');
  219. conference.off(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, stopListener);
  220. stopListener = undefined;
  221. dispatch(pause());
  222. dispatch({
  223. type: SET_CONTROLLED_PARTICIPANT,
  224. controlled: undefined
  225. });
  226. dispatch(setRemoteControlActive(false));
  227. dispatch(showNotification({
  228. descriptionKey: 'dialog.remoteControlStopMessage',
  229. titleKey: 'dialog.remoteControlTitle'
  230. }));
  231. };
  232. }
  233. /**
  234. * Clears a pending permission request.
  235. *
  236. * @returns {Function}
  237. */
  238. export function clearRequest() {
  239. return (dispatch: Function, getState: Function) => {
  240. const { conference } = getState()['features/base/conference'];
  241. dispatch({
  242. type: SET_REQUESTED_PARTICIPANT,
  243. requestedParticipant: undefined
  244. });
  245. conference.off(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, permissionsReplyListener);
  246. permissionsReplyListener = undefined;
  247. };
  248. }
  249. /**
  250. * Sets that transport object that is used by the receiver to communicate with the native part of the remote control
  251. * implementation.
  252. *
  253. * @param {Transport} transport - The transport to be set.
  254. * @returns {{
  255. * type: SET_RECEIVER_TRANSPORT,
  256. * transport: Transport
  257. * }}
  258. */
  259. export function setReceiverTransport(transport: Object) {
  260. return {
  261. type: SET_RECEIVER_TRANSPORT,
  262. transport
  263. };
  264. }
  265. /**
  266. * Enables the receiver functionality.
  267. *
  268. * @returns {Function}
  269. */
  270. export function enableReceiver() {
  271. return (dispatch: Function, getState: Function) => {
  272. const state = getState();
  273. const { enabled } = state['features/remote-control'].receiver;
  274. if (enabled) {
  275. return;
  276. }
  277. const { connection } = state['features/base/connection'];
  278. const { conference } = state['features/base/conference'];
  279. if (!connection || !conference) {
  280. logger.error('Couldn\'t enable the remote receiver! The connection or conference instance is undefined!');
  281. return;
  282. }
  283. dispatch({
  284. type: SET_RECEIVER_ENABLED,
  285. enabled: true
  286. });
  287. connection.addFeature(DISCO_REMOTE_CONTROL_FEATURE, true);
  288. receiverEndpointMessageListener = (participant, message) => {
  289. dispatch(endpointMessageReceived(participant.getId(), message));
  290. };
  291. conference.on(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, receiverEndpointMessageListener);
  292. };
  293. }
  294. /**
  295. * Disables the receiver functionality.
  296. *
  297. * @returns {Function}
  298. */
  299. export function disableReceiver() {
  300. return (dispatch: Function, getState: Function) => {
  301. const state = getState();
  302. const { enabled } = state['features/remote-control'].receiver;
  303. if (!enabled) {
  304. return;
  305. }
  306. const { connection } = state['features/base/connection'];
  307. const { conference } = state['features/base/conference'];
  308. if (!connection || !conference) {
  309. logger.error('Couldn\'t enable the remote receiver! The connection or conference instance is undefined!');
  310. return;
  311. }
  312. logger.log('Remote control receiver disabled.');
  313. dispatch({
  314. type: SET_RECEIVER_ENABLED,
  315. enabled: false
  316. });
  317. dispatch(stopReceiver(true));
  318. connection.removeFeature(DISCO_REMOTE_CONTROL_FEATURE);
  319. conference.off(JitsiConferenceEvents.ENDPOINT_MESSAGE_RECEIVED, receiverEndpointMessageListener);
  320. };
  321. }
  322. /**
  323. * Stops a remote control session on the receiver side.
  324. *
  325. * @param {boolean} [dontNotifyLocalParty] - If true - a notification about stopping
  326. * the remote control won't be displayed.
  327. * @param {boolean} [dontNotifyRemoteParty] - If true a endpoint message to the controller participant will be sent.
  328. * @returns {Function}
  329. */
  330. export function stopReceiver(dontNotifyLocalParty: boolean = false, dontNotifyRemoteParty: boolean = false) {
  331. return (dispatch: Function, getState: Function) => {
  332. const state = getState();
  333. const { receiver } = state['features/remote-control'];
  334. const { controller, transport } = receiver;
  335. if (!controller) {
  336. return;
  337. }
  338. const { conference } = state['features/base/conference'];
  339. if (!dontNotifyRemoteParty) {
  340. sendRemoteControlEndpointMessage(conference, controller, {
  341. type: EVENTS.stop
  342. });
  343. }
  344. dispatch({
  345. type: SET_CONTROLLER,
  346. controller: undefined
  347. });
  348. transport.sendEvent({
  349. name: REMOTE_CONTROL_MESSAGE_NAME,
  350. type: EVENTS.stop
  351. });
  352. dispatch(setRemoteControlActive(false));
  353. if (!dontNotifyLocalParty) {
  354. dispatch(showNotification({
  355. descriptionKey: 'dialog.remoteControlStopMessage',
  356. titleKey: 'dialog.remoteControlTitle'
  357. }));
  358. }
  359. };
  360. }
  361. /**
  362. * Handles only remote control endpoint messages.
  363. *
  364. * @param {string} participantId - The controller participant ID.
  365. * @param {Object} message - EndpointMessage from the data channels.
  366. * @param {string} message.name - The function processes only messages with
  367. * name REMOTE_CONTROL_MESSAGE_NAME.
  368. * @returns {Function}
  369. */
  370. export function endpointMessageReceived(participantId: string, message: Object) {
  371. return (dispatch: Function, getState: Function) => {
  372. const { action, name, type } = message;
  373. if (name !== REMOTE_CONTROL_MESSAGE_NAME) {
  374. return;
  375. }
  376. const state = getState();
  377. const { receiver } = state['features/remote-control'];
  378. const { enabled, transport } = receiver;
  379. if (enabled) {
  380. const { controller } = receiver;
  381. if (!controller && type === EVENTS.permissions && action === PERMISSIONS_ACTIONS.request) {
  382. dispatch(setRemoteControlActive(true));
  383. dispatch(openRemoteControlAuthorizationDialog(participantId));
  384. } else if (controller === participantId) {
  385. if (type === EVENTS.stop) {
  386. dispatch(stopReceiver(false, true));
  387. } else { // forward the message
  388. transport.sendEvent(message);
  389. }
  390. } // else ignore
  391. } else {
  392. logger.log('Remote control message is ignored because remote control is disabled', message);
  393. }
  394. };
  395. }
  396. /**
  397. * Denies remote control access for user associated with the passed user id.
  398. *
  399. * @param {string} participantId - The id associated with the user who sent the
  400. * request for remote control authorization.
  401. * @returns {Function}
  402. */
  403. export function deny(participantId: string) {
  404. return (dispatch: Function, getState: Function) => {
  405. const state = getState();
  406. const { conference } = state['features/base/conference'];
  407. dispatch(setRemoteControlActive(false));
  408. sendRemoteControlEndpointMessage(conference, participantId, {
  409. type: EVENTS.permissions,
  410. action: PERMISSIONS_ACTIONS.deny
  411. });
  412. };
  413. }
  414. /**
  415. * Sends start remote control request to the native implementation.
  416. *
  417. * @returns {Function}
  418. */
  419. export function sendStartRequest() {
  420. return (dispatch: Function, getState: Function) => {
  421. const state = getState();
  422. const tracks = state['features/base/tracks'];
  423. const track = getLocalVideoTrack(tracks);
  424. const { sourceId } = track?.jitsiTrack || {};
  425. const { transport } = state['features/remote-control'].receiver;
  426. return transport.sendRequest({
  427. name: REMOTE_CONTROL_MESSAGE_NAME,
  428. type: REQUESTS.start,
  429. sourceId
  430. });
  431. };
  432. }
  433. /**
  434. * Grants remote control access to user associated with the passed user id.
  435. *
  436. * @param {string} participantId - The id associated with the user who sent the
  437. * request for remote control authorization.
  438. * @returns {Function}
  439. */
  440. export function grant(participantId: string) {
  441. return (dispatch: Function, getState: Function) => {
  442. dispatch({
  443. type: SET_CONTROLLER,
  444. controller: participantId
  445. });
  446. logger.log(`Remote control permissions granted to: ${participantId}`);
  447. let promise;
  448. const state = getState();
  449. const tracks = state['features/base/tracks'];
  450. const track = getLocalVideoTrack(tracks);
  451. const isScreenSharing = track?.videoType === 'desktop';
  452. const { sourceType } = track?.jitsiTrack || {};
  453. if (isScreenSharing && sourceType === 'screen') {
  454. promise = dispatch(sendStartRequest());
  455. } else {
  456. // FIXME: Use action here once toggleScreenSharing is moved to redux.
  457. promise = APP.conference.toggleScreenSharing(
  458. true,
  459. {
  460. desktopSharingSources: [ 'screen' ]
  461. })
  462. .then(() => dispatch(sendStartRequest()));
  463. }
  464. const { conference } = state['features/base/conference'];
  465. promise
  466. .then(() => sendRemoteControlEndpointMessage(conference, participantId, {
  467. type: EVENTS.permissions,
  468. action: PERMISSIONS_ACTIONS.grant
  469. }))
  470. .catch(error => {
  471. logger.error(error);
  472. sendRemoteControlEndpointMessage(conference, participantId, {
  473. type: EVENTS.permissions,
  474. action: PERMISSIONS_ACTIONS.error
  475. });
  476. dispatch(showNotification({
  477. descriptionKey: 'dialog.startRemoteControlErrorMessage',
  478. titleKey: 'dialog.remoteControlTitle'
  479. }));
  480. dispatch(stopReceiver(true));
  481. });
  482. };
  483. }
  484. /**
  485. * Handler for mouse click events on the controller side.
  486. *
  487. * @param {string} type - The type of event ("mousedown"/"mouseup").
  488. * @param {Event} event - The mouse event.
  489. * @returns {Function}
  490. */
  491. export function mouseClicked(type: string, event: Object) {
  492. return (dispatch: Function, getState: Function) => {
  493. const state = getState();
  494. const { conference } = state['features/base/conference'];
  495. const { controller } = state['features/remote-control'];
  496. sendRemoteControlEndpointMessage(conference, controller.controlled, {
  497. type,
  498. button: event.which
  499. });
  500. };
  501. }
  502. /**
  503. * Handles mouse moved events on the controller side.
  504. *
  505. * @param {Event} event - The mouse event.
  506. * @returns {Function}
  507. */
  508. export function mouseMoved(event: Object) {
  509. return (dispatch: Function, getState: Function) => {
  510. const area = getRemoteConrolEventCaptureArea();
  511. if (!area) {
  512. return;
  513. }
  514. const position = area.position();
  515. const state = getState();
  516. const { conference } = state['features/base/conference'];
  517. const { controller } = state['features/remote-control'];
  518. sendRemoteControlEndpointMessage(conference, controller.controlled, {
  519. type: EVENTS.mousemove,
  520. x: (event.pageX - position.left) / area.width(),
  521. y: (event.pageY - position.top) / area.height()
  522. });
  523. };
  524. }
  525. /**
  526. * Handles mouse scroll events on the controller side.
  527. *
  528. * @param {Event} event - The mouse event.
  529. * @returns {Function}
  530. */
  531. export function mouseScrolled(event: Object) {
  532. return (dispatch: Function, getState: Function) => {
  533. const state = getState();
  534. const { conference } = state['features/base/conference'];
  535. const { controller } = state['features/remote-control'];
  536. sendRemoteControlEndpointMessage(conference, controller.controlled, {
  537. type: EVENTS.mousescroll,
  538. x: event.deltaX,
  539. y: event.deltaY
  540. });
  541. };
  542. }
  543. /**
  544. * Handles key press events on the controller side..
  545. *
  546. * @param {string} type - The type of event ("keydown"/"keyup").
  547. * @param {Event} event - The key event.
  548. * @returns {Function}
  549. */
  550. export function keyPressed(type: string, event: Object) {
  551. return (dispatch: Function, getState: Function) => {
  552. const state = getState();
  553. const { conference } = state['features/base/conference'];
  554. const { controller } = state['features/remote-control'];
  555. sendRemoteControlEndpointMessage(conference, controller.controlled, {
  556. type,
  557. key: getKey(event),
  558. modifiers: getModifiers(event)
  559. });
  560. };
  561. }
  562. /**
  563. * Disables the keyboatd shortcuts. Starts collecting remote control
  564. * events. It can be used to resume an active remote control session which
  565. * was paused with the pause action.
  566. *
  567. * @returns {Function}
  568. */
  569. export function resume() {
  570. return (dispatch: Function, getState: Function) => {
  571. const area = getRemoteConrolEventCaptureArea();
  572. const state = getState();
  573. const { controller } = state['features/remote-control'];
  574. const { controlled, isCapturingEvents } = controller;
  575. if (!isRemoteControlEnabled(state) || !area || !controlled || isCapturingEvents) {
  576. return;
  577. }
  578. logger.log('Resuming remote control controller.');
  579. // FIXME: Once the keyboard shortcuts are using react/redux.
  580. APP.keyboardshortcut.enable(false);
  581. area.mousemove(event => {
  582. dispatch(mouseMoved(event));
  583. });
  584. area.mousedown(event => dispatch(mouseClicked(EVENTS.mousedown, event)));
  585. area.mouseup(event => dispatch(mouseClicked(EVENTS.mouseup, event)));
  586. area.dblclick(event => dispatch(mouseClicked(EVENTS.mousedblclick, event)));
  587. area.contextmenu(() => false);
  588. area[0].onwheel = event => {
  589. event.preventDefault();
  590. event.stopPropagation();
  591. dispatch(mouseScrolled(event));
  592. return false;
  593. };
  594. $(window).keydown(event => dispatch(keyPressed(EVENTS.keydown, event)));
  595. $(window).keyup(event => dispatch(keyPressed(EVENTS.keyup, event)));
  596. dispatch({
  597. type: CAPTURE_EVENTS,
  598. isCapturingEvents: true
  599. });
  600. };
  601. }
  602. /**
  603. * Pauses the collecting of events and enables the keyboard shortcus. But
  604. * it doesn't removes any other listeners. Basically the remote control
  605. * session will be still active after the pause action, but no events from the
  606. * controller side will be captured and sent. You can resume the collecting
  607. * of the events with the resume action.
  608. *
  609. * @returns {Function}
  610. */
  611. export function pause() {
  612. return (dispatch: Function, getState: Function) => {
  613. const state = getState();
  614. const { controller } = state['features/remote-control'];
  615. const { controlled, isCapturingEvents } = controller;
  616. if (!isRemoteControlEnabled(state) || !controlled || !isCapturingEvents) {
  617. return;
  618. }
  619. logger.log('Pausing remote control controller.');
  620. // FIXME: Once the keyboard shortcuts are using react/redux.
  621. APP.keyboardshortcut.enable(true);
  622. const area = getRemoteConrolEventCaptureArea();
  623. if (area) {
  624. area.off('contextmenu');
  625. area.off('dblclick');
  626. area.off('mousedown');
  627. area.off('mousemove');
  628. area.off('mouseup');
  629. area[0].onwheel = undefined;
  630. }
  631. $(window).off('keydown');
  632. $(window).off('keyup');
  633. dispatch({
  634. type: CAPTURE_EVENTS,
  635. isCapturingEvents: false
  636. });
  637. };
  638. }