mirror of
https://github.com/netzbegruenung/jitsi-meet-electron.git
synced 2024-05-06 02:33:42 +02:00
feat(remotecontrol): multi monitor support
This commit is contained in:
parent
a94732fff5
commit
f9f5f7692b
|
@ -10,6 +10,10 @@ from config.js
|
||||||
```bash
|
```bash
|
||||||
npm install
|
npm install
|
||||||
```
|
```
|
||||||
|
Since node_addons/sourceId2Coordinates add-on is local dependency, every code change requires increasing the version in its package.json. To rebuild the add-on if it is already installed execute:
|
||||||
|
```bash
|
||||||
|
npm update
|
||||||
|
```
|
||||||
|
|
||||||
## Statring the application
|
## Statring the application
|
||||||
```bash
|
```bash
|
||||||
|
|
|
@ -2,12 +2,5 @@ module.exports = {
|
||||||
/**
|
/**
|
||||||
* The URL of the Jitsi Meet deployment that will be used.
|
* The URL of the Jitsi Meet deployment that will be used.
|
||||||
*/
|
*/
|
||||||
jitsiMeetURL: "https://meet.jit.si/",
|
jitsiMeetURL: "https://meet.jit.si/"
|
||||||
|
|
||||||
/**
|
|
||||||
* If true, the authorization of remote control will be handled by jitsi
|
|
||||||
* meet electron app. Otherwise the authorization will be handled in Jitsi
|
|
||||||
* Meet.
|
|
||||||
*/
|
|
||||||
handleRemoteControlAuthorization: false
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
/**
|
/**
|
||||||
* Types of remote-control-event events.
|
* Types of remote-control events.
|
||||||
*/
|
*/
|
||||||
EVENT_TYPES: {
|
EVENTS: {
|
||||||
mousemove: "mousemove",
|
mousemove: "mousemove",
|
||||||
mousedown: "mousedown",
|
mousedown: "mousedown",
|
||||||
mouseup: "mouseup",
|
mouseup: "mouseup",
|
||||||
|
@ -10,22 +10,19 @@ module.exports = {
|
||||||
mousescroll: "mousescroll",
|
mousescroll: "mousescroll",
|
||||||
keydown: "keydown",
|
keydown: "keydown",
|
||||||
keyup: "keyup",
|
keyup: "keyup",
|
||||||
permissions: "permissions",
|
|
||||||
stop: "stop",
|
stop: "stop",
|
||||||
supported: "supported"
|
supported: "supported"
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Actions for the remote control permission events.
|
* Types of remote-control requests.
|
||||||
*/
|
*/
|
||||||
PERMISSIONS_ACTIONS: {
|
REQUESTS: {
|
||||||
request: "request",
|
start: "start"
|
||||||
grant: "grant",
|
|
||||||
deny: "deny"
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of remote control events sent trough the API module.
|
* The name of remote control messages.
|
||||||
*/
|
*/
|
||||||
REMOTE_CONTROL_EVENT_NAME: "remote-control-event"
|
REMOTE_CONTROL_MESSAGE_NAME: "remote-control"
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
let robot = require("robotjs");
|
let robot = require("robotjs");
|
||||||
|
const sourceId2Coordinates = require("sourceId2Coordinates");
|
||||||
|
const electron = require("electron");
|
||||||
const constants = require("../../modules/remotecontrol/constants");
|
const constants = require("../../modules/remotecontrol/constants");
|
||||||
const {EVENT_TYPES, PERMISSIONS_ACTIONS, REMOTE_CONTROL_EVENT_NAME} = constants;
|
const { EVENTS, REQUESTS, REMOTE_CONTROL_MESSAGE_NAME } = constants;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attaching to the window for debug purposes.
|
* Attaching to the window for debug purposes.
|
||||||
|
@ -9,13 +11,8 @@ const {EVENT_TYPES, PERMISSIONS_ACTIONS, REMOTE_CONTROL_EVENT_NAME} = constants;
|
||||||
window.robot = robot;
|
window.robot = robot;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Width/Heught for the screen.
|
* Mouse button mapping between the values in remote control mouse event and
|
||||||
*/
|
* robotjs methods.
|
||||||
const {width, height} = robot.getScreenSize();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mouse button mapping between the values in remote-control-event and robotjs
|
|
||||||
* methods.
|
|
||||||
*/
|
*/
|
||||||
const MOUSE_BUTTONS = {
|
const MOUSE_BUTTONS = {
|
||||||
1: "left",
|
1: "left",
|
||||||
|
@ -24,8 +21,8 @@ const MOUSE_BUTTONS = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mouse actions mapping between the values in remote-control-event and robotjs
|
* Mouse actions mapping between the values in remote control mouse event and
|
||||||
* methods.
|
* robotjs methods.
|
||||||
*/
|
*/
|
||||||
const MOUSE_ACTIONS_FROM_EVENT_TYPE = {
|
const MOUSE_ACTIONS_FROM_EVENT_TYPE = {
|
||||||
"mousedown": "down",
|
"mousedown": "down",
|
||||||
|
@ -33,8 +30,8 @@ const MOUSE_ACTIONS_FROM_EVENT_TYPE = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Key actions mapping between the values in remote-control-event and robotjs
|
* Key actions mapping between the values in remote control key event and
|
||||||
* methods.
|
* robotjs methods.
|
||||||
*/
|
*/
|
||||||
const KEY_ACTIONS_FROM_EVENT_TYPE = {
|
const KEY_ACTIONS_FROM_EVENT_TYPE = {
|
||||||
"keydown": "down",
|
"keydown": "down",
|
||||||
|
@ -50,35 +47,25 @@ const KEY_ACTIONS_FROM_EVENT_TYPE = {
|
||||||
let mouseButtonStatus = "up";
|
let mouseButtonStatus = "up";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the remote-control-events and executes them via robotjs.
|
* Parses the remote control events and executes them via robotjs.
|
||||||
*/
|
*/
|
||||||
class RemoteControl {
|
class RemoteControl {
|
||||||
/**
|
/**
|
||||||
* Construcs new instance.
|
* Constructs new instance and initializes the remote control functionality.
|
||||||
* @constructor
|
*
|
||||||
|
* @param {Postis} channel the postis channel.
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor(channel) {
|
||||||
this.started = false;
|
// TODO: if no channel is passed, create one.
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the remote control functionality.
|
|
||||||
*/
|
|
||||||
init(channel, windowManager, handleAuthorization) {
|
|
||||||
this.handleAuthorization = handleAuthorization;
|
|
||||||
this.windowManager = windowManager;
|
|
||||||
this.channel = channel;
|
this.channel = channel;
|
||||||
this.channel.ready(() => {
|
this.channel.ready(() => {
|
||||||
this.channel.listen('message', message => {
|
this.channel.listen('message', message => {
|
||||||
const event = message.data;
|
const { name } = message.data;
|
||||||
if(event.name === REMOTE_CONTROL_EVENT_NAME) {
|
if(name === REMOTE_CONTROL_MESSAGE_NAME) {
|
||||||
this.onRemoteControlEvent(event);
|
this.onRemoteControlMessage(message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.sendEvent({type: EVENT_TYPES.supported});
|
this.sendEvent({ type: EVENTS.supported });
|
||||||
if (!handleAuthorization) {
|
|
||||||
this.start();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,114 +73,137 @@ class RemoteControl {
|
||||||
* Disposes the remote control functionality.
|
* Disposes the remote control functionality.
|
||||||
*/
|
*/
|
||||||
dispose() {
|
dispose() {
|
||||||
this.windowManager = null;
|
|
||||||
this.channel = null;
|
this.channel = null;
|
||||||
this.stop();
|
this.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles permission requests from Jitsi Meet.
|
* Handles remote control start messages.
|
||||||
* @param {object} userInfo - information about the user that has requested
|
*
|
||||||
* permissions:
|
* @param {number} id - the id of the request that will be used for the
|
||||||
* @param {string} userInfo.displayName - display name
|
* response.
|
||||||
* @param {string} userInfo.userJID - the JID of the user.
|
* @param {string} sourceId - The source id of the desktop sharing stream.
|
||||||
* @param {string} userInfo.userId - the user id (the resource of the JID)
|
|
||||||
* @param {boolean} userInfo.screenSharing - true if the screen sharing
|
|
||||||
* is started.
|
|
||||||
*/
|
*/
|
||||||
handlePermissionRequest(userInfo) {
|
start(id, sourceId) {
|
||||||
this.windowManager.requestRemoteControlPermissions(userInfo)
|
const displays = electron.screen.getAllDisplays();
|
||||||
.then(result => {
|
|
||||||
this.sendEvent({
|
|
||||||
type: EVENT_TYPES.permissions,
|
|
||||||
action: result ? PERMISSIONS_ACTIONS.grant
|
|
||||||
: PERMISSIONS_ACTIONS.deny,
|
|
||||||
userId: userInfo.userId
|
|
||||||
});
|
|
||||||
if(result) {
|
|
||||||
this.start();
|
|
||||||
}
|
|
||||||
}).catch(e => {
|
|
||||||
console.error(e);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
switch(displays.length) {
|
||||||
* Starts processing the events.
|
case 0:
|
||||||
*/
|
this.display = undefined;
|
||||||
start() {
|
break;
|
||||||
this.started = true;
|
case 1:
|
||||||
|
// On Linux probably we'll end up here even if there are
|
||||||
|
// multiple monitors.
|
||||||
|
this.display = displays[0];
|
||||||
|
break;
|
||||||
|
// eslint-disable-next-line no-case-declarations
|
||||||
|
default: // > 1 display
|
||||||
|
const coordinates = sourceId2Coordinates(sourceId);
|
||||||
|
if(coordinates) {
|
||||||
|
// Currently sourceId2Coordinates will return undefined for
|
||||||
|
// any OS except Windows. This code will be executed only on
|
||||||
|
// Windows.
|
||||||
|
const { x, y } = coordinates;
|
||||||
|
this.display = electron.screen.getDisplayNearestPoint({
|
||||||
|
x: x + 1,
|
||||||
|
y: y + 1
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// On Mac OS the sourceId = 'screen' + displayId.
|
||||||
|
// Try to match displayId with sourceId.
|
||||||
|
const displayId = Number(sourceId.replace('screen:', ''));
|
||||||
|
this.display
|
||||||
|
= displays.find(display => display.id === displayId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = {
|
||||||
|
id,
|
||||||
|
type: 'response'
|
||||||
|
};
|
||||||
|
|
||||||
|
if(this.display) {
|
||||||
|
response.result = true;
|
||||||
|
} else {
|
||||||
|
response.error
|
||||||
|
= 'Error: Can\'t detect the display that is currently shared';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.sendMessage(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops processing the events.
|
* Stops processing the events.
|
||||||
*/
|
*/
|
||||||
stop() {
|
stop() {
|
||||||
this.started = false;
|
this.display = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Executes the passed event.
|
* Executes the passed message.
|
||||||
* @param {Object} event the remote-control-event.
|
* @param {Object} message the remote control message.
|
||||||
*/
|
*/
|
||||||
onRemoteControlEvent(event) {
|
onRemoteControlMessage(message) {
|
||||||
if(!this.started && event.type !== EVENT_TYPES.permissions) {
|
const { id, data } = message;
|
||||||
|
|
||||||
|
// If we haven't set the display prop. We haven't received the remote
|
||||||
|
// control start message or there was an error associating a display.
|
||||||
|
if(!this.display
|
||||||
|
&& data.type != REQUESTS.start) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
switch(event.type) {
|
switch(data.type) {
|
||||||
case EVENT_TYPES.mousemove: {
|
case EVENTS.mousemove: {
|
||||||
const x = event.x * width, y = event.y * height;
|
const { width, height, x, y } = this.display.workArea;
|
||||||
|
const destX = data.x * width + x;
|
||||||
|
const destY = data.y * height + y;
|
||||||
if(mouseButtonStatus === "down") {
|
if(mouseButtonStatus === "down") {
|
||||||
robot.dragMouse(x, y);
|
robot.dragMouse(destX, destY);
|
||||||
} else {
|
} else {
|
||||||
robot.moveMouse(x, y);
|
robot.moveMouse(destX, destY);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EVENT_TYPES.mousedown:
|
case EVENTS.mousedown:
|
||||||
case EVENT_TYPES.mouseup: {
|
case EVENTS.mouseup: {
|
||||||
mouseButtonStatus = MOUSE_ACTIONS_FROM_EVENT_TYPE[event.type];
|
mouseButtonStatus
|
||||||
|
= MOUSE_ACTIONS_FROM_EVENT_TYPE[data.type];
|
||||||
robot.mouseToggle(
|
robot.mouseToggle(
|
||||||
mouseButtonStatus,
|
mouseButtonStatus,
|
||||||
(event.button ? MOUSE_BUTTONS[event.button] : undefined));
|
(data.button
|
||||||
|
? MOUSE_BUTTONS[data.button] : undefined));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EVENT_TYPES.mousedblclick: {
|
case EVENTS.mousedblclick: {
|
||||||
robot.mouseClick(
|
robot.mouseClick(
|
||||||
(event.button ? MOUSE_BUTTONS[event.button] : undefined),
|
(data.button
|
||||||
|
? MOUSE_BUTTONS[data.button] : undefined),
|
||||||
true);
|
true);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EVENT_TYPES.mousescroll:{
|
case EVENTS.mousescroll:{
|
||||||
//FIXME: implement horizontal scrolling
|
//FIXME: implement horizontal scrolling
|
||||||
if(event.y !== 0) {
|
if(data.y !== 0) {
|
||||||
robot.scrollMouse(
|
robot.scrollMouse(
|
||||||
Math.abs(event.y),
|
Math.abs(data.y),
|
||||||
event.y > 0 ? "down" : "up"
|
data.y > 0 ? "down" : "up"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EVENT_TYPES.keydown:
|
case EVENTS.keydown:
|
||||||
case EVENT_TYPES.keyup: {
|
case EVENTS.keyup: {
|
||||||
robot.keyToggle(event.key,
|
robot.keyToggle(
|
||||||
KEY_ACTIONS_FROM_EVENT_TYPE[event.type], event.modifiers);
|
data.key,
|
||||||
|
KEY_ACTIONS_FROM_EVENT_TYPE[data.type],
|
||||||
|
data.modifiers);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EVENT_TYPES.permissions: {
|
case REQUESTS.start: {
|
||||||
if(event.action === PERMISSIONS_ACTIONS.request
|
this.start(id, data.sourceId);
|
||||||
&& this.handleAuthorization) {
|
|
||||||
// Open Dialog and answer
|
|
||||||
this.handlePermissionRequest({
|
|
||||||
userId: event.userId,
|
|
||||||
userJID: event.userJID,
|
|
||||||
displayName: event.displayName,
|
|
||||||
screenSharing: event.screenSharing
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EVENT_TYPES.stop: {
|
case EVENTS.stop: {
|
||||||
this.stop();
|
this.stop();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -204,18 +214,28 @@ class RemoteControl {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends remote control event to the controlled participant.
|
* Sends remote control event to the controlled participant.
|
||||||
|
*
|
||||||
* @param {Object} event the remote control event.
|
* @param {Object} event the remote control event.
|
||||||
*/
|
*/
|
||||||
sendEvent(event) {
|
sendEvent(event) {
|
||||||
const remoteControlEvent = Object.assign(
|
const remoteControlEvent = Object.assign(
|
||||||
{ name: REMOTE_CONTROL_EVENT_NAME },
|
{ name: REMOTE_CONTROL_MESSAGE_NAME },
|
||||||
event
|
event
|
||||||
);
|
);
|
||||||
|
this.sendMessage({ data: remoteControlEvent });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message to Jitsi Meet.
|
||||||
|
*
|
||||||
|
* @param {Object} message the message to be sent.
|
||||||
|
*/
|
||||||
|
sendMessage(message) {
|
||||||
this.channel.send({
|
this.channel.send({
|
||||||
method: 'message',
|
method: 'message',
|
||||||
params: { data: remoteControlEvent }
|
params: message
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = new RemoteControl();
|
module.exports = RemoteControl;
|
||||||
|
|
27
node_addons/sourceId2Coordinates/binding.gyp
Normal file
27
node_addons/sourceId2Coordinates/binding.gyp
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
'targets': [{
|
||||||
|
'target_name': 'sourceId2Coordinates',
|
||||||
|
'include_dirs': [
|
||||||
|
"<!(node -e \"require('nan')\")"
|
||||||
|
],
|
||||||
|
|
||||||
|
'cflags': [
|
||||||
|
'-Wall',
|
||||||
|
'-Wparentheses',
|
||||||
|
'-Winline',
|
||||||
|
'-Wbad-function-cast',
|
||||||
|
'-Wdisabled-optimization'
|
||||||
|
],
|
||||||
|
|
||||||
|
'conditions': [
|
||||||
|
["OS=='win'", {
|
||||||
|
'defines': ['IS_WINDOWS']
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
|
||||||
|
'sources': [
|
||||||
|
'src/index.cc',
|
||||||
|
'src/sourceId2Coordinates.cc'
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
}
|
22
node_addons/sourceId2Coordinates/index.js
Normal file
22
node_addons/sourceId2Coordinates/index.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
const sourceId2Coordinates
|
||||||
|
= require('./build/Release/sourceId2Coordinates.node').sourceId2Coordinates;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the coordinates of a desktop using the passed desktop sharing source
|
||||||
|
* id.
|
||||||
|
*
|
||||||
|
* @param {string} sourceId - The desktop sharing source id.
|
||||||
|
* @returns {Object.<string, number>|undefined} - The x and y coordinates of the
|
||||||
|
* top left corner of the desktop. Currently works only for windows. Returns
|
||||||
|
* undefined for Mac OS, Linux.
|
||||||
|
*/
|
||||||
|
module.exports = function(sourceID) {
|
||||||
|
// On windows the source id will have the following format "0:desktop_id".
|
||||||
|
// we need the "desktop_id" only to get the coordinates.
|
||||||
|
const idArr = sourceID.split(":");
|
||||||
|
const id = Number(idArr.length > 1 ? idArr[1] : sourceID);
|
||||||
|
if(id) {
|
||||||
|
return sourceId2Coordinates(id);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
17
node_addons/sourceId2Coordinates/package.json
Normal file
17
node_addons/sourceId2Coordinates/package.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"name": "sourceId2Coordinates",
|
||||||
|
"version": "0.0.3",
|
||||||
|
"description": "Native addon that returns the coordinates of desktop using the passed source id",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"install": "node-gyp rebuild"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"Util",
|
||||||
|
"WebRTC"
|
||||||
|
],
|
||||||
|
"gypfile": true,
|
||||||
|
"dependencies": {
|
||||||
|
"nan": "^2.2.1"
|
||||||
|
}
|
||||||
|
}
|
37
node_addons/sourceId2Coordinates/src/index.cc
Normal file
37
node_addons/sourceId2Coordinates/src/index.cc
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#include <node.h>
|
||||||
|
#include <nan.h>
|
||||||
|
#include <v8.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "sourceId2Coordinates.h"
|
||||||
|
|
||||||
|
using namespace v8;
|
||||||
|
|
||||||
|
NAN_METHOD(sourceId2Coordinates)
|
||||||
|
{
|
||||||
|
const int sourceID = info[0]->Int32Value();
|
||||||
|
Local<Object> obj = Nan::New<Object>();
|
||||||
|
Point coordinates;
|
||||||
|
if(!sourceId2Coordinates(sourceID, &coordinates))
|
||||||
|
{ // return undefined if sourceId2Coordinates function fail.
|
||||||
|
info.GetReturnValue().Set(Nan::Undefined());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{ // return the coordinates if sourceId2Coordinates function succeed.
|
||||||
|
Nan::Set(obj, Nan::New("x").ToLocalChecked(), Nan::New(coordinates.x));
|
||||||
|
Nan::Set(obj, Nan::New("y").ToLocalChecked(), Nan::New(coordinates.y));
|
||||||
|
info.GetReturnValue().Set(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NAN_MODULE_INIT(Init)
|
||||||
|
{
|
||||||
|
Nan::Set(
|
||||||
|
target,
|
||||||
|
Nan::New("sourceId2Coordinates").ToLocalChecked(),
|
||||||
|
Nan::GetFunction(Nan::New<FunctionTemplate>(sourceId2Coordinates))
|
||||||
|
.ToLocalChecked()
|
||||||
|
);
|
||||||
|
NAN_EXPORT(target, sourceId2Coordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
NODE_MODULE(sourceId2CoordinatesModule, Init)
|
45
node_addons/sourceId2Coordinates/src/sourceId2Coordinates.cc
Normal file
45
node_addons/sourceId2Coordinates/src/sourceId2Coordinates.cc
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
#if defined(IS_WINDOWS)
|
||||||
|
#include <windows.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "sourceId2Coordinates.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to get the coordinates of a desktop from passed sourceId
|
||||||
|
* (which identifies a desktop sharing source). Used to match the source id to a
|
||||||
|
* screen in Electron.
|
||||||
|
*
|
||||||
|
* Returns true on success and false on failure.
|
||||||
|
*
|
||||||
|
* NOTE: Works on windows only because on the other platforms there is an easier
|
||||||
|
* way to match the source id and the screen.
|
||||||
|
*/
|
||||||
|
bool sourceId2Coordinates(int sourceId, Point* res)
|
||||||
|
{
|
||||||
|
#if defined(IS_WINDOWS)
|
||||||
|
DISPLAY_DEVICE device;
|
||||||
|
device.cb = sizeof(device);
|
||||||
|
|
||||||
|
if (!EnumDisplayDevices(NULL, sourceId, &device, 0) // device not found
|
||||||
|
|| !(device.StateFlags & DISPLAY_DEVICE_ACTIVE))// device is not active
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEVMODE deviceSettings;
|
||||||
|
deviceSettings.dmSize = sizeof(deviceSettings);
|
||||||
|
deviceSettings.dmDriverExtra = 0;
|
||||||
|
if(!EnumDisplaySettingsEx(device.DeviceName, ENUM_CURRENT_SETTINGS,
|
||||||
|
&deviceSettings, 0))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
res->x = deviceSettings.dmPosition.x;
|
||||||
|
res->y = deviceSettings.dmPosition.y;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
struct Point {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
Point(): x(0), y(0) {};
|
||||||
|
};
|
||||||
|
|
||||||
|
bool sourceId2Coordinates(int sourceId, Point* res);
|
|
@ -30,7 +30,8 @@
|
||||||
"readmeFilename": "README.md",
|
"readmeFilename": "README.md",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"robotjs": "hristoterezov/robotjs",
|
"sourceId2Coordinates": "file:./node_addons/sourceId2Coordinates",
|
||||||
|
"robotjs": "jitsi/robotjs#jitsi",
|
||||||
"postis": "^2.2.0"
|
"postis": "^2.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
/* global process */
|
/* global process */
|
||||||
const remoteControl = require("../../modules/remotecontrol");
|
const RemoteControl = require("../../modules/remotecontrol");
|
||||||
let postis = require("postis");
|
let postis = require("postis");
|
||||||
const setupScreenSharingForWindow = require("../../modules/screensharing");
|
const setupScreenSharingForWindow = require("../../modules/screensharing");
|
||||||
const config = require("../../config.js");
|
const config = require("../../config.js");
|
||||||
const {dialog} = require('electron').remote;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The postis channel.
|
* The postis channel.
|
||||||
*/
|
*/
|
||||||
let channel;
|
let channel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The remote control instance.
|
||||||
|
*/
|
||||||
|
let remoteControl;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cteates the iframe that will load Jitsi Meet.
|
* Cteates the iframe that will load Jitsi Meet.
|
||||||
*/
|
*/
|
||||||
|
@ -19,52 +23,6 @@ iframe.allowFullscreen = true;
|
||||||
iframe.onload = onload;
|
iframe.onload = onload;
|
||||||
document.body.appendChild(iframe);
|
document.body.appendChild(iframe);
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory for dialogs.
|
|
||||||
*/
|
|
||||||
class DialogFactory {
|
|
||||||
/**
|
|
||||||
* Creates new instance
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
constructor() { }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows message box dialog for request for remote control permissions
|
|
||||||
* @param {object} userInfo - information about the user that has sent the
|
|
||||||
* request:
|
|
||||||
* @param {string} userInfo.displayName - display name
|
|
||||||
* @param {string} userInfo.userJID - the JID of the user.
|
|
||||||
* @param {boolean} userInfo.screenSharing - true if the screen sharing
|
|
||||||
* is started.
|
|
||||||
*/
|
|
||||||
requestRemoteControlPermissions(userInfo) {
|
|
||||||
return new Promise( resolve =>
|
|
||||||
dialog.showMessageBox({
|
|
||||||
type: "question",
|
|
||||||
buttons: [
|
|
||||||
"Yes",
|
|
||||||
"No"
|
|
||||||
],
|
|
||||||
defaultId: 0,
|
|
||||||
title: "Request for permission for remote control",
|
|
||||||
message: "Would you like to allow " + userInfo.displayName
|
|
||||||
+ " to remotely control your desktop?"
|
|
||||||
+ (userInfo.screenSharing ? ""
|
|
||||||
: "\nNote: If you press \"Yes\" the screen sharing "
|
|
||||||
+ "will start!"),
|
|
||||||
detail: "userId: " + userInfo.userJID,
|
|
||||||
cancelId: 1
|
|
||||||
}, response => resolve(response === 0? true : false))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dialog factory instance.
|
|
||||||
*/
|
|
||||||
const dialogFactory = new DialogFactory();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles loaded event for iframe:
|
* Handles loaded event for iframe:
|
||||||
* Enables screen sharing functionality to the iframe webpage.
|
* Enables screen sharing functionality to the iframe webpage.
|
||||||
|
@ -78,10 +36,7 @@ function onload() {
|
||||||
window: iframe.contentWindow,
|
window: iframe.contentWindow,
|
||||||
windowForEventListening: window
|
windowForEventListening: window
|
||||||
});
|
});
|
||||||
remoteControl.init(
|
remoteControl = new RemoteControl(channel);
|
||||||
channel,
|
|
||||||
dialogFactory,
|
|
||||||
config.handleRemoteControlAuthorization);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue