mirror of
https://github.com/finos/SymphonyElectron.git
synced 2025-02-25 18:55:29 -06:00
Electron 32 (Activity Detection) (#71)
* Implemented user activity detection * ELECTRON-32: Implemented throttle function * ELECTRON-32: Fixed some bugs in throttle function * ELECTRON-32: Updated comments * ELECTRON-32: Fixed clear interval bug * ELECTRON-32: Updated as per the review * ELECTRON-32: Updated return statement * ELECTRON-32: Added a boolean to the callback function * ELECTRON-32: Resolved conflicts * ELECTRON-32 - Added period attribute
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,4 +3,4 @@ dist
|
||||
.DS_Store
|
||||
js/preload/_*.js
|
||||
.idea/
|
||||
coverage/
|
||||
coverage/
|
||||
93
js/activityDetection/activityDetection.js
Normal file
93
js/activityDetection/activityDetection.js
Normal file
@@ -0,0 +1,93 @@
|
||||
'use strict';
|
||||
|
||||
const systemIdleTime = require('@paulcbetts/system-idle-time');
|
||||
const throttle = require('../utils/throttle');
|
||||
|
||||
let maxIdleTime;
|
||||
let activityWindow;
|
||||
let intervalId;
|
||||
let throttleActivity;
|
||||
|
||||
/**
|
||||
* Check if the user is idle
|
||||
*/
|
||||
function activityDetection() {
|
||||
// Get system idle status and idle time from PaulCBetts package
|
||||
if (systemIdleTime.getIdleTime() < maxIdleTime) {
|
||||
return {isUserIdle: false, systemIdleTime: systemIdleTime.getIdleTime()};
|
||||
}
|
||||
|
||||
// If idle for more than 4 mins, monitor system idle status every second
|
||||
if (!intervalId) {
|
||||
monitorUserActivity();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start monitoring user activity status.
|
||||
* Run every 4 mins to check user idle status
|
||||
*/
|
||||
function initiateActivityDetection() {
|
||||
|
||||
if (!throttleActivity) {
|
||||
throttleActivity = throttle(maxIdleTime, sendActivity);
|
||||
setInterval(throttleActivity, maxIdleTime);
|
||||
}
|
||||
|
||||
sendActivity();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Monitor system idle status every second
|
||||
*/
|
||||
function monitorUserActivity() {
|
||||
intervalId = setInterval(monitor, 1000);
|
||||
|
||||
function monitor() {
|
||||
if (systemIdleTime.getIdleTime() < maxIdleTime) {
|
||||
// If system is active, send an update to the app bridge and clear the timer
|
||||
sendActivity();
|
||||
clearInterval(intervalId);
|
||||
intervalId = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Send user activity status to the app bridge
|
||||
* to be updated across all clients
|
||||
*/
|
||||
function sendActivity() {
|
||||
let systemActivity = activityDetection();
|
||||
if (systemActivity && !systemActivity.isUserIdle && systemActivity.systemIdleTime) {
|
||||
send({systemIdleTime: systemActivity.systemIdleTime});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends user activity status from main process to activity detection hosted by
|
||||
* renderer process. Allows main process to use activity detection
|
||||
* provided by JS.
|
||||
* @param {object} data - data as object
|
||||
*/
|
||||
function send(data) {
|
||||
if (activityWindow && data) {
|
||||
activityWindow.send('activity', {
|
||||
systemIdleTime: data.systemIdleTime
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setActivityWindow(period, win) {
|
||||
maxIdleTime = period;
|
||||
activityWindow = win;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
send: send,
|
||||
setActivityWindow: setActivityWindow,
|
||||
initiateActivityDetection: initiateActivityDetection
|
||||
};
|
||||
@@ -8,7 +8,8 @@ const cmds = keyMirror({
|
||||
setBadgeCount: null,
|
||||
badgeDataUrl: null,
|
||||
activate: null,
|
||||
registerBoundsChange: null
|
||||
registerBoundsChange: null,
|
||||
registerActivityDetection: null,
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
|
||||
@@ -8,6 +8,7 @@ const electron = require('electron');
|
||||
|
||||
const windowMgr = require('./windowMgr.js');
|
||||
const log = require('./log.js');
|
||||
const activityDetection = require('./activityDetection/activityDetection');
|
||||
const badgeCount = require('./badgeCount.js');
|
||||
|
||||
const apiEnums = require('./enums/api.js');
|
||||
@@ -87,11 +88,16 @@ electron.ipcMain.on(apiName, (event, arg) => {
|
||||
// renderer window that has a registered logger from JS.
|
||||
log.setLogWindow(event.sender);
|
||||
}
|
||||
|
||||
if (arg.cmd === apiCmds.registerActivityDetection) {
|
||||
// renderer window that has a registered activity detection from JS.
|
||||
activityDetection.setActivityWindow(arg.period, event.sender);
|
||||
}
|
||||
});
|
||||
|
||||
// expose these methods primarily for testing...
|
||||
module.exports = {
|
||||
shouldCheckValidWindow: function(shouldCheck) {
|
||||
shouldCheckValidWindow: function (shouldCheck) {
|
||||
checkValidWindow = shouldCheck;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,6 +123,27 @@ function createAPI() {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* allows JS to register a activity detector that can be used by electron main process.
|
||||
* @param {Object} activityDetection - function that can be called accepting
|
||||
* @param {Object} period - minimum user idle time in millisecond
|
||||
* object: {
|
||||
* period: Number
|
||||
* systemIdleTime: Number
|
||||
* }
|
||||
*/
|
||||
registerActivityDetection: function(period, activityDetection) {
|
||||
if (typeof activityDetection === 'function') {
|
||||
local.activityDetection = activityDetection;
|
||||
|
||||
// only main window can register
|
||||
local.ipcRenderer.send(apiName, {
|
||||
cmd: apiCmds.registerActivityDetection,
|
||||
period: period
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Implements equivalent of desktopCapturer.getSources - that works in
|
||||
* a sandboxed renderer process.
|
||||
@@ -130,6 +151,7 @@ function createAPI() {
|
||||
* for interface: see documentation in desktopCapturer/getSources.js
|
||||
*/
|
||||
getMediaSources: getMediaSources
|
||||
|
||||
};
|
||||
|
||||
// add support for both ssf and SYM_API name-space.
|
||||
@@ -158,6 +180,13 @@ function createAPI() {
|
||||
}
|
||||
});
|
||||
|
||||
// listen for user activity from main process
|
||||
local.ipcRenderer.on('activity', (event, arg) => {
|
||||
if (local.activityDetection && arg && arg.systemIdleTime) {
|
||||
local.activityDetection(arg.systemIdleTime);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Use render process to create badge count img and send back to main process.
|
||||
* If number is greater than 99 then 99+ img is returned.
|
||||
|
||||
@@ -8,14 +8,17 @@ const querystring = require('querystring');
|
||||
|
||||
const menuTemplate = require('./menus/menuTemplate.js');
|
||||
const loadErrors = require('./dialogs/showLoadError.js');
|
||||
const { isMac } = require('./utils/misc.js');
|
||||
const {isMac} = require('./utils/misc.js');
|
||||
const isInDisplayBounds = require('./utils/isInDisplayBounds.js');
|
||||
const getGuid = require('./utils/getGuid.js');
|
||||
const log = require('./log.js')
|
||||
const log = require('./log.js');
|
||||
const logLevels = require('./enums/logLevels.js');
|
||||
const notify = require('./notify/electron-notify.js');
|
||||
|
||||
const activityDetection = require('./activityDetection/activityDetection.js');
|
||||
|
||||
const throttle = require('./utils/throttle.js');
|
||||
const { getConfigField, updateConfigField } = require('./config.js');
|
||||
const {getConfigField, updateConfigField} = require('./config.js');
|
||||
|
||||
//context menu
|
||||
const contextMenu = require('./menus/contextMenu.js');
|
||||
@@ -35,11 +38,11 @@ let boundsChangeWindow;
|
||||
const preloadMainScript = path.join(__dirname, 'preload/_preloadMain.js');
|
||||
|
||||
function addWindowKey(key, browserWin) {
|
||||
windows[ key ] = browserWin;
|
||||
windows[key] = browserWin;
|
||||
}
|
||||
|
||||
function removeWindowKey(key) {
|
||||
delete windows[ key ];
|
||||
delete windows[key];
|
||||
}
|
||||
|
||||
function getParsedUrl(url) {
|
||||
@@ -49,10 +52,10 @@ function getParsedUrl(url) {
|
||||
|
||||
function createMainWindow(initialUrl) {
|
||||
getConfigField('mainWinPos').then(
|
||||
function(bounds) {
|
||||
function (bounds) {
|
||||
doCreateMainWindow(initialUrl, bounds);
|
||||
},
|
||||
function() {
|
||||
function () {
|
||||
// failed, use default bounds
|
||||
doCreateMainWindow(initialUrl, null);
|
||||
}
|
||||
@@ -105,7 +108,7 @@ function doCreateMainWindow(initialUrl, initialBounds) {
|
||||
|
||||
let throttledMainWinBoundsChange = throttle(5000, saveMainWinBounds);
|
||||
mainWindow.on('move', throttledMainWinBoundsChange);
|
||||
mainWindow.on('resize',throttledMainWinBoundsChange);
|
||||
mainWindow.on('resize', throttledMainWinBoundsChange);
|
||||
|
||||
function retry() {
|
||||
if (!isOnline) {
|
||||
@@ -120,7 +123,7 @@ function doCreateMainWindow(initialUrl, initialBounds) {
|
||||
|
||||
// content can be cached and will still finish load but
|
||||
// we might not have netowrk connectivity, so warn the user.
|
||||
mainWindow.webContents.on('did-finish-load', function() {
|
||||
mainWindow.webContents.on('did-finish-load', function () {
|
||||
url = mainWindow.webContents.getURL();
|
||||
|
||||
if (!isOnline) {
|
||||
@@ -129,11 +132,14 @@ function doCreateMainWindow(initialUrl, initialBounds) {
|
||||
// removes all existing notifications when main window reloads
|
||||
notify.reset();
|
||||
log.send(logLevels.INFO, 'main window loaded url: ' + url);
|
||||
|
||||
// Initiate activity detection to monitor user activity status
|
||||
activityDetection.initiateActivityDetection();
|
||||
}
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('did-fail-load', function(event, errorCode,
|
||||
errorDesc, validatedURL) {
|
||||
mainWindow.webContents.on('did-fail-load', function (event, errorCode,
|
||||
errorDesc, validatedURL) {
|
||||
loadErrors.showLoadFailure(mainWindow, validatedURL, errorDesc, errorCode, retry);
|
||||
});
|
||||
|
||||
@@ -143,7 +149,7 @@ function doCreateMainWindow(initialUrl, initialBounds) {
|
||||
const menu = electron.Menu.buildFromTemplate(menuTemplate(app));
|
||||
electron.Menu.setApplicationMenu(menu);
|
||||
|
||||
mainWindow.on('close', function(e) {
|
||||
mainWindow.on('close', function (e) {
|
||||
if (willQuitApp) {
|
||||
destroyAllWindows();
|
||||
return;
|
||||
@@ -157,7 +163,7 @@ function doCreateMainWindow(initialUrl, initialBounds) {
|
||||
|
||||
function destroyAllWindows() {
|
||||
let keys = Object.keys(windows);
|
||||
for(var i = 0, len = keys.length; i < len; i++) {
|
||||
for (var i = 0, len = keys.length; i < len; i++) {
|
||||
let winKey = keys[i];
|
||||
removeWindowKey(winKey);
|
||||
}
|
||||
@@ -168,8 +174,8 @@ function doCreateMainWindow(initialUrl, initialBounds) {
|
||||
mainWindow.on('closed', destroyAllWindows);
|
||||
|
||||
// open external links in default browser - a tag, window.open
|
||||
mainWindow.webContents.on('new-window', function(event, newWinUrl,
|
||||
frameName, disposition, newWinOptions) {
|
||||
mainWindow.webContents.on('new-window', function (event, newWinUrl,
|
||||
frameName, disposition, newWinOptions) {
|
||||
let newWinParsedUrl = getParsedUrl(newWinUrl);
|
||||
let mainWinParsedUrl = getParsedUrl(url);
|
||||
|
||||
@@ -201,7 +207,7 @@ function doCreateMainWindow(initialUrl, initialBounds) {
|
||||
let newX = Number.parseInt(query.x, 10);
|
||||
let newY = Number.parseInt(query.y, 10);
|
||||
|
||||
let newWinRect = { x: newX, y: newY, width, height };
|
||||
let newWinRect = {x: newX, y: newY, width, height};
|
||||
|
||||
// only accept if both are successfully parsed.
|
||||
if (Number.isInteger(newX) && Number.isInteger(newY) &&
|
||||
@@ -214,9 +220,9 @@ function doCreateMainWindow(initialUrl, initialBounds) {
|
||||
}
|
||||
} else {
|
||||
// create new window at slight offset from main window.
|
||||
({ x, y } = getWindowSizeAndPosition(mainWindow));
|
||||
x+=50;
|
||||
y+=50;
|
||||
({x, y} = getWindowSizeAndPosition(mainWindow));
|
||||
x += 50;
|
||||
y += 50;
|
||||
}
|
||||
|
||||
/* eslint-disable no-param-reassign */
|
||||
@@ -232,13 +238,13 @@ function doCreateMainWindow(initialUrl, initialBounds) {
|
||||
|
||||
let webContents = newWinOptions.webContents;
|
||||
|
||||
webContents.once('did-finish-load', function() {
|
||||
webContents.once('did-finish-load', function () {
|
||||
let browserWin = electron.BrowserWindow.fromWebContents(webContents);
|
||||
|
||||
if (browserWin) {
|
||||
browserWin.winName = frameName;
|
||||
|
||||
browserWin.once('closed', function() {
|
||||
browserWin.once('closed', function () {
|
||||
removeWindowKey(newWinKey);
|
||||
browserWin.removeListener('move', throttledBoundsChange);
|
||||
browserWin.removeListener('resize', throttledBoundsChange);
|
||||
@@ -250,7 +256,7 @@ function doCreateMainWindow(initialUrl, initialBounds) {
|
||||
let throttledBoundsChange = throttle(1000,
|
||||
sendChildWinBoundsChange.bind(null, browserWin));
|
||||
browserWin.on('move', throttledBoundsChange);
|
||||
browserWin.on('resize',throttledBoundsChange);
|
||||
browserWin.on('resize', throttledBoundsChange);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -259,7 +265,7 @@ function doCreateMainWindow(initialUrl, initialBounds) {
|
||||
contextMenu(mainWindow);
|
||||
}
|
||||
|
||||
app.on('before-quit', function() {
|
||||
app.on('before-quit', function () {
|
||||
willQuitApp = true;
|
||||
});
|
||||
|
||||
@@ -280,7 +286,7 @@ function getWindowSizeAndPosition(window) {
|
||||
let newSize = window.getSize();
|
||||
|
||||
if (newPos && newPos.length === 2 &&
|
||||
newSize && newSize.length === 2 ) {
|
||||
newSize && newSize.length === 2) {
|
||||
return {
|
||||
x: newPos[0],
|
||||
y: newPos[1],
|
||||
@@ -302,7 +308,7 @@ function isMainWindow(win) {
|
||||
|
||||
function hasWindow(win, winKey) {
|
||||
if (win instanceof electron.BrowserWindow) {
|
||||
let browserWin = windows[ winKey ];
|
||||
let browserWin = windows[winKey];
|
||||
return browserWin && win === browserWin;
|
||||
}
|
||||
|
||||
@@ -321,7 +327,7 @@ function setIsOnline(status) {
|
||||
*/
|
||||
function activate(windowName) {
|
||||
let keys = Object.keys(windows);
|
||||
for(let i = 0, len = keys.length; i < len; i++) {
|
||||
for (let i = 0, len = keys.length; i < len; i++) {
|
||||
let window = windows[keys[i]];
|
||||
if (window && !window.isDestroyed() && window.winName === windowName) {
|
||||
if (window.isMinimized()) {
|
||||
|
||||
11
package.json
11
package.json
@@ -15,13 +15,14 @@
|
||||
"unpacked-win": "npm run prebuild && build --win --x64 --dir",
|
||||
"unpacked-win-x86": "npm run prebuild && build --win --ia32 --dir",
|
||||
"prebuild": "npm run lint && npm run test && npm run browserify-preload",
|
||||
"rebuild": "npm install electron-rebuild && ./node_modules/.bin/electron-rebuild",
|
||||
"lint": "eslint --ext .js js/",
|
||||
"test": "jest --testPathPattern test",
|
||||
"browserify-preload": "browserify -o js/preload/_preloadMain.js -x electron --insert-global-vars=__filename,__dirname js/preload/preloadMain.js"
|
||||
},
|
||||
"jest": {
|
||||
"collectCoverage": true,
|
||||
"transformIgnorePatterns": []
|
||||
"collectCoverage": true,
|
||||
"transformIgnorePatterns": []
|
||||
},
|
||||
"build": {
|
||||
"files": [
|
||||
@@ -82,13 +83,15 @@
|
||||
"jest": "^19.0.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@paulcbetts/system-idle-time": "^1.0.4",
|
||||
"async": "^2.1.5",
|
||||
"electron-context-menu": "^0.8.0",
|
||||
"electron-rebuild": "^1.5.7",
|
||||
"electron-squirrel-startup": "^1.0.0",
|
||||
"keymirror": "0.1.1",
|
||||
"electron-context-menu": "^0.8.0",
|
||||
"winreg": "^1.2.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"screen-snippet": "git+https://github.com/symphonyoss/ScreenSnippet.git#v1.0.1"
|
||||
"screen-snippet": "git+https://github.com/symphonyoss/ScreenSnippet.git#v1.0.1"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user