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

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