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:
Kiran Niranjan
2017-05-09 00:00:45 +05:30
committed by Lynn
parent 92822fec3f
commit 4087eb3acc
7 changed files with 171 additions and 33 deletions

2
.gitignore vendored
View File

@@ -3,4 +3,4 @@ dist
.DS_Store
js/preload/_*.js
.idea/
coverage/
coverage/

View 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
};

View File

@@ -8,7 +8,8 @@ const cmds = keyMirror({
setBadgeCount: null,
badgeDataUrl: null,
activate: null,
registerBoundsChange: null
registerBoundsChange: null,
registerActivityDetection: null,
});
module.exports = {

View File

@@ -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;
}
}

View File

@@ -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.

View File

@@ -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()) {

View File

@@ -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"
}
}