From 9c64a80aea2befbfccfed4d30d9ea18798e7b25f Mon Sep 17 00:00:00 2001 From: ste Date: Wed, 27 Mar 2019 16:39:09 +0000 Subject: [PATCH 001/123] GPII-3830: Detect if a drive exists before opening it. --- gpii/node_modules/WindowsUtilities/WindowsUtilities.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js index 8829655fe..c52b5f832 100644 --- a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js +++ b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js @@ -1849,7 +1849,8 @@ gpii.windows.getUsbDrives = function (drivePaths) { } } - return busType === BusTypeUsb; + // Return this drive if it's a USB and the path exists. + return busType === BusTypeUsb && fs.existsSync(drivePath); }); return drives; From 6cca89f6e59e2e6377535ce67a65c47465fdf3d6 Mon Sep 17 00:00:00 2001 From: ste Date: Thu, 4 Apr 2019 20:36:38 +0100 Subject: [PATCH 002/123] GPII-3811: Custom themes working on Windows 10 1809 --- .../src/SpiSettingsHandler.js | 54 ++++++++----------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index 4739e6409..71abe04d6 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -539,8 +539,29 @@ gpii.windows.spiSettingsHandler.setHighContrastTheme = function (newThemeFile, c } var displayName; + // Get the localised display name of the theme. if (themeData && themeData.Theme) { - displayName = gpii.windows.getIndirectString(themeData.Theme.DisplayName); + var highContrastId = themeData.VisualStyles && themeData.VisualStyles.HighContrast; + // In a custom theme, the HighContrast value is a number from 1-4 which identifies the built-in theme upon + // which this theme is based. + if (highContrastId) { + // The localised string resources for the built-in theme names. + var stringIds = [ + "2107", // High Contrast #1 + "2108", // High Contrast #2 + "2103", // High Contrast Black + "2104" // High Contrast White + ]; + + var stringId = stringIds[highContrastId - 1]; + displayName = stringId && + gpii.windows.getIndirectString("@%SystemRoot%\\System32\\themeui.dll,-" + stringId); + } + + if (!displayName) { + displayName = gpii.windows.getIndirectString(themeData.Theme.DisplayName); + } + if (displayName && !dryRun) { gpii.windows.writeRegistryKey("HKEY_CURRENT_USER", "Control Panel\\Accessibility\\HighContrast", "High Contrast Scheme", displayName, "REG_SZ"); @@ -662,38 +683,9 @@ gpii.windows.spiSettingsHandler.themeColours = fluid.freezeRecursive([ ]); /** - * Manually apply the colours of a given .theme file. - * After upgrading to version 1810 of Windows 10, the custom themes provided by Morphic stopped working [GPII-3811]. - * Due to lack of time/knowledge, the cause is currently unknown. To work-around this, the .theme file is read and the - * colours are manually applied. This is called after the high-contrast theme has been applied. * - * @param {String} themeFile The .theme file. - * @param {Boolean} allThemes true to apply every theme, rather than just the custom ones. */ -gpii.windows.spiSettingsHandler.applyCustomTheme = function (themeFile, allThemes) { - var themeData = gpii.iniFile.readFile(themeFile); - fluid.log("applyCustomTHeme", arguments); - - var isValid = themeData && themeData.Theme; - // The custom themes, unlike the built-in ones, have the ThemeId value set. - if (isValid && (themeData.Theme.ThemeId || allThemes)) { - var colors = themeData["Control Panel\\Colors"]; - var colorNumbers = []; - var colorValues = []; - fluid.each(colors, function (rgbValues, name) { - colorNumbers.push(gpii.windows.spiSettingsHandler.themeColours.indexOf(name)); - var rgb = rgbValues.split(" "); - colorValues.push(rgb[0] | rgb[1] << 8 | rgb[2] << 16); - }); - - /* global Int32Array */ - var elementsBuffer = Int32Array.from(colorNumbers); - var valuesBuffer = Int32Array.from(colorValues); - gpii.windows.user32.SetSysColors(elementsBuffer.length, elementsBuffer, valuesBuffer); - // Make the windows update to the new colour scheme - gpii.windows.messages.sendMessage(null, gpii.windows.API_constants.WM_THEMECHANGED, 0, 0); - } - +gpii.windows.spiSettingsHandler.applyCustomTheme = function () { }; fluid.defaults("gpii.windows.spiSettingsHandler.setHighContrastTheme", { From 3e2a1dfba38679b41971e0fb8746efb6b262f2ac Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 5 Apr 2019 12:15:34 +0100 Subject: [PATCH 003/123] GPII-3811: Custom themes also working on Windows 10 older than 1809 --- .../src/SpiSettingsHandler.js | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index 71abe04d6..cbc620427 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -22,7 +22,8 @@ var ref = require("ref"), ffi = require("ffi-napi"), fs = require("fs"), path = require("path"), - mkdirp = require("mkdirp"); + mkdirp = require("mkdirp"), + semver = require("semver"); var fluid = require("gpii-universal"); var gpii = fluid.registerNamespace("gpii"); @@ -541,24 +542,29 @@ gpii.windows.spiSettingsHandler.setHighContrastTheme = function (newThemeFile, c var displayName; // Get the localised display name of the theme. if (themeData && themeData.Theme) { - var highContrastId = themeData.VisualStyles && themeData.VisualStyles.HighContrast; - // In a custom theme, the HighContrast value is a number from 1-4 which identifies the built-in theme upon - // which this theme is based. - if (highContrastId) { - // The localised string resources for the built-in theme names. - var stringIds = [ - "2107", // High Contrast #1 - "2108", // High Contrast #2 - "2103", // High Contrast Black - "2104" // High Contrast White - ]; - - var stringId = stringIds[highContrastId - 1]; - displayName = stringId && - gpii.windows.getIndirectString("@%SystemRoot%\\System32\\themeui.dll,-" + stringId); + var windows1809 = semver.gte(os.release(), "10.0.17134"); + // For windows 10 1809 or above, the name of the built-in theme which the custom theme is based on is used. + if (windows1809) { + // In a custom theme, the HighContrast value is a number from 1-4 which identifies the built-in theme upon + // which this theme is based. + var highContrastId = themeData.VisualStyles && themeData.VisualStyles.HighContrast; + if (highContrastId) { + // The localised string resources for the built-in theme names. + var stringIds = [ + "2107", // High Contrast #1 + "2108", // High Contrast #2 + "2103", // High Contrast Black + "2104" // High Contrast White + ]; + + var stringId = stringIds[highContrastId - 1]; + displayName = stringId && + gpii.windows.getIndirectString("@%SystemRoot%\\System32\\themeui.dll,-" + stringId); + } } if (!displayName) { + // Use the name of this theme. displayName = gpii.windows.getIndirectString(themeData.Theme.DisplayName); } From fcb51a79dc5941bc2c51ccc23e34b95dd5cb1897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Hern=C3=A1ndez?= Date: Thu, 9 May 2019 17:08:28 +0200 Subject: [PATCH 004/123] GPII-3811: Fixed windows version check Taken from from https://github.com/GPII/windows/commit/095c974d526693f95b00aec1409f714a4116ee90 --- gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index cbc620427..856258afb 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -542,7 +542,8 @@ gpii.windows.spiSettingsHandler.setHighContrastTheme = function (newThemeFile, c var displayName; // Get the localised display name of the theme. if (themeData && themeData.Theme) { - var windows1809 = semver.gte(os.release(), "10.0.17134"); + // 10.0.17134 = Windows 1803 + var windows1809 = semver.gt(os.release(), "10.0.17134"); // For windows 10 1809 or above, the name of the built-in theme which the custom theme is based on is used. if (windows1809) { // In a custom theme, the HighContrast value is a number from 1-4 which identifies the built-in theme upon From 30132937487fe03ac82d5141eb146bb886164f0c Mon Sep 17 00:00:00 2001 From: ste Date: Mon, 24 Jun 2019 21:06:54 +0100 Subject: [PATCH 005/123] GPII-3853: Implemented sub-sessions. --- gpii/node_modules/windowsMetrics/README.md | 34 ++++++++ .../windowsMetrics/src/windowsMetrics.js | 4 +- .../test/WindowsMetricsTests.js | 81 ++++++++++++++++++- 3 files changed, 113 insertions(+), 6 deletions(-) diff --git a/gpii/node_modules/windowsMetrics/README.md b/gpii/node_modules/windowsMetrics/README.md index 25ef54a27..a17ce959f 100644 --- a/gpii/node_modules/windowsMetrics/README.md +++ b/gpii/node_modules/windowsMetrics/README.md @@ -278,3 +278,37 @@ For mouse wheels, the number of 'ticks' in the sample is captured (the scroll sp } ``` +## Sub-sessions + +Sub-sessions are periods of activity (separated by inactivity) within a user session. The primary objective of these +is to guess if a different person is using the machine, for open-access installations (ie, GPII is always keyed-in with +the `noUser` key). + +The start of a sub-session is triggered by either a session starting (`SessionStart`) or when the system received input +from the user after a period of inactivity (`inactive-end`). + +```json5 +{ + "module": "metrics", + "event": "subsession-begin", + "level": "INFO", + "data": { + "subSessionID": "zhsq7yv-1691-0" + } +} +``` + +The end of a sub-session is determined when the user keys out (`SessionEnd`), or when the system has not received any +input for a period of time (`inactive-begin`). + +```json5 +{ + "module": "metrics", + "event": "subsession-end", + "level": "INFO", + "data": { + "subSessionID": "zhsq7yv-1691-0", + "duration": 52 + } +} +``` diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index 6f2606a90..6ae304176 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -920,8 +920,8 @@ windows.metrics.userInput = function (that) { if (state.inactive) { // First input from being inactive. var duration = process.hrtime(state.lastInputTime); - that.logMetric("inactive-stop", { duration: duration[0] }); state.inactive = false; + that.events.onActive.fire(duration[0]); } if (state.inactivityTimer) { clearTimeout(state.inactivityTimer); @@ -939,7 +939,7 @@ windows.metrics.userInput = function (that) { */ windows.metrics.userInactive = function (that) { that.state.input.inactive = true; - that.logMetric("inactive-begin"); + that.events.onInactive.fire(); }; /** diff --git a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js index 1053de3a9..0e034e375 100644 --- a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js +++ b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js @@ -1341,12 +1341,15 @@ jqUnit.asyncTest("Testing input metrics: inputHook - only logging desired keys", }); jqUnit.asyncTest("Testing input metrics: inactivity", function () { - jqUnit.expect(4); + jqUnit.expect(7); var logFile = gpii.tests.metrics.setLogFile(); var windowsMetrics = gpii.tests.metrics.windowsMetricsWrapper({ members: { - logFilePath: logFile + logFilePath: logFile, + eventData: { + sessionID: "test-session-id" + } } }); @@ -1366,8 +1369,78 @@ jqUnit.asyncTest("Testing input metrics: inactivity", function () { gpii.windows.metrics.userInput(windowsMetrics); jqUnit.assertFalse("Should not be inactive after last input", state.inactive); - windowsMetrics.stopInputMetrics(); - jqUnit.start(); + // 2nd pass - this will cause the start of a sub-session. + setTimeout(function () { + jqUnit.assertTrue("Should be inactive after timer (pass 2)", state.inactive); + gpii.windows.metrics.userInput(windowsMetrics); + jqUnit.assertFalse("Should not be inactive after last input (pass 2)", state.inactive); + + windowsMetrics.stopInputMetrics(); + + gpii.tests.metrics.completeLogTest(logFile, [ + { + "module": "metrics", + "event": "inactive-begin", + "level": "INFO" + }, + { + "module": "metrics", + "event": "subsession-end", + "level": "INFO", + "data": { + "subSessionID": undefined, + "duration": /^[0-9]+$/ + } + }, + { + "module": "metrics", + "event": "inactive-stop", + "level": "INFO", + "data": { + "duration": /^[0-9]+$/ + } + }, + { + "module": "metrics", + "event": "subsession-begin", + "level": "INFO", + "data": { + "subSessionID": "test-session-id-0" + } + }, + { + "module": "metrics", + "event": "inactive-begin", + "level": "INFO" + }, + { + "module": "metrics", + "event": "subsession-end", + "level": "INFO", + "data": { + "subSessionID": "test-session-id-0", + "duration": /^[0-9]+$/ + } + }, + { + "module": "metrics", + "event": "inactive-stop", + "level": "INFO", + "data": { + "duration": /^[0-9]+$/ + } + }, + { + "module": "metrics", + "event": "subsession-begin", + "level": "INFO", + "data": { + "subSessionID": "test-session-id-1" + } + } + ]); + }, 10); + }, 10); }); From eba83d7670f808577b2e1a932a9c0e47b2de7af9 Mon Sep 17 00:00:00 2001 From: ste Date: Tue, 25 Jun 2019 15:08:51 +0100 Subject: [PATCH 006/123] GPII-3853: Recording inactivity due to entering sleep mode --- .../WindowsUtilities/WindowsUtilities.js | 6 + gpii/node_modules/windowsMetrics/README.md | 34 +++- .../windowsMetrics/src/windowsMetrics.js | 29 +++- .../test/WindowsMetricsTests.js | 164 ++++++++++++++++-- 4 files changed, 210 insertions(+), 23 deletions(-) diff --git a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js index 20188edaa..2744c6dfd 100644 --- a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js +++ b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js @@ -499,6 +499,8 @@ windows.API_constants = { WM_USER: 0x400, // https://docs.microsoft.com/windows/desktop/dataxchg/wm-copydata WM_COPYDATA: 0x4a, + // https://docs.microsoft.com/windows/desktop/power/wm-powerbroadcast + WM_POWERBROADCAST: 0x0218, // Registered messages WM_SHELLHOOK: "SHELLHOOK", @@ -526,6 +528,10 @@ windows.API_constants = { DBT_DEVICEREMOVECOMPLETE: 0x8004, DBT_DEVTYP_VOLUME: 0x2, + // https://docs.microsoft.com/windows/desktop/power/wm-powerbroadcast + PBT_APMSUSPEND: 0x4, + PBT_APMRESUMEAUTOMATIC: 0x12, + // https://msdn.microsoft.com/library/dd375731 virtualKeyCodes: { VK_BACK: 0x08, diff --git a/gpii/node_modules/windowsMetrics/README.md b/gpii/node_modules/windowsMetrics/README.md index a17ce959f..eefafe0d1 100644 --- a/gpii/node_modules/windowsMetrics/README.md +++ b/gpii/node_modules/windowsMetrics/README.md @@ -171,7 +171,9 @@ Example: { "module": "metrics", "event": "inactive-begin", - "level": "INFO" + "data": { + "sleep": true // optional: only present if this event is in response to the system going into sleep mode. + } } ``` @@ -298,8 +300,8 @@ from the user after a period of inactivity (`inactive-end`). } ``` -The end of a sub-session is determined when the user keys out (`SessionEnd`), or when the system has not received any -input for a period of time (`inactive-begin`). +The end of a sub-session is determined when the user keys out (`SessionEnd`), when the system has not received any +input for a period of time (`inactive-begin`), or when the system goes in sleep mode (`power-suspend`). ```json5 { @@ -312,3 +314,29 @@ input for a period of time (`inactive-begin`). } } ``` + +### Sleep + +The `power-suspend` event is recorded when the system goes into sleep mode. This could be either due to the system +being inactive, or the user purposefully making the computer sleep (there is no distinction between the two reasons). + +This will also trigger the `inactive-begin` event. + +```json5 +{ + "module": "metrics", + "event": "power-suspend" +} +``` + +When the computer wakes up, the `power-resume` event is recorded. + +The `inactive-stop` event will not be recorded as a result of this - it will, however, be recorded when the user uses +their keyboard/mouse. + +```json5 +{ + "module": "metrics", + "event": "power-resume" +} +``` diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index 6ae304176..d0d34b4b9 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -65,6 +65,10 @@ fluid.defaults("gpii.windowsMetrics", { funcName: "gpii.windows.metrics.windowMessage", // that, hwnd, msg, wParam, lParam args: [ "{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2", "{arguments}.3" ] + }, + "onInactive.input": { + funcName: "gpii.windows.metrics.userInactive", + args: [ "{that}" ] } }, invokers: { @@ -322,6 +326,17 @@ windows.metrics.windowMessage = function (that, hwnd, msg, wParam, lParam) { process.nextTick(windows.metrics.shellMessage, that, wParam, lParamNumber); break; + case windows.API_constants.WM_POWERBROADCAST: + if (wParam === windows.API_constants.PBT_APMSUSPEND) { + // About to suspend + that.logMetric("power-suspend"); + that.events.onInactive.fire({sleep: true}); + } else if (wParam === windows.API_constants.PBT_APMRESUMEAUTOMATIC) { + // Woke up. (onActive will be fired when there's input) + that.logMetric("power-resume"); + } + break; + default: if (windows.metrics.settingsMessages.indexOf(msg) > -1) { process.nextTick(windows.metrics.configMessage, that, hwnd, msg, wParam, lParamNumber); @@ -919,9 +934,9 @@ windows.metrics.userInput = function (that) { if (state.inactive) { // First input from being inactive. - var duration = process.hrtime(state.lastInputTime); + var duration = state.lastInputTime ? process.hrtime(state.lastInputTime)[0] : undefined; state.inactive = false; - that.events.onActive.fire(duration[0]); + that.events.onActive.fire(duration); } if (state.inactivityTimer) { clearTimeout(state.inactivityTimer); @@ -929,17 +944,21 @@ windows.metrics.userInput = function (that) { } state.lastInputTime = process.hrtime(); - state.inactivityTimer = setTimeout(windows.metrics.userInactive, that.config.input.inactiveTime, that); + state.inactivityTimer = setTimeout(windows.metrics.userInactive, that.config.input.inactiveTime, that, + that.events.onInactive); }; /** * Called when there's been some time since receiving input from the user. * * @param {Component} that The gpii.windowsMetrics instance. + * @param {Event} inactiveEvent [optional] The event to fire. */ -windows.metrics.userInactive = function (that) { +windows.metrics.userInactive = function (that, inactiveEvent) { that.state.input.inactive = true; - that.events.onInactive.fire(); + if (inactiveEvent) { + inactiveEvent.fire(); + } }; /** diff --git a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js index 0e034e375..19ad62d4f 100644 --- a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js +++ b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js @@ -715,8 +715,105 @@ gpii.tests.metrics.inputHookTests = fluid.freezeRecursive([ } ]); -gpii.tests.metrics.allLogLines = []; +gpii.tests.metrics.powerMessageTests = fluid.freezeRecursive([ + { + // Entering sleep state + // msg, wParam, lParam + input: [gpii.windows.API_constants.WM_POWERBROADCAST, gpii.windows.API_constants.PBT_APMSUSPEND, 0], + expect: [{ + module: "metrics", + event: "power-suspend" + }, { + module: "metrics", + event: "inactive-begin", + data: { + sleep: true + } + // }, { + // module: "metrics", + // event: "subsession-end", + // data: { + // "subSessionID": undefined, + // "duration": /^[0-9]+$/ + // } + }] + }, + { + // Resuming from sleep + input: [gpii.windows.API_constants.WM_POWERBROADCAST, gpii.windows.API_constants.PBT_APMRESUMEAUTOMATIC, 0], + expect: [{ + module: "metrics", + event: "power-resume" + }] + }, + { + // Start user activity + input: "userInput", + expect: [{ + module: "metrics", + event: "inactive-stop" + }, { + module: "metrics", + event: "subsession-begin", + data: { + subSessionID: "test-session-id-0" + } + }] + }, + { + // Re-entering sleep state + input: [gpii.windows.API_constants.WM_POWERBROADCAST, gpii.windows.API_constants.PBT_APMSUSPEND, 0], + expect: [{ + module: "metrics", + event: "power-suspend" + }, { + module: "metrics", + event: "inactive-begin", + data: { + sleep: true + } + }, { + module: "metrics", + event: "subsession-end", + data: { + subSessionID: "test-session-id-0", + duration: /^[0-9]+$/ + } + }] + }, + { + // Entering sleep state (duplicate) + input: [gpii.windows.API_constants.WM_POWERBROADCAST, gpii.windows.API_constants.PBT_APMSUSPEND, 0], + expect: [{ + module: "metrics", + event: "power-suspend" + }, { + module: "metrics", + event: "inactive-begin", + data: { + sleep: true + } + }] + }, + { + // Resuming from sleep again + input: [gpii.windows.API_constants.WM_POWERBROADCAST, gpii.windows.API_constants.PBT_APMRESUMEAUTOMATIC, 0], + expect: [{ + module: "metrics", + event: "power-resume" + }] + }, + { + // Resuming from sleep (duplicate) + input: [gpii.windows.API_constants.WM_POWERBROADCAST, gpii.windows.API_constants.PBT_APMRESUMEAUTOMATIC, 0], + expect: [{ + module: "metrics", + event: "power-resume" + }] + } +]); +gpii.tests.metrics.allLogLines = []; /** * Start a log server, to capture everything that's being logged in this test module. The logs will then be looked at @@ -841,9 +938,10 @@ gpii.tests.metrics.deepMatch = function (subject, expected, maxDepth) { * * @param {String} logFile The file to read. * @param {Array} expected An array of log items to search for (in order of how they should be found). + * @param {Boolean} strict Checks all logged lines, raise an error on extraneous log lines. * @return {Promise} Resolves when all expected lines where found, or rejects if the end of the log is reached first. */ -gpii.tests.metrics.expectLogLines = function (logFile, expected) { +gpii.tests.metrics.expectLogLines = function (logFile, expected, strict) { jqUnit.expect(expected.length); var promise = fluid.promise(); var reader = readline.createInterface({ @@ -861,7 +959,7 @@ gpii.tests.metrics.expectLogLines = function (logFile, expected) { }); reader.on("line", function (line) { - if (complete) { + if (complete && !strict) { return; } @@ -876,10 +974,16 @@ gpii.tests.metrics.expectLogLines = function (logFile, expected) { remainingLines = []; if (index >= expected.length) { complete = true; - reader.close(); + if (!strict) { + reader.close(); + } } else { currentExpected = expected[index]; } + } else if (strict) { + fluid.log("Expected: ", currentExpected); + fluid.log("Found: ", obj); + jqUnit.fail("Non-matching log line found: " + line); } }); @@ -902,9 +1006,10 @@ gpii.tests.metrics.expectLogLines = function (logFile, expected) { * * @param {String} logFile The log file. * @param {Array} expectedLines An array of log items to search for (in order of how they should be found). + * @param {Boolean} strict Checks all logged lines, raise an error on extraneous log lines. */ -gpii.tests.metrics.completeLogTest = function (logFile, expectedLines) { - gpii.tests.metrics.expectLogLines(logFile, expectedLines).then(function () { +gpii.tests.metrics.completeLogTest = function (logFile, expectedLines, strict) { + gpii.tests.metrics.expectLogLines(logFile, expectedLines, strict).then(function () { jqUnit.assert("All expected lines should be found."); jqUnit.start(); }, function (err) { @@ -1383,15 +1488,6 @@ jqUnit.asyncTest("Testing input metrics: inactivity", function () { "event": "inactive-begin", "level": "INFO" }, - { - "module": "metrics", - "event": "subsession-end", - "level": "INFO", - "data": { - "subSessionID": undefined, - "duration": /^[0-9]+$/ - } - }, { "module": "metrics", "event": "inactive-stop", @@ -1488,6 +1584,44 @@ jqUnit.asyncTest("Testing version and system info logging", function () { ]); }); +jqUnit.asyncTest("Testing power notifications", function () { + jqUnit.expect(1); + + var logFile = gpii.tests.metrics.setLogFile(); + var windowsMetrics = gpii.tests.metrics.windowsMetricsWrapper({ + members: { + logFilePath: logFile, + eventData: { + sessionID: "test-session-id" + } + } + }); + + var expectedLines = [ + // Ignore the initial events + { + module: "metrics", event: "version" + }, { + module: "metrics", event: "system-info" + } + ]; + + + fluid.each(gpii.tests.metrics.powerMessageTests, function (test) { + if (test.input === "userInput") { + gpii.windows.metrics.userInput(windowsMetrics); + } else { + var msg = test.input[0], + wp = test.input[1], + lp = test.input[2]; + + gpii.windows.metrics.windowMessage(windowsMetrics, 0, msg, wp, lp); + } + expectedLines.push.apply(expectedLines, fluid.makeArray(test.expect)); + }); + + gpii.tests.metrics.completeLogTest(logFile, expectedLines, true); +}); /** * Checks the documentation (README.md) by extracting the JSON code regions for the following: From 98344b555f4e75b7417c7e8222cd0e8b663e20c7 Mon Sep 17 00:00:00 2001 From: ste Date: Tue, 25 Jun 2019 21:38:07 +0100 Subject: [PATCH 007/123] GPII-3853: Added calculation of duration between related events. --- .../windowsMetrics/src/windowsMetrics.js | 7 +- .../test/WindowsMetricsTests.js | 71 +++++++++---------- 2 files changed, 34 insertions(+), 44 deletions(-) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index d0d34b4b9..89d8e6f8a 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -148,8 +148,7 @@ fluid.defaults("gpii.windowsMetrics", { specialCount: 0, // Mouse position lastPos: null, - distance: 0, - lastInputTime: 0 + distance: 0 } }, keyboardHookHandle: null, @@ -934,16 +933,14 @@ windows.metrics.userInput = function (that) { if (state.inactive) { // First input from being inactive. - var duration = state.lastInputTime ? process.hrtime(state.lastInputTime)[0] : undefined; state.inactive = false; - that.events.onActive.fire(duration); + that.events.onActive.fire(); } if (state.inactivityTimer) { clearTimeout(state.inactivityTimer); state.inactivityTimer = null; } - state.lastInputTime = process.hrtime(); state.inactivityTimer = setTimeout(windows.metrics.userInactive, that.config.input.inactiveTime, that, that.events.onInactive); }; diff --git a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js index 19ad62d4f..b3337c718 100644 --- a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js +++ b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js @@ -729,13 +729,6 @@ gpii.tests.metrics.powerMessageTests = fluid.freezeRecursive([ data: { sleep: true } - // }, { - // module: "metrics", - // event: "subsession-end", - // data: { - // "subSessionID": undefined, - // "duration": /^[0-9]+$/ - // } }] }, { @@ -1484,54 +1477,54 @@ jqUnit.asyncTest("Testing input metrics: inactivity", function () { gpii.tests.metrics.completeLogTest(logFile, [ { - "module": "metrics", - "event": "inactive-begin", - "level": "INFO" + module: "metrics", + event: "inactive-begin", + level: "INFO" }, { - "module": "metrics", - "event": "inactive-stop", - "level": "INFO", - "data": { - "duration": /^[0-9]+$/ + module: "metrics", + event: "inactive-stop", + level: "INFO", + data: { + duration: /^[0-9]+$/ } }, { - "module": "metrics", - "event": "subsession-begin", - "level": "INFO", - "data": { - "subSessionID": "test-session-id-0" + module: "metrics", + event: "subsession-begin", + level: "INFO", + data: { + subSessionID: "test-session-id-0" } }, { - "module": "metrics", - "event": "inactive-begin", - "level": "INFO" + module: "metrics", + event: "inactive-begin", + level: "INFO" }, { - "module": "metrics", - "event": "subsession-end", - "level": "INFO", - "data": { - "subSessionID": "test-session-id-0", - "duration": /^[0-9]+$/ + module: "metrics", + event: "subsession-end", + level: "INFO", + data: { + subSessionID: "test-session-id-0", + duration: /^[0-9]+$/ } }, { - "module": "metrics", - "event": "inactive-stop", - "level": "INFO", - "data": { - "duration": /^[0-9]+$/ + module: "metrics", + event: "inactive-stop", + level: "INFO", + data: { + duration: /^[0-9]+$/ } }, { - "module": "metrics", - "event": "subsession-begin", - "level": "INFO", - "data": { - "subSessionID": "test-session-id-1" + module: "metrics", + event: "subsession-begin", + level: "INFO", + data: { + subSessionID: "test-session-id-1" } } ]); From 8c525ae5a6747ec34a1868260c10f55e3b1b5712 Mon Sep 17 00:00:00 2001 From: ste Date: Wed, 26 Jun 2019 23:31:10 +0100 Subject: [PATCH 008/123] GPII-3878: Ignoring extreme mouse distances. --- .../windowsMetrics/src/windowsMetrics.js | 8 +++++++- .../test/WindowsMetricsTests.js | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index 89d8e6f8a..7115d31fc 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -872,6 +872,12 @@ windows.metrics.recordMouseEvent = function (that, button, pos) { if (state.lastPos) { state.distance += Math.sqrt(Math.pow(state.lastPos.x - pos.x, 2) + Math.pow(state.lastPos.y - pos.y, 2)); + // There have been some very large mouse distances captured (billions of pixels). The cause is unknown, so let's + // just ignore anything that's larger than expected [GPII-3878]. + if (state.distance > 0xffff) { + fluid.log("Dropping large mouse distance"); + state.distance = 0; + } } state.lastPos = pos; @@ -965,7 +971,7 @@ windows.metrics.userInactive = function (that, inactiveEvent) { * @param {Component} that The gpii.windowsMetrics instance. * @param {Number} code If less than zero, then don't process. * @param {Number} message The input message (eg, WM_KEYDOWN or WM_MOUSEMOVE). - * @param {Number} eventData A KBDLLHOOKSTRUCT or MSDLLHOOKSTRUCT structure. + * @param {Number} eventData A KBDLLHOOKSTRUCT or MSLLHOOKSTRUCT structure. */ windows.metrics.inputHook = function (that, code, message, eventData) { if (code >= 0) { diff --git a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js index b3337c718..d0049137c 100644 --- a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js +++ b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js @@ -637,6 +637,25 @@ gpii.tests.metrics.inputHookTests = fluid.freezeRecursive([ data: { button: 1, distance: 61 } } }, + { // Large movement + input: { + nCode: 0, + wParam: gpii.windows.API_constants.WM_LBUTTONUP, + lParam: { + ptX: 0xffffff, + ptY: 0xffffff, + mouseData: 0, + flags: 0, + time: 0, + dwExtraInfo: 0 + } + }, + expect: { + module: "metrics", + event: "mouse", + data: { button: 1, distance: 0 } + } + }, { // Mouse wheel up input: { nCode: 0, From 729ce6b446772780dbef67acf65d6e8b35997e6e Mon Sep 17 00:00:00 2001 From: ste Date: Thu, 27 Jun 2019 13:01:36 +0100 Subject: [PATCH 009/123] GPII-3877: Fixed negative key press times --- gpii/node_modules/windowsMetrics/src/windowsMetrics.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index 7115d31fc..5b905f548 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -797,6 +797,10 @@ windows.metrics.recordKeyTiming = function (that, timestamp, specialKey, keyValu if (keyTime > config.sessionTimeout) { // Only care about the time between keys in a typing session. keyTime = 0; + } else if (keyTime < 0) { + // The timestamp is 32bit, and wraps when it gets too big, causing negative times to be recorded [GPII-3877]. + // https://docs.microsoft.com/windows/desktop/api/winuser/nf-winuser-getmessagetime + keyTime = 0; } /* "A recordable typing session would be determined only once a threshold of thirty seconds of typing has been From 5eba5483a56f030c61ca7314cbf24bd621c3b878 Mon Sep 17 00:00:00 2001 From: ste Date: Thu, 27 Jun 2019 19:05:42 +0100 Subject: [PATCH 010/123] GPII-3906: Capturing window de-activation, app-inactive --- .../windowsMetrics/src/windowsMetrics.js | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index 5b905f548..bb18211db 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -118,8 +118,7 @@ fluid.defaults("gpii.windowsMetrics", { }, members: { config: { - application: { - }, + application: {}, input: { // Minimum typing session time, in milliseconds. minSession: 30000, @@ -153,6 +152,9 @@ fluid.defaults("gpii.windowsMetrics", { }, keyboardHookHandle: null, mouseHookHandle: null + }, + durationEvents: { + "app-active": "app-inactive" } }); @@ -294,18 +296,20 @@ windows.metrics.stopApplicationMetrics = function (that) { /** * Logs the application active metric - how long an application has been active for. - * Called when a new window is being activated, while currentProcess refers to the application losing focus. + * Called when a new window is being activated or deactivated, while currentProcess refers to the application losing + * focus. * * @param {Component} that The gpii.windowsMetrics instance. * @param {WindowInfo} windowInfo The activated window. + * @param {Boolean} activated true if the window is being activated, false if it's deactivated */ -windows.metrics.logAppActivate = function (that, windowInfo) { +windows.metrics.logAppActivate = function (that, windowInfo, activated) { var data = { exe: windows.metrics.genericisePath(windowInfo.exe), window: windowInfo.pid.toString(36) + "-" + windowInfo.hwnd.toString(36), windowClass: windowInfo.className }; - that.logMetric("app-active", data); + that.logMetric(activated ? "app-active" : "app-inactive", data); }; /** @@ -508,21 +512,29 @@ windows.metrics.windowActivated = function (that, hwnd) { hwnd = windows.user32.GetForegroundWindow(); } - if (hwnd && hwnd !== state.activeWindow) { - var windowInfo = state.knownWindows[hwnd]; - if (!windowInfo) { - // The window isn't known - it may have been created before this process. - that.windowCreated(hwnd); - windowInfo = state.knownWindows[hwnd]; + if (hwnd !== state.activeWindow) { + // Record the window that's losing focus. + var oldWindowInfo = state.knownWindows[state.activeWindow]; + if (oldWindowInfo) { + windows.metrics.logAppActivate(that, oldWindowInfo, false); } + // Record the window that's being activated. + if (hwnd) { + var windowInfo = state.knownWindows[hwnd]; + if (!windowInfo) { + // The window isn't known - it may have been created before this process. + that.windowCreated(hwnd); + windowInfo = state.knownWindows[hwnd]; + } - if (windowInfo) { - that.state.application.currentProcess = { - pid: windowInfo.pid, - exe: windows.metrics.genericisePath(windowInfo.exe) - }; + if (windowInfo) { + that.state.application.currentProcess = { + pid: windowInfo.pid, + exe: windows.metrics.genericisePath(windowInfo.exe) + }; - windows.metrics.logAppActivate(that, windowInfo); + windows.metrics.logAppActivate(that, windowInfo, true); + } } } state.activeWindow = hwnd; From 880ecc8d0c023890bbe8338e448c9cb0c2c396a8 Mon Sep 17 00:00:00 2001 From: ste Date: Thu, 27 Jun 2019 19:31:42 +0100 Subject: [PATCH 011/123] GPII-3906: app-inactive tests --- gpii/node_modules/windowsMetrics/README.md | 18 ++++++++++++++++++ .../windowsMetrics/test/WindowsMetricsTests.js | 15 +++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/gpii/node_modules/windowsMetrics/README.md b/gpii/node_modules/windowsMetrics/README.md index eefafe0d1..bc7cb5a1f 100644 --- a/gpii/node_modules/windowsMetrics/README.md +++ b/gpii/node_modules/windowsMetrics/README.md @@ -162,6 +162,24 @@ Example: } ``` +Apart from the first one, every activation of a Window will be preceded by a de-activation. This is identified by the +`app-inactive` event. The `duration` field is the time since the last `app-active` event, therefore how long the window +has been active for. + +```json5 +{ + "module": "metrics", + "event": "app-inactive", + "level": "INFO", + "data": { + "duration": 54, + "exe": "%SystemRoot%\\notepad.exe", + "window": "41k-494w", + "windowClass": "Notepad", + } +} +``` + ### Inactivity When the user is not using the computer. diff --git a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js index d0049137c..603c4de80 100644 --- a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js +++ b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js @@ -1077,6 +1077,8 @@ jqUnit.asyncTest("Testing application metrics", function () { } }); + windowsMetrics.metrics = windowsMetrics; + // Pick three windows (owned by different processes) that already exist, and pretend they've became active. var windows = []; var exes = {}; @@ -1105,6 +1107,7 @@ jqUnit.asyncTest("Testing application metrics", function () { var activeWindowIndex = 0; var expectedLines = []; + var lastWindow = null; // Activate the windows a few times. var activateWindow = function (remaining) { @@ -1116,6 +1119,17 @@ jqUnit.asyncTest("Testing application metrics", function () { gpii.windows.API_constants.HSHELL_WINDOWACTIVATED, windows[activeWindowIndex].hwnd); if (remaining > 0) { + if (lastWindow !== null) { + expectedLines.push({ + module: "metrics", + event: "app-inactive", + data: { + exe: windows[lastWindow].exe, + window: fluid.VALUE + } + }); + } + if (!windows[activeWindowIndex].done) { // The launch event is only expected on a window's first occurrence. expectedLines.push({ @@ -1157,6 +1171,7 @@ jqUnit.asyncTest("Testing application metrics", function () { windowsMetrics.destroy(); }, 100); } + lastWindow = activeWindowIndex; }; // Start monitoring. From 5a0f3af6958b4c6f53d3599f37818c93fb6cefb1 Mon Sep 17 00:00:00 2001 From: ste Date: Thu, 27 Jun 2019 19:50:47 +0100 Subject: [PATCH 012/123] GPII-3876: Made typing-session metric work. --- gpii/node_modules/windowsMetrics/src/windowsMetrics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index bb18211db..e7e1a403a 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -125,7 +125,7 @@ fluid.defaults("gpii.windowsMetrics", { // The time a session will last with no activity, in milliseconds. sessionTimeout: 60000, // Minimum number of keys in a typing session time. - minSessionKeys: 30000, + minSessionKeys: 10, // Milliseconds of no input to assume inactive inactiveTime: 300000 } From 2b0cc62aabd0d8636bb91ecc05f64ae00c3b93bf Mon Sep 17 00:00:00 2001 From: ste Date: Mon, 1 Jul 2019 22:52:44 +0100 Subject: [PATCH 013/123] GPII-3853: Recording space bar --- gpii/node_modules/windowsMetrics/src/windowsMetrics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index e7e1a403a..17a024b0e 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -777,7 +777,7 @@ windows.metrics.specialKeys = fluid.freezeRecursive((function () { "SELECT", "PRINT", "EXECUTE", "SNAPSHOT", "INSERT", "DELETE", "HELP", "LWIN", "RWIN", "SCROLL", "NUMLOCK", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "F19", "F20", "F21", "F22", "F23", "F24", - "CONTROL", "MENU", "SHIFT" + "CONTROL", "MENU", "SHIFT", "SPACE" ]; var special = {}; From 21a24efc93cf7fed9ddc2a254e64c55e44e60998 Mon Sep 17 00:00:00 2001 From: ste Date: Mon, 1 Jul 2019 22:55:47 +0100 Subject: [PATCH 014/123] GPII-3853: Recording if a morphic is active --- gpii/node_modules/windowsMetrics/src/windowsMetrics.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index 17a024b0e..0a6eb9c8c 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -102,7 +102,7 @@ fluid.defaults("gpii.windowsMetrics", { }, windowActivated: { funcName: "gpii.windows.metrics.windowActivated", - args: ["{that}", "{arguments}.0"] // window handle (hwnd) + args: ["{that}", "{eventLog}", "{arguments}.0"] // window handle (hwnd) }, windowCreated: { funcName: "gpii.windows.metrics.windowCreated", @@ -503,9 +503,10 @@ windows.metrics.windowDestroyed = function (that, hwnd) { /** * Called when a window has been activated. * @param {Component} that The gpii.windowsMetrics component. + * @param {Component} eventLog The gpii.eventLog component. * @param {Number} hwnd The window handle. */ -windows.metrics.windowActivated = function (that, hwnd) { +windows.metrics.windowActivated = function (that, eventLog, hwnd) { var state = that.state.application; if (!hwnd) { @@ -533,6 +534,7 @@ windows.metrics.windowActivated = function (that, hwnd) { exe: windows.metrics.genericisePath(windowInfo.exe) }; + eventLog.setState("app", windowInfo.pid === process.pid ? "active" : null); windows.metrics.logAppActivate(that, windowInfo, true); } } From e0f4ee34893d59b32a774b8ccdcdc415469ce5b9 Mon Sep 17 00:00:00 2001 From: ste Date: Mon, 1 Jul 2019 23:01:46 +0100 Subject: [PATCH 015/123] GPII-3853: Updated test to treat space as non-printable --- gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js index 603c4de80..5e3be32fd 100644 --- a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js +++ b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js @@ -1353,7 +1353,8 @@ jqUnit.test("Testing input metrics: inputHook - not logging character keys", fun // Return true if keyCode corresponds to a character key. var isCharacterKey = function (keyCode) { var char = gpii.windows.user32.MapVirtualKeyW(keyCode, gpii.windows.API_constants.MAPVK_VK_TO_CHAR); - return char && char >= 0x20; + // Returns space (0x20) as non-printable + return char && char > 0x20; }; isCharacterKey(gpii.windows.API_constants.virtualKeyCodes.VK_BACK); From 8900484f05671909d3cbba6949b35384cecfd98d Mon Sep 17 00:00:00 2001 From: ste Date: Mon, 1 Jul 2019 23:10:03 +0100 Subject: [PATCH 016/123] GPII-3853: Documenting recent changes --- gpii/node_modules/windowsMetrics/src/windowsMetrics.js | 1 + 1 file changed, 1 insertion(+) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index 0a6eb9c8c..a261b7933 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -518,6 +518,7 @@ windows.metrics.windowActivated = function (that, eventLog, hwnd) { var oldWindowInfo = state.knownWindows[state.activeWindow]; if (oldWindowInfo) { windows.metrics.logAppActivate(that, oldWindowInfo, false); + eventLog.setState("app", null); } // Record the window that's being activated. if (hwnd) { From ee011b43442eda5a9a5fd3bd52d4d72672fc008d Mon Sep 17 00:00:00 2001 From: ste Date: Tue, 2 Jul 2019 13:23:43 +0100 Subject: [PATCH 017/123] GPII-3853: Disabling metrics via siteConfig --- .../node_modules/windowsMetrics/src/windowsMetrics.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index a261b7933..f2ceea542 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -689,11 +689,16 @@ windows.metrics.genericisePath = function (rawPath, env) { windows.metrics.startInputMetrics = function (that) { windows.metrics.stopInputMetrics(that); - var disable = process.env.GPII_NO_INPUT_METRICS; + var disable = false; + if (process.env.GPII_NO_INPUT_METRICS) { + disable = "GPII_NO_INPUT_METRICS"; + } else if (that.options.siteConfig.disable || that.options.siteConfig.disableInput) { + disable = "siteConfig"; + }; if (disable) { - fluid.log(fluid.logLevel.WARN, "Input metrics disabled with GPII_NO_INPUT_METRICS."); - that.logMetrics("input-disabled"); + fluid.log(fluid.logLevel.WARN, "Input metrics disabled by " + disable); + that.logMetrics("input-disabled", { reason: disable } ); } else if (process.versions.electron || that.options.forceInputMetrics) { var callHook = function (code, wparam, lparam) { // Handle the hook in the next tick, to allow the hook callback to return quickly. The lparam data needs to From a2489a03b44e7bde06285dcad2902722cb4c2086 Mon Sep 17 00:00:00 2001 From: ste Date: Tue, 2 Jul 2019 14:23:56 +0100 Subject: [PATCH 018/123] GPII-3830: Fixed array check in tests --- .../node_modules/WindowsUtilities/test/WindowsUtilitiesTests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpii/node_modules/WindowsUtilities/test/WindowsUtilitiesTests.js b/gpii/node_modules/WindowsUtilities/test/WindowsUtilitiesTests.js index 6f6365e30..b8b160086 100644 --- a/gpii/node_modules/WindowsUtilities/test/WindowsUtilitiesTests.js +++ b/gpii/node_modules/WindowsUtilities/test/WindowsUtilitiesTests.js @@ -287,7 +287,7 @@ jqUnit.test("Testing getUsbDrives", function () { } }); - jqUnit.assertDeepEq("getUsbDrives should return the expected drive paths", expected, result); + jqUnit.assertDeepEq("getUsbDrives should return the expected drive paths", expected.sort(), result.sort()); } finally { fluid.each(Object.keys(origValues), function (funcName) { From 6f75a1b263f3ef007798e32bfd6c5fe84baca3e1 Mon Sep 17 00:00:00 2001 From: ste Date: Tue, 2 Jul 2019 15:11:39 +0100 Subject: [PATCH 019/123] GPII-3853: Updating package reference to related branch --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8f4048aea..8c6285c51 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dependencies": { "edge-js": "10.3.1", "ffi-napi": "2.4.3", - "gpii-universal": "0.3.0-dev.20190529T130102Z.a3fea390", + "gpii-universal": "stegru/universal#GPII-3853", "@pokusew/pcsclite": "0.4.18", "ref": "1.3.4", "ref-struct": "1.1.0", From da16a5cf124c7500723739df7b726bf06ed97ece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Tue, 9 Apr 2019 21:26:41 +0200 Subject: [PATCH 020/123] GPII-3845: Moved high-contrast localized name retrieval operation into new subprocess (cherry picked from commit 3fa2a215b683fc8b908edf12769ddbde8b79475c) --- .../src/GetHighContrastSchemeName.js | 27 ++++++++ .../src/SpiSettingsHandler.js | 68 +++++++++++++------ 2 files changed, 76 insertions(+), 19 deletions(-) create mode 100644 gpii/node_modules/spiSettingsHandler/src/GetHighContrastSchemeName.js diff --git a/gpii/node_modules/spiSettingsHandler/src/GetHighContrastSchemeName.js b/gpii/node_modules/spiSettingsHandler/src/GetHighContrastSchemeName.js new file mode 100644 index 000000000..34a551074 --- /dev/null +++ b/gpii/node_modules/spiSettingsHandler/src/GetHighContrastSchemeName.js @@ -0,0 +1,27 @@ +/** + * Child process to get localised high-contrast theme name. + * + * Copyright 2018 Raising the Floor - International + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * The R&D leading to these results received funding from the + * Department of Education - Grant H421A150005 (GPII-APCP). However, + * these results do not necessarily represent the policy of the + * Department of Education, and you should not assume endorsement by the + * Federal Government. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ +"use strict"; + +var gpii = fluid.registerNamespace("gpii"); + +require("../../WindowsUtilities/WindowsUtilities.js"); + +var stringId = process.stringId; +var localizedName = gpii.windows.getIndirectString("@%SystemRoot%\\System32\\themeui.dll,-" + stringId); + +process.send(localizedName); diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index 856258afb..46268e3e5 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -23,7 +23,9 @@ var ref = require("ref"), fs = require("fs"), path = require("path"), mkdirp = require("mkdirp"), - semver = require("semver"); + semver = require("semver"), + child_process = require("child_process"); + var fluid = require("gpii-universal"); var gpii = fluid.registerNamespace("gpii"); @@ -539,7 +541,7 @@ gpii.windows.spiSettingsHandler.setHighContrastTheme = function (newThemeFile, c themeData = null; } - var displayName; + var promise = fluid.promise(); // Get the localised display name of the theme. if (themeData && themeData.Theme) { // 10.0.17134 = Windows 1803 @@ -558,28 +560,56 @@ gpii.windows.spiSettingsHandler.setHighContrastTheme = function (newThemeFile, c "2104" // High Contrast White ]; - var stringId = stringIds[highContrastId - 1]; - displayName = stringId && - gpii.windows.getIndirectString("@%SystemRoot%\\System32\\themeui.dll,-" + stringId); + var options = { + stringId: stringIds[highContrastId - 1], + execArgv: [] + }; + + var child = child_process.fork(__dirname + "/GetHighContrastSchemeName.js", options); + var timer = setTimeout(function () { + child.kill(); + timer = null; + }, 10000); + + child.on("message", function (hcName) { + var displayName = options.stringId && hcName; + + if (!displayName) { + // Use the name of this theme. + displayName = gpii.windows.getIndirectString(themeData.Theme.DisplayName); + } + + if (displayName && !dryRun) { + gpii.windows.writeRegistryKey("HKEY_CURRENT_USER", "Control Panel\\Accessibility\\HighContrast", + "High Contrast Scheme", displayName, "REG_SZ"); + // Store the current theme file so it can be referred to later. + gpii.windows.writeRegistryKey( + "HKEY_CURRENT_USER", "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\HighContrast", + "LastSet", newThemeFile, "REG_SZ"); + } + + promise.resolve(displayName); + }); + + child.on("exit", function () { + if (timer) { + clearTimeout(timer); + } + + if (!promise.disposition) { + fluid.log("Error: Getting high-contrast localized name failed."); + promise.reject({ + isError: true, + message: "Timed out waiting for high-contrast localized name." + }); + } + }); } } - if (!displayName) { - // Use the name of this theme. - displayName = gpii.windows.getIndirectString(themeData.Theme.DisplayName); - } - - if (displayName && !dryRun) { - gpii.windows.writeRegistryKey("HKEY_CURRENT_USER", "Control Panel\\Accessibility\\HighContrast", - "High Contrast Scheme", displayName, "REG_SZ"); - // Store the current theme file so it can be referred to later. - gpii.windows.writeRegistryKey( - "HKEY_CURRENT_USER", "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\HighContrast", - "LastSet", newThemeFile, "REG_SZ"); - } } - return displayName; + return promise; }; /** From ed894d6d1d4a739894b9307338fffafc7b530cfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Tue, 9 Apr 2019 23:10:28 +0200 Subject: [PATCH 021/123] GPII-3845: Added required import and fixed returned 'undefined' in case of fail (cherry picked from commit f9e58b357d554b9ac8991301be546e7d39e9561c) --- .../spiSettingsHandler/src/GetHighContrastSchemeName.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/GetHighContrastSchemeName.js b/gpii/node_modules/spiSettingsHandler/src/GetHighContrastSchemeName.js index 34a551074..3c0eae96f 100644 --- a/gpii/node_modules/spiSettingsHandler/src/GetHighContrastSchemeName.js +++ b/gpii/node_modules/spiSettingsHandler/src/GetHighContrastSchemeName.js @@ -17,11 +17,15 @@ */ "use strict"; -var gpii = fluid.registerNamespace("gpii"); +var fluid = require("gpii-universal"), + gpii = fluid.registerNamespace("gpii"); require("../../WindowsUtilities/WindowsUtilities.js"); -var stringId = process.stringId; +var stringId = process.env.stringId; var localizedName = gpii.windows.getIndirectString("@%SystemRoot%\\System32\\themeui.dll,-" + stringId); +if (localizedName === undefined) { + localizedName = "undefined"; +} process.send(localizedName); From 987a2ec84b75fb82622324f2ecbd72fa40ba1abd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Tue, 9 Apr 2019 23:17:04 +0200 Subject: [PATCH 022/123] GPII-3845: Fixed options passed to child process, improved message reception and windows version branching (cherry picked from commit 68d76339a87c14ea74b6323243b26e2c1751dbcb) --- .../src/SpiSettingsHandler.js | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index 46268e3e5..68e3df060 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -542,6 +542,7 @@ gpii.windows.spiSettingsHandler.setHighContrastTheme = function (newThemeFile, c } var promise = fluid.promise(); + var displayName; // Get the localised display name of the theme. if (themeData && themeData.Theme) { // 10.0.17134 = Windows 1803 @@ -561,7 +562,9 @@ gpii.windows.spiSettingsHandler.setHighContrastTheme = function (newThemeFile, c ]; var options = { - stringId: stringIds[highContrastId - 1], + env: { + stringId: stringIds[highContrastId - 1] + }, execArgv: [] }; @@ -572,11 +575,10 @@ gpii.windows.spiSettingsHandler.setHighContrastTheme = function (newThemeFile, c }, 10000); child.on("message", function (hcName) { - var displayName = options.stringId && hcName; - - if (!displayName) { - // Use the name of this theme. - displayName = gpii.windows.getIndirectString(themeData.Theme.DisplayName); + if (hcName === "undefined") { + displayName = options.env.stringId; + } else { + displayName = hcName; } if (displayName && !dryRun) { @@ -605,6 +607,20 @@ gpii.windows.spiSettingsHandler.setHighContrastTheme = function (newThemeFile, c } }); } + } else { + if (!displayName) { + // Use the name of this theme. + displayName = gpii.windows.getIndirectString(themeData.Theme.DisplayName); + } + + if (displayName && !dryRun) { + gpii.windows.writeRegistryKey("HKEY_CURRENT_USER", "Control Panel\\Accessibility\\HighContrast", + "High Contrast Scheme", displayName, "REG_SZ"); + // Store the current theme file so it can be referred to later. + gpii.windows.writeRegistryKey( + "HKEY_CURRENT_USER", "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\HighContrast", + "LastSet", newThemeFile, "REG_SZ"); + } } } From d73c08f974e05dcd90afda795cbbad0429868c23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Wed, 10 Apr 2019 01:05:15 +0200 Subject: [PATCH 023/123] GPII-3845: Making displayName resolution logic more similar to previous one (cherry picked from commit 31176fb0a715112f1d0c6e807542e941ec3f3ae8) --- .../spiSettingsHandler/src/SpiSettingsHandler.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index 68e3df060..ba31ee730 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -575,12 +575,15 @@ gpii.windows.spiSettingsHandler.setHighContrastTheme = function (newThemeFile, c }, 10000); child.on("message", function (hcName) { - if (hcName === "undefined") { - displayName = options.env.stringId; - } else { + if (hcName !== "undefined") { displayName = hcName; } + if (!displayName) { + // Use the name of this theme. + displayName = gpii.windows.getIndirectString(themeData.Theme.DisplayName); + } + if (displayName && !dryRun) { gpii.windows.writeRegistryKey("HKEY_CURRENT_USER", "Control Panel\\Accessibility\\HighContrast", "High Contrast Scheme", displayName, "REG_SZ"); From 57a00dae889ccb2f1fd3f6874f0aa3ba340b597d Mon Sep 17 00:00:00 2001 From: ste Date: Tue, 2 Jul 2019 22:37:38 +0100 Subject: [PATCH 024/123] GPII-3811: Remove universal require from child process --- .../src/GetHighContrastSchemeName.js | 40 ++++++++++++++----- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/GetHighContrastSchemeName.js b/gpii/node_modules/spiSettingsHandler/src/GetHighContrastSchemeName.js index 3c0eae96f..688b73b74 100644 --- a/gpii/node_modules/spiSettingsHandler/src/GetHighContrastSchemeName.js +++ b/gpii/node_modules/spiSettingsHandler/src/GetHighContrastSchemeName.js @@ -17,15 +17,37 @@ */ "use strict"; -var fluid = require("gpii-universal"), - gpii = fluid.registerNamespace("gpii"); +var ffi = require("ffi-napi"); +var ref = require("ref"); -require("../../WindowsUtilities/WindowsUtilities.js"); +var shlwapi = new ffi.Library("shlwapi", { + // https://docs.microsoft.com/en-us/windows/desktop/api/shlwapi/nf-shlwapi-shloadindirectstring + "SHLoadIndirectString": [ + "uint32", [ "char*", "char*", "uint32", "int" ] + ] +}); -var stringId = process.env.stringId; -var localizedName = gpii.windows.getIndirectString("@%SystemRoot%\\System32\\themeui.dll,-" + stringId); -if (localizedName === undefined) { - localizedName = "undefined"; -} -process.send(localizedName); +// The localised string resources for the built-in theme names. +var stringIds = [ + "2107", // High Contrast #1 + "2108", // High Contrast #2 + "2103", // High Contrast Black + "2104" // High Contrast White +]; + +var buf = Buffer.alloc(0xfff); + +var localisedNames = stringIds.map(function (stringId) { + var src = new Buffer("@%SystemRoot%\\System32\\themeui.dll,-" + stringId + "\u0000", "ucs2"); + var togo; + if (shlwapi.SHLoadIndirectString(src, buf, buf.length, 0) === 0) { + togo = ref.reinterpretUntilZeros(buf, 2, 0).toString("ucs2"); + } else { + togo = undefined; + } + + return togo; +}); + +process.send(localisedNames); From 6ba2d4dbde95001b348b30631c53f0f962c10e93 Mon Sep 17 00:00:00 2001 From: ste Date: Tue, 2 Jul 2019 22:38:28 +0100 Subject: [PATCH 025/123] GPII-3811: Reduced the need to start a child process --- .../src/SpiSettingsHandler.js | 131 +++++++++--------- 1 file changed, 66 insertions(+), 65 deletions(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index ba31ee730..eb770aaf8 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -541,8 +541,7 @@ gpii.windows.spiSettingsHandler.setHighContrastTheme = function (newThemeFile, c themeData = null; } - var promise = fluid.promise(); - var displayName; + var promise; // Get the localised display name of the theme. if (themeData && themeData.Theme) { // 10.0.17134 = Windows 1803 @@ -552,70 +551,13 @@ gpii.windows.spiSettingsHandler.setHighContrastTheme = function (newThemeFile, c // In a custom theme, the HighContrast value is a number from 1-4 which identifies the built-in theme upon // which this theme is based. var highContrastId = themeData.VisualStyles && themeData.VisualStyles.HighContrast; - if (highContrastId) { - // The localised string resources for the built-in theme names. - var stringIds = [ - "2107", // High Contrast #1 - "2108", // High Contrast #2 - "2103", // High Contrast Black - "2104" // High Contrast White - ]; - - var options = { - env: { - stringId: stringIds[highContrastId - 1] - }, - execArgv: [] - }; - - var child = child_process.fork(__dirname + "/GetHighContrastSchemeName.js", options); - var timer = setTimeout(function () { - child.kill(); - timer = null; - }, 10000); - - child.on("message", function (hcName) { - if (hcName !== "undefined") { - displayName = hcName; - } - - if (!displayName) { - // Use the name of this theme. - displayName = gpii.windows.getIndirectString(themeData.Theme.DisplayName); - } - - if (displayName && !dryRun) { - gpii.windows.writeRegistryKey("HKEY_CURRENT_USER", "Control Panel\\Accessibility\\HighContrast", - "High Contrast Scheme", displayName, "REG_SZ"); - // Store the current theme file so it can be referred to later. - gpii.windows.writeRegistryKey( - "HKEY_CURRENT_USER", "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\HighContrast", - "LastSet", newThemeFile, "REG_SZ"); - } - - promise.resolve(displayName); - }); - - child.on("exit", function () { - if (timer) { - clearTimeout(timer); - } - - if (!promise.disposition) { - fluid.log("Error: Getting high-contrast localized name failed."); - promise.reject({ - isError: true, - message: "Timed out waiting for high-contrast localized name." - }); - } - }); - } + promise = gpii.windows.spiSettingsHandler.getLocalisedThemeName(highContrastId); } else { - if (!displayName) { - // Use the name of this theme. - displayName = gpii.windows.getIndirectString(themeData.Theme.DisplayName); - } + promise = fluid.promise().resolve(gpii.windows.getIndirectString(themeData.Theme.DisplayName)); + } + promise.then(function (displayName) { + console.log(displayName); if (displayName && !dryRun) { gpii.windows.writeRegistryKey("HKEY_CURRENT_USER", "Control Panel\\Accessibility\\HighContrast", "High Contrast Scheme", displayName, "REG_SZ"); @@ -624,8 +566,67 @@ gpii.windows.spiSettingsHandler.setHighContrastTheme = function (newThemeFile, c "HKEY_CURRENT_USER", "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\HighContrast", "LastSet", newThemeFile, "REG_SZ"); } - } + }, function (reason) { + console.log(reason); + }); + } + + return promise; +}; + +/** Cache for getLocalisedThemeName */ +gpii.windows.spiSettingsHandler.getLocalisedThemeNameCache = {}; + +/** + * Gets the localised name of the high-contrast themes, for the current language. Windows appears to use the localised + * name of the high-contrast theme. [GPII-3811] + * + * This starts a child process in order to use the system's current language, because it may be different to when this + * process had started with. + * + * @param {Number} highContrastId The HighContrast value of the .theme file, 1-4. + * @return {Promise} Resolves with the localised name of the high-contrast theme. + */ +gpii.windows.spiSettingsHandler.getLocalisedThemeName = function (highContrastId) { + var promise = fluid.promise(); + + var currentLanguage = gpii.windows.language.getDisplayLanguage(); + var themeNames = gpii.windows.spiSettingsHandler.getLocalisedThemeNameCache[currentLanguage]; + + if (themeNames) { + promise.resolve(themeNames[highContrastId - 1]); + } else { + var options = { + env: { + }, + execArgv: [] + }; + + var child = child_process.fork(__dirname + "/GetHighContrastSchemeName.js", options); + var timer = setTimeout(function () { + child.kill(); + timer = null; + }, 10000); + + child.on("message", function (hcNames) { + gpii.windows.spiSettingsHandler.getLocalisedThemeNameCache[currentLanguage] = hcNames; + promise.resolve(hcNames[highContrastId - 1]); + }); + + child.on("exit", function () { + if (timer) { + clearTimeout(timer); + } + + if (!promise.disposition) { + fluid.log("Error: Getting high-contrast localized name failed."); + promise.reject({ + isError: true, + message: "Timed out waiting for high-contrast localized name." + }); + } + }); } return promise; From 16db65c5e255392b1cfc4afb8d2417f1d7610433 Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 5 Jul 2019 22:55:52 +0100 Subject: [PATCH 026/123] GPII-4011: Showing the volume change on the screen. --- .../WindowsUtilities/WindowsUtilities.js | 6 + .../VolumeControl/VolumeControl.cpp | 217 ++++++++++-------- .../src/nativeSettingsHandler.js | 20 +- 3 files changed, 140 insertions(+), 103 deletions(-) diff --git a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js index 20188edaa..9f0baeb68 100644 --- a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js +++ b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js @@ -517,6 +517,8 @@ windows.API_constants = { WM_DISPLAYCHANGE: 0x7e, // https://docs.microsoft.com/windows/desktop/winmsg/wm-themechanged WM_THEMECHANGED: 0x31a, + // https://docs.microsoft.com/windows/win32/inputdev/wm-appcommand + WM_APPCOMMAND: 0x319, // https://msdn.microsoft.com/library/aa363205 @@ -526,6 +528,10 @@ windows.API_constants = { DBT_DEVICEREMOVECOMPLETE: 0x8004, DBT_DEVTYP_VOLUME: 0x2, + // https://docs.microsoft.com/windows/win32/inputdev/wm-appcommand + APPCOMMAND_VOLUME_DOWN: 9, + APPCOMMAND_VOLUME_UP: 10, + // https://msdn.microsoft.com/library/dd375731 virtualKeyCodes: { VK_BACK: 0x08, diff --git a/gpii/node_modules/nativeSettingsHandler/nativeSolutions/VolumeControl/VolumeControl/VolumeControl.cpp b/gpii/node_modules/nativeSettingsHandler/nativeSolutions/VolumeControl/VolumeControl/VolumeControl.cpp index 6c4aa8461..776502349 100644 --- a/gpii/node_modules/nativeSettingsHandler/nativeSolutions/VolumeControl/VolumeControl/VolumeControl.cpp +++ b/gpii/node_modules/nativeSettingsHandler/nativeSolutions/VolumeControl/VolumeControl/VolumeControl.cpp @@ -1,99 +1,118 @@ -// VolumeControl.cpp : This file contains the 'main' function. Program execution begins and ends there. -// - -#include "pch.h" -#include -#include -#include - -#include - -// Operation constants -const wchar_t* Get = L"Get"; -const wchar_t* Set = L"Set"; - -int wmain(int argc, wchar_t *argv[]) { - // Payload - std::wstring operation; - float value; - // COM Interfaces - HRESULT hr = NULL; - const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); - const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); - IMMDeviceEnumerator* pEnumerator; - LPVOID pvReserved = NULL; - - if (argc == 2) { - operation = argv[1]; - if (operation != Get) { - std::wcout << L"{ \"code\": \"160\", \"message\": \"EINVAL\" }"; - return 0; - } - } else if (argc == 3) { - operation = argv[1]; - std::wstring strValue = argv[2]; - - // Parse the received string into a float - const wchar_t* start = strValue.c_str(); - wchar_t* end = NULL; - - value = std::wcstof(start, &end); - - if (operation != Set || (value == 0 && end == start)) { - std::wcout << L"{ \"code\": \"160\", \"message\": \"EINVAL\" }"; - return 0; - } - } else { - std::wcout << L"{ \"code\": \"160\", \"message\": \"EINVAL\" }"; - return 0; - } - - hr = CoInitialize(pvReserved); - if (hr != S_OK) { - std::wcout << L"{ \"code\": \"" << std::to_wstring(hr) << "\", \"message\": \"Failed to initialize COM\" }"; - } - - hr = CoCreateInstance( - CLSID_MMDeviceEnumerator, - NULL, - CLSCTX_ALL, - IID_IMMDeviceEnumerator, - (void**)&pEnumerator - ); - if (hr != S_OK) { - std::wcout << L"{ \"code\": \"" << std::to_wstring(hr) << "\", \"message\": \"Failed to initialize COM instance\" }"; - } - - IMMDevice* pAudioDevice; - hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &pAudioDevice); - if (hr != S_OK) { - std::wcout << L"{ \"code\": \"" << std::to_wstring(hr) << "\", \"message\": \"Failed to get default audio endpoint\" }"; - } - - IAudioEndpointVolume* pEndpointVolume; - pAudioDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL, (void**)&pEndpointVolume); - if (hr != S_OK) { - std::wcout << L"{ \"code\": \"" << std::to_wstring(hr) << "\", \"message\": \"Failed to activate the audio endpoint\" }"; - } - - if (operation == Get) { - float curVolume = 0; - hr = pEndpointVolume->GetMasterVolumeLevelScalar(&curVolume); - if (hr != S_OK) { - std::wcout << L"{ \"code\": \"" << std::to_wstring(hr) << "\", \"message\": \"Failed to get current system volume\" }"; - } else { - std::wcout << L"{ \"Value\": \"" << std::to_wstring(curVolume) << "\" }"; - } - } else { - float curVolume = 0; - hr = pEndpointVolume->SetMasterVolumeLevelScalar(value, NULL); - - if (hr != S_OK) { - std::wcout << L"{ \"code\": \"" << std::to_wstring(hr) << "\", \"message\": \"Failed to set current system volume\" }"; - } else { - std::wcout << L"{ \"Value\": \"" << std::to_wstring(curVolume) << "\" }"; - } - } - - return 0; -} +// VolumeControl.cpp : This file contains the 'main' function. Program execution begins and ends there. +// + +#include "pch.h" +#include +#include +#include + +#include + +// Operation constants +const wchar_t* Get = L"Get"; +const wchar_t* Set = L"Set"; +const wchar_t* SetNear = L"SetNear"; + +int wmain(int argc, wchar_t *argv[]) { + // Payload + std::wstring operation; + float value; + // COM Interfaces + HRESULT hr = NULL; + const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator); + const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator); + IMMDeviceEnumerator* pEnumerator; + LPVOID pvReserved = NULL; + + if (argc == 2) { + operation = argv[1]; + if (operation != Get) { + std::wcout << L"{ \"code\": \"160\", \"message\": \"EINVAL\" }"; + return 0; + } + } else if (argc == 3) { + operation = argv[1]; + std::wstring strValue = argv[2]; + + // Parse the received string into a float + const wchar_t* start = strValue.c_str(); + wchar_t* end = NULL; + + value = std::wcstof(start, &end); + + if ((operation != Set && operation != SetNear) || (value == 0 && end == start)) { + std::wcout << L"{ \"code\": \"160\", \"message\": \"EINVAL\" }"; + return 0; + } + } else { + std::wcout << L"{ \"code\": \"160\", \"message\": \"EINVAL\" }"; + return 0; + } + + hr = CoInitialize(pvReserved); + if (hr != S_OK) { + std::wcout << L"{ \"code\": \"" << std::to_wstring(hr) << "\", \"message\": \"Failed to initialize COM\" }"; + } + + hr = CoCreateInstance( + CLSID_MMDeviceEnumerator, + NULL, + CLSCTX_ALL, + IID_IMMDeviceEnumerator, + (void**)&pEnumerator + ); + if (hr != S_OK) { + std::wcout << L"{ \"code\": \"" << std::to_wstring(hr) << "\", \"message\": \"Failed to initialize COM instance\" }"; + } + + IMMDevice* pAudioDevice; + hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &pAudioDevice); + if (hr != S_OK) { + std::wcout << L"{ \"code\": \"" << std::to_wstring(hr) << "\", \"message\": \"Failed to get default audio endpoint\" }"; + } + + IAudioEndpointVolume* pEndpointVolume; + pAudioDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_ALL, NULL, (void**)&pEndpointVolume); + if (hr != S_OK) { + std::wcout << L"{ \"code\": \"" << std::to_wstring(hr) << "\", \"message\": \"Failed to activate the audio endpoint\" }"; + } + + if (operation == Get) { + float curVolume = 0; + hr = pEndpointVolume->GetMasterVolumeLevelScalar(&curVolume); + if (hr != S_OK) { + std::wcout << L"{ \"code\": \"" << std::to_wstring(hr) << "\", \"message\": \"Failed to get current system volume\" }"; + } else { + std::wcout << L"{ \"Value\": \"" << std::to_wstring(curVolume) << "\" }"; + } + } else { + float curVolume = 0; + hr = pEndpointVolume->GetMasterVolumeLevelScalar(&curVolume); + + if (operation == SetNear && hr == S_OK) { + // Set the volume to near the target volume, allowing a small amount for the UI to finish the job. + if (abs(value - curVolume) > 0.02) { + value += (value > curVolume) ? -0.02 : 0.02; + } else { + value = curVolume; + } + } + + if (operation == Set || value != curVolume) { + hr = pEndpointVolume->SetMasterVolumeLevelScalar(value, NULL); + } + + if (hr != S_OK) { + std::wcout << L"{ \"code\": \"" << std::to_wstring(hr) << "\", \"message\": \"Failed to set current system volume\" }"; + } else { + float newVolume = 0; + if (pEndpointVolume->GetMasterVolumeLevelScalar(&newVolume) != S_OK) { + newVolume = value; + } + std::wcout << L"{ \"Value\": \"" << std::to_wstring(newVolume) << "\", \"Old\": \"" + << std::to_wstring(curVolume) << "\" }"; + } + } + + return 0; +} diff --git a/gpii/node_modules/nativeSettingsHandler/src/nativeSettingsHandler.js b/gpii/node_modules/nativeSettingsHandler/src/nativeSettingsHandler.js index 78624d29d..919d4213e 100644 --- a/gpii/node_modules/nativeSettingsHandler/src/nativeSettingsHandler.js +++ b/gpii/node_modules/nativeSettingsHandler/src/nativeSettingsHandler.js @@ -164,7 +164,8 @@ windows.nativeSettingsHandler.SetDoubleClickHeight = function (num) { /** * Function that handles calling the native executable for volume control. * - * @param {String} mode The operation mode, could be either "Set" or "Get". + * @param {String} mode The operation mode, could be either "Set", "SetNear" or "Get". Calling with "SetNear" will + * adjust the volume so it's near the target volume, allowing room for the shell to do the rest. * @param {Number} [num] The value to set as the current system volume. Range is normalized from 0 to 1. * @return {Promise} A promise that resolves on success or holds a object with the error information. */ @@ -176,9 +177,9 @@ windows.nativeSettingsHandler.VolumeHandler = function (mode, num) { var valueBuff; if (mode === "Get") { - valueBuff = child_process.execFileSync(fileName, ["Get"], {}); + valueBuff = child_process.execFileSync(fileName, [mode], {}); } else { - valueBuff = child_process.execFileSync(fileName, ["Set", num], {}); + valueBuff = child_process.execFileSync(fileName, [mode, num], {}); } var strRes = valueBuff.toString("utf8"); @@ -215,7 +216,18 @@ windows.nativeSettingsHandler.GetVolume = function () { * @return {Promise} A promise that resolves on success or holds a object with the error information. */ windows.nativeSettingsHandler.SetVolume = function (num) { - return windows.nativeSettingsHandler.VolumeHandler("Set", num); + return windows.nativeSettingsHandler.VolumeHandler("SetNear", num).then(function (result) { + var current = parseFloat(result); + if (current !== num) { + // Complete the last bit of the change, as though the media keys where used, in order to show the new volume + // on the screen. + var action = current < num + ? windows.API_constants.APPCOMMAND_VOLUME_UP : windows.API_constants.APPCOMMAND_VOLUME_DOWN; + // Send the volume up/down command directly to the task tray (rather than a simulated key press) + gpii.windows.messages.sendMessage("Shell_TrayWnd", windows.API_constants.WM_APPCOMMAND, 0, + windows.makeLong(0, action)); + } + }); }; /** From 57324ace4b8b07fad11ba0631630a9c9fa029f89 Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 5 Jul 2019 23:42:08 +0100 Subject: [PATCH 027/123] GPII-4011: Putting line separators back to CRLF. --- .../VolumeControl/VolumeControl/VolumeControl.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/gpii/node_modules/nativeSettingsHandler/nativeSolutions/VolumeControl/VolumeControl/VolumeControl.cpp b/gpii/node_modules/nativeSettingsHandler/nativeSolutions/VolumeControl/VolumeControl/VolumeControl.cpp index 776502349..e44a472a6 100644 --- a/gpii/node_modules/nativeSettingsHandler/nativeSolutions/VolumeControl/VolumeControl/VolumeControl.cpp +++ b/gpii/node_modules/nativeSettingsHandler/nativeSolutions/VolumeControl/VolumeControl/VolumeControl.cpp @@ -87,6 +87,7 @@ int wmain(int argc, wchar_t *argv[]) { } } else { float curVolume = 0; + hr = pEndpointVolume->GetMasterVolumeLevelScalar(&curVolume); if (operation == SetNear && hr == S_OK) { From 5a5e9575c9d719dbf346aa19ac5fac2d841fa14f Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 5 Jul 2019 15:46:39 -0700 Subject: [PATCH 028/123] GPII-4011: Putting line separators back to CRLF. (trying harder) --- .../VolumeControl/VolumeControl/VolumeControl.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/gpii/node_modules/nativeSettingsHandler/nativeSolutions/VolumeControl/VolumeControl/VolumeControl.cpp b/gpii/node_modules/nativeSettingsHandler/nativeSolutions/VolumeControl/VolumeControl/VolumeControl.cpp index e44a472a6..776502349 100644 --- a/gpii/node_modules/nativeSettingsHandler/nativeSolutions/VolumeControl/VolumeControl/VolumeControl.cpp +++ b/gpii/node_modules/nativeSettingsHandler/nativeSolutions/VolumeControl/VolumeControl/VolumeControl.cpp @@ -87,7 +87,6 @@ int wmain(int argc, wchar_t *argv[]) { } } else { float curVolume = 0; - hr = pEndpointVolume->GetMasterVolumeLevelScalar(&curVolume); if (operation == SetNear && hr == S_OK) { From 223506127a70dfe967a947628e248e6983828bf7 Mon Sep 17 00:00:00 2001 From: ste Date: Mon, 8 Jul 2019 18:12:21 +0100 Subject: [PATCH 029/123] GPII-3853: Potential fix for CI-only test failure. --- .../windowsMetrics/test/WindowsMetricsTests.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js index 5e3be32fd..1dbec1a1e 100644 --- a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js +++ b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js @@ -1622,18 +1622,18 @@ jqUnit.asyncTest("Testing power notifications", function () { eventData: { sessionID: "test-session-id" } + }, + invokers: { + logVersions: { + funcName: "fluid.identity" + }, + logSystemInfo: { + funcName: "fluid.identity" + } } }); - var expectedLines = [ - // Ignore the initial events - { - module: "metrics", event: "version" - }, { - module: "metrics", event: "system-info" - } - ]; - + var expectedLines = []; fluid.each(gpii.tests.metrics.powerMessageTests, function (test) { if (test.input === "userInput") { From b674229e6297123508b42cf57008b124d5285938 Mon Sep 17 00:00:00 2001 From: ste Date: Mon, 8 Jul 2019 19:38:24 +0100 Subject: [PATCH 030/123] GPII-3853: Better fix for CI-only test failure. --- gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js index 1dbec1a1e..c6e2c7671 100644 --- a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js +++ b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js @@ -1610,6 +1610,8 @@ jqUnit.asyncTest("Testing version and system info logging", function () { } } ]); + + windowsMetrics.destroy(); }); jqUnit.asyncTest("Testing power notifications", function () { @@ -1648,7 +1650,7 @@ jqUnit.asyncTest("Testing power notifications", function () { expectedLines.push.apply(expectedLines, fluid.makeArray(test.expect)); }); - gpii.tests.metrics.completeLogTest(logFile, expectedLines, true); + gpii.tests.metrics.completeLogTest(logFile, expectedLines); }); /** From 15073f46d429e0c49825178354e1062044a574a8 Mon Sep 17 00:00:00 2001 From: ste Date: Tue, 9 Jul 2019 21:12:55 +0100 Subject: [PATCH 031/123] GPII-4016: App-zoom for explorer folder windows, notepad++, and wordpad. --- .../WindowsUtilities/WindowsUtilities.js | 2 + .../node_modules/gpii-app-zoom/src/appZoom.js | 96 +++++++++++++++---- 2 files changed, 79 insertions(+), 19 deletions(-) diff --git a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js index 20188edaa..5b9abce37 100644 --- a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js +++ b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js @@ -554,6 +554,8 @@ windows.API_constants = { VK_LWIN: 0x5B, VK_RWIN: 0x5C, VK_APPS: 0x5D, + VK_ADD: 0x6B, + VK_SUBTRACT: 0x6D, VK_F1: 0x70, VK_F2: 0x71, VK_F3: 0x72, diff --git a/gpii/node_modules/gpii-app-zoom/src/appZoom.js b/gpii/node_modules/gpii-app-zoom/src/appZoom.js index d5ed11072..1eccb9ff2 100644 --- a/gpii/node_modules/gpii-app-zoom/src/appZoom.js +++ b/gpii/node_modules/gpii-app-zoom/src/appZoom.js @@ -60,24 +60,25 @@ fluid.defaults("gpii.windows.appZoom", { currentWindow: null }, configurations: { - ignored: { - // "ignored" means they don't become the current window. Windows owned by this process are included. + taskTray: { ignore: true, - match: ["explorer.exe"] + matchProcess: ["explorer.exe"], + matchWindow: ["Shell_TrayWnd"] }, generic: { // All unmatched windows - the wheel message, with simulated ctrl press. + matchProcess: "*", wheel: {}, ctrl: true }, wheel: { // Well behaved applications, that just require the wheel message (which includes the ctrl key). - match: ["chrome.exe"], + matchProcess: ["chrome.exe"], wheel: {} }, standardKey: { // Presses ctrl + "-" or "=" - match: ["acrord32.exe", "firefox.exe" ], + matchProcess: ["acrord32.exe", "firefox.exe" ], ctrl: true, key: { decrease: "-", @@ -85,7 +86,7 @@ fluid.defaults("gpii.windows.appZoom", { } }, word: { - match: ["winword.exe"], + matchProcess: ["winword.exe"], wheel: { delay: 500 }, @@ -93,7 +94,7 @@ fluid.defaults("gpii.windows.appZoom", { childWindow: "_WwG" }, edge: { - match: ["microsoftedgecp.exe"], + matchProcess: ["microsoftedgecp.exe"], // The active window reported by the shell message is wrong. getForegroundWindow: true, childWindow: "Windows.UI.Core.CoreWindow", @@ -104,15 +105,38 @@ fluid.defaults("gpii.windows.appZoom", { } }, uwp: { - match: ["applicationframehost.exe"], + matchProcess: ["applicationframehost.exe"], getForegroundWindow: true, childWindow: "Windows.UI.Core.CoreWindow", ctrl: true, wheel: { simulate: true } + }, + "notepad++": { + matchProcess: ["notepad++.exe"], + ctrl: true, + childWindow: "Scintilla", + key: { + decrease: gpii.windows.API_constants.virtualKeyCodes.VK_SUBTRACT, + increase: gpii.windows.API_constants.virtualKeyCodes.VK_ADD + } + }, + explorer: { + matchProcess: ["explorer.exe"], + matchWindow: ["CabinetWClass"], + childWindow: "SHELLDLL_DefView", + wheel: {} + }, + wordPad: { + // Send left/right keys to the track bar at the bottom. + matchProcess: ["wordpad.exe"], + childWindow: "msctls_trackbar32", + key: { + decrease: gpii.windows.API_constants.virtualKeyCodes.VK_LEFT, + increase: gpii.windows.API_constants.virtualKeyCodes.VK_RIGHT + } } - } }); @@ -130,7 +154,8 @@ fluid.defaults("gpii.windows.appZoom", { * * @typedef {Object} ZoomConfig * @property {String} name Config name. - * @property {String[]} match The executable files (without the directory) to match against. + * @property {String[]} matchProcess The executable files (without the directory) to match against. + * @property {String[]} matchWindow The class-name of the window to match against. * @property {String} [optional] The class-name of a child window to sent the notifications to. * @property {Boolean} getForegroundWindow true to call GetForegroundWindow instead of trusting the handle passed by * WM_SHELLHOOKMESSAGE. @@ -139,6 +164,7 @@ fluid.defaults("gpii.windows.appZoom", { * * @property {Object} wheel Use ctrl+mouse wheel. * @property {Boolean} wheel.delay Wait a number of milliseconds before and after. + * @property {Boolean} wheel.amount [optional] Number of notches to rotate [default: 1]. * @property {Boolean} wheel.simulate Simulate the mouse action by moving the cursor and injecting a wheel movement, * rather than just sending WM_MOUSEWHEEL. * @@ -161,9 +187,7 @@ windows.appZoom.sendZoom = function (that, direction) { fluid.log("sendZoom(" + direction + "): ", window); - var minimised = windows.user32.IsIconic(window.hwnd); - - if (minimised) { + if (!window || windows.user32.IsIconic(window.hwnd)) { promise.resolve(); } else if (config) { if (config.ctrl) { @@ -176,6 +200,9 @@ windows.appZoom.sendZoom = function (that, direction) { if (increment < 0 && direction !== "decrease") { fluid.fail("sendZoom: direction should be either 'decrease' or 'increase'"); } + if (config.wheel && config.wheel.amount) { + increment *= config.wheel.amount; + } var hwnd = 0; if (config.childWindow) { @@ -187,7 +214,9 @@ windows.appZoom.sendZoom = function (that, direction) { hwnd = window.hwnd; } - if (config.wheel) { + if (config.listView) { + windows.appZoom.zoomListView(hwnd, increment, window); + } else if (config.wheel) { setTimeout(function () { windows.appZoom.sendWheel(hwnd, increment, window); setTimeout(function () { @@ -247,13 +276,32 @@ windows.appZoom.setControlKeyState = function (down) { } }; +/** + * Zooms a listview control. + * See https://docs.microsoft.com/windows/win32/controls/list-view-control-reference + * + * @param {Number} hwnd The window handle to send events to (the ListView). + * @param {Number} amount The amount and direction to spin the wheel: > 0 for up, < 0 for down. + */ +windows.appZoom.zoomListView = function (hwnd, amount) { + var LVM_GETVIEW = 0x108f; + var LVM_SETVIEW = 0x108e; + var LV_VIEW_MAX = 0x4; + + windows.messages.sendMessageAsync(hwnd, LVM_GETVIEW, 0, 0).then(function (value) { + var newValue = Math.max(0, Math.min(LV_VIEW_MAX, value + amount)); + windows.messages.sendMessageAsync(hwnd, LVM_SETVIEW, newValue, 0); + }); + +}; + /** * Send the mouse-wheel event to a given window. * @param {Number} hwnd The window handle to send events to. - * @param {Number} direction The direction to spin the wheel: > 0 for up, < 0 for down. + * @param {Number} amount The amount and direction to spin the wheel: > 0 for up, < 0 for down. * @param {WindowInfo} window The window of the application that is being zoomed. */ -windows.appZoom.sendWheel = function (hwnd, direction, window) { +windows.appZoom.sendWheel = function (hwnd, amount, window) { var MK_CONTROL = 0x8; var WHEEL_DELTA = 120; var MOUSEEVENTF_WHEEL = 0x800; @@ -261,7 +309,7 @@ windows.appZoom.sendWheel = function (hwnd, direction, window) { var config = window.config; // Say that the mouse cursor is in the middle of the window. - var distance = WHEEL_DELTA * Math.sign(direction); + var distance = WHEEL_DELTA * amount; // Some (or most?) applications will not zoom if their window isn't top-most at that point. var targetPoint = windows.appZoom.findUncoveredPoint(hwnd, window); @@ -406,7 +454,18 @@ windows.appZoom.getWindowInfo = function (hwnd) { */ windows.appZoom.getConfig = function (that, window) { var config = fluid.find(that.options.configurations, function (config) { - return fluid.makeArray(config.match).indexOf(window.exe) >= 0 ? config : undefined; + var match = config.matchProcess ? fluid.makeArray(config.matchProcess).indexOf(window.exe) >= 0 : true; + + if (match && config.matchWindow) { + var classBuffer = Buffer.alloc(0xff); + var len = windows.user32.GetClassNameW(window.hwnd, classBuffer, classBuffer.length); + if (len > 0) { + var className = windows.stringFromWideChar(classBuffer); + match = fluid.makeArray(config.matchWindow).indexOf(className) >= 0; + } + } + + return match ? config : undefined; }); return config || that.options.configurations.generic; @@ -421,7 +480,6 @@ windows.appZoom.windowActivated = function (that, hwnd) { if (that.activeWindow !== hwnd) { that.activeWindow = hwnd; var window = windows.appZoom.getWindowInfo(hwnd); - // Get the configuration for the window. if (window && (window.pid !== process.pid)) { window.config = that.getConfig(window); From 3ade7732b105ed0a69095136cfc10c2bf79d8d5e Mon Sep 17 00:00:00 2001 From: ste Date: Mon, 15 Jul 2019 11:11:41 +0100 Subject: [PATCH 032/123] GPII-4016: Added zoom support for IE, improved Edge --- gpii/node_modules/gpii-app-zoom/src/appZoom.js | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/gpii/node_modules/gpii-app-zoom/src/appZoom.js b/gpii/node_modules/gpii-app-zoom/src/appZoom.js index 1eccb9ff2..5fc1f65c2 100644 --- a/gpii/node_modules/gpii-app-zoom/src/appZoom.js +++ b/gpii/node_modules/gpii-app-zoom/src/appZoom.js @@ -99,9 +99,9 @@ fluid.defaults("gpii.windows.appZoom", { getForegroundWindow: true, childWindow: "Windows.UI.Core.CoreWindow", ctrl: true, - wheel: { - delay: 500, - simulate: true + key: { + decrease: "-", + increase: "=" } }, uwp: { @@ -128,6 +128,15 @@ fluid.defaults("gpii.windows.appZoom", { childWindow: "SHELLDLL_DefView", wheel: {} }, + internetExplorer: { + matchProcess: ["iexplore.exe"], + childWindow: "Internet Explorer_Server", + ctrl: true, + key: { + decrease: "-", + increase: "=" + } + }, wordPad: { // Send left/right keys to the track bar at the bottom. matchProcess: ["wordpad.exe"], @@ -185,6 +194,8 @@ windows.appZoom.sendZoom = function (that, direction) { var config = window && window.config; var promise = fluid.promise(); + console.log(window); + fluid.log("sendZoom(" + direction + "): ", window); if (!window || windows.user32.IsIconic(window.hwnd)) { From 7b3669c75e3978b888dbbe599382e6838f9eb0e7 Mon Sep 17 00:00:00 2001 From: ste Date: Mon, 15 Jul 2019 14:16:44 +0100 Subject: [PATCH 033/123] GPII-4016: Removed amateur debugging code --- gpii/node_modules/gpii-app-zoom/src/appZoom.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/gpii/node_modules/gpii-app-zoom/src/appZoom.js b/gpii/node_modules/gpii-app-zoom/src/appZoom.js index 5fc1f65c2..e82f1a42f 100644 --- a/gpii/node_modules/gpii-app-zoom/src/appZoom.js +++ b/gpii/node_modules/gpii-app-zoom/src/appZoom.js @@ -194,8 +194,6 @@ windows.appZoom.sendZoom = function (that, direction) { var config = window && window.config; var promise = fluid.promise(); - console.log(window); - fluid.log("sendZoom(" + direction + "): ", window); if (!window || windows.user32.IsIconic(window.hwnd)) { From 83d3ff954f26f06502ed26010ad291202551625e Mon Sep 17 00:00:00 2001 From: ste Date: Tue, 16 Jul 2019 20:24:50 +0100 Subject: [PATCH 034/123] GPII-3811: Getting the localised theme names when the language changes. --- .../spiSettingsHandler/src/SpiSettingsHandler.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index eb770aaf8..be2b969b9 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -577,6 +577,22 @@ gpii.windows.spiSettingsHandler.setHighContrastTheme = function (newThemeFile, c /** Cache for getLocalisedThemeName */ gpii.windows.spiSettingsHandler.getLocalisedThemeNameCache = {}; +// Attaches to the language component to update the localised theme names when the language changes. +fluid.defaults("gpii.windows.spiSettingsHandler.highContrastThemes", { + gradeNames: ["fluid.modelComponent"], + modelListeners: { + "{gpii.windows.language}.model.installedLanguages": { + funcName: "gpii.windows.spiSettingsHandler.getLocalisedThemeName", + args: [1] + } + } +}); + +fluid.makeGradeLinkage("gpii.windows.spiSettingsHandler.highContrastThemesLink", + ["gpii.windows.language"], + "gpii.windows.spiSettingsHandler.highContrastThemes" +); + /** * Gets the localised name of the high-contrast themes, for the current language. Windows appears to use the localised * name of the high-contrast theme. [GPII-3811] From 073a280f7097825f45cfec5f48a6f68dfd9031d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Hern=C3=A1ndez?= Date: Thu, 18 Jul 2019 20:54:06 +0200 Subject: [PATCH 035/123] GPII-4033: Removed language constraints when dealing with wmi queries --- .../wmiSettingsHandler/src/WmiSettingsHandler.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/gpii/node_modules/wmiSettingsHandler/src/WmiSettingsHandler.js b/gpii/node_modules/wmiSettingsHandler/src/WmiSettingsHandler.js index d9bb26696..10ad79d9e 100644 --- a/gpii/node_modules/wmiSettingsHandler/src/WmiSettingsHandler.js +++ b/gpii/node_modules/wmiSettingsHandler/src/WmiSettingsHandler.js @@ -129,9 +129,7 @@ windows.wmi.updateWMISetting = function (query) { try { result = updateQuery(query, true); } catch (error) { - if (error.message.indexOf("Not supported") === -1 ) { - fluid.fail("windows.wmi.updateWMISetting: Failed with WMI error msg - '" + error + "'"); - } + fluid.fail("windows.wmi.updateWMISetting: Failed with WMI error msg - '" + error + "'"); } return result; @@ -330,9 +328,7 @@ gpii.windows.wmi.getQuery = function (query) { try { result = getQuery(query, true); } catch (error) { - if (error.message.indexOf("Not supported") === -1 ) { - fluid.fail("gpii.windows.wmi.getQuery: Query failed with WMI error msg - '" + error + "'"); - } + fluid.log("gpii.windows.wmi.getQuery: Query failed with WMI error msg - '" + error + "'"); } return result; From b72e018929ca41c8f405154c606ecf07df245946 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Hern=C3=A1ndez?= Date: Thu, 1 Aug 2019 15:10:40 +0200 Subject: [PATCH 036/123] GPII-4033: Re-added NotSupported check but with a non-localised string Updated tests and also added a comment following review --- .../wmiSettingsHandler/src/WmiSettingsHandler.js | 10 ++++++++-- .../wmiSettingsHandler/test/testWmiSettingsHandler.js | 5 +---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/gpii/node_modules/wmiSettingsHandler/src/WmiSettingsHandler.js b/gpii/node_modules/wmiSettingsHandler/src/WmiSettingsHandler.js index 10ad79d9e..e55592ec3 100644 --- a/gpii/node_modules/wmiSettingsHandler/src/WmiSettingsHandler.js +++ b/gpii/node_modules/wmiSettingsHandler/src/WmiSettingsHandler.js @@ -129,7 +129,9 @@ windows.wmi.updateWMISetting = function (query) { try { result = updateQuery(query, true); } catch (error) { - fluid.fail("windows.wmi.updateWMISetting: Failed with WMI error msg - '" + error + "'"); + if (error.ErrorCode !== "NotSupported") { + fluid.fail("windows.wmi.updateWMISetting: Failed with WMI error msg - '" + error + "'"); + } } return result; @@ -328,7 +330,11 @@ gpii.windows.wmi.getQuery = function (query) { try { result = getQuery(query, true); } catch (error) { - fluid.log("gpii.windows.wmi.getQuery: Query failed with WMI error msg - '" + error + "'"); + if (error.ErrorCode !== "NotSupported") { + // We do not raise an error here because this code is being called by the deviceReporter, + // which at this moment doesn't handle failures in the same way the settingsHandler code does. + fluid.log("gpii.windows.wmi.getQuery: Query failed with WMI error msg - '" + error + "'"); + } } return result; diff --git a/gpii/node_modules/wmiSettingsHandler/test/testWmiSettingsHandler.js b/gpii/node_modules/wmiSettingsHandler/test/testWmiSettingsHandler.js index c8dbab10e..8433fa116 100644 --- a/gpii/node_modules/wmiSettingsHandler/test/testWmiSettingsHandler.js +++ b/gpii/node_modules/wmiSettingsHandler/test/testWmiSettingsHandler.js @@ -384,10 +384,7 @@ jqUnit.test("Testing wmiSettingsHandler set errors on missing placeholder.", fun jqUnit.test("Testing wmiSettingsHandler bad getQuery.", function () { var query = "Not a valid query"; - - jqUnit.expectFrameworkDiagnostic("Should fail due to bad getQuery", function () { - windows.wmi.getQuery (query); - }, "Query failed with WMI error msg"); + jqUnit.assertNull("Should return null due to bad getQuery", windows.wmi.getQuery(query)); }); jqUnit.test("Testing wmiSettingsHandler updates successfully.", function () { From e47c686ff6274ed04b03e0804e68e09639687722 Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 16 Aug 2019 15:03:42 +0100 Subject: [PATCH 037/123] GPII-4087: Added hack to stop wallpaper being reset. --- .../spiSettingsHandler/src/SpiSettingsHandler.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index 4739e6409..f9d2ab066 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -406,6 +406,18 @@ gpii.windows.spi.getPvParam = function (payload) { gpii.windows.spi.setImpl = function (payload) { gpii.windows.defineStruct(payload); + // Hack for GPII-4087, the high-contrast is being set to "off" even when it's already "off". This causes Windows + // to reload the default theme (or last saved?) - causing any custom wallpaper to be lost. + // This hack just gets the current high-contrast state and does nothing if it's already turned off. + if (payload.settings.HighContrastOn && !payload.settings.HighContrastOn.value) { + var currentState = gpii.windows.spi.getImpl(fluid.copy(payload)); + if (!currentState.HighContrastOn.value) { + return fluid.promise.resolve({ + HighContrastOn: { oldValue: false, newValue: false } + }); + } + } + var results = {}; gpii.windows.spi.populateResults(payload, false, false, results); From 4ba3ed7d4f33a76f418edd1c4709800f88ed5216 Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 16 Aug 2019 15:44:11 +0100 Subject: [PATCH 038/123] GPII-4087: Fixed the high-contrast fix. --- gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index f9d2ab066..5129f2da7 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -412,7 +412,7 @@ gpii.windows.spi.setImpl = function (payload) { if (payload.settings.HighContrastOn && !payload.settings.HighContrastOn.value) { var currentState = gpii.windows.spi.getImpl(fluid.copy(payload)); if (!currentState.HighContrastOn.value) { - return fluid.promise.resolve({ + return fluid.promise().resolve({ HighContrastOn: { oldValue: false, newValue: false } }); } From 91814712e7158a02ce7ee1b66e7872e281ebba81 Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 16 Aug 2019 15:49:59 +0100 Subject: [PATCH 039/123] GPII-4087: Tidied the high-contrast fix. --- .../src/SpiSettingsHandler.js | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index 5129f2da7..ff35d2948 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -406,39 +406,41 @@ gpii.windows.spi.getPvParam = function (payload) { gpii.windows.spi.setImpl = function (payload) { gpii.windows.defineStruct(payload); + var togo = fluid.promise(); + // Hack for GPII-4087, the high-contrast is being set to "off" even when it's already "off". This causes Windows // to reload the default theme (or last saved?) - causing any custom wallpaper to be lost. // This hack just gets the current high-contrast state and does nothing if it's already turned off. if (payload.settings.HighContrastOn && !payload.settings.HighContrastOn.value) { var currentState = gpii.windows.spi.getImpl(fluid.copy(payload)); if (!currentState.HighContrastOn.value) { - return fluid.promise().resolve({ + togo.resolve({ HighContrastOn: { oldValue: false, newValue: false } }); } } - var results = {}; - gpii.windows.spi.populateResults(payload, false, false, results); - - var togo = fluid.promise(); + if (!togo.disposition) { + var results = {}; + gpii.windows.spi.populateResults(payload, false, false, results); - gpii.windows.spi.applySettings(payload) - .then(function () { - gpii.windows.spi.populateResults(payload, true, false, results); + gpii.windows.spi.applySettings(payload) + .then(function () { + gpii.windows.spi.populateResults(payload, true, false, results); - gpii.windows.spi.GPII1873_HighContrastBug("SET", payload, results); + gpii.windows.spi.GPII1873_HighContrastBug("SET", payload, results); - // transform results here oldValue: x --> oldValue: { value: x, path: ... } - fluid.each(results, function (value, setting) { - results[setting].oldValue = {"value": value.oldValue, "path": payload.settings[setting].path}; - results[setting].newValue = {"value": value.newValue, "path": payload.settings[setting].path}; - }); + // transform results here oldValue: x --> oldValue: { value: x, path: ... } + fluid.each(results, function (value, setting) { + results[setting].oldValue = {"value": value.oldValue, "path": payload.settings[setting].path}; + results[setting].newValue = {"value": value.newValue, "path": payload.settings[setting].path}; + }); - fluid.log("SPI settings handler SET returning results ", results); + fluid.log("SPI settings handler SET returning results ", results); - togo.resolve(results); - }, togo.reject); + togo.resolve(results); + }, togo.reject); + } return togo; }; From 5928661bdcd82f0809f9cbff409207f292b52e67 Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 16 Aug 2019 16:45:05 +0100 Subject: [PATCH 040/123] GPII-4087: High-contrast fix returning the correct value. --- gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index ff35d2948..b2acddd72 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -415,7 +415,7 @@ gpii.windows.spi.setImpl = function (payload) { var currentState = gpii.windows.spi.getImpl(fluid.copy(payload)); if (!currentState.HighContrastOn.value) { togo.resolve({ - HighContrastOn: { oldValue: false, newValue: false } + HighContrastOn: { oldValue: payload.settings.HighContrastOn, newValue: payload.settings.HighContrastOn } }); } } From 12449fd47b1efeb3fe25f6e2a5a7272df64dcae3 Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 16 Aug 2019 23:01:07 +0100 Subject: [PATCH 041/123] GPII-4087: Tidied the high-contrast fix. --- .../src/SpiSettingsHandler.js | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index b2acddd72..8315c50af 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -407,40 +407,40 @@ gpii.windows.spi.setImpl = function (payload) { gpii.windows.defineStruct(payload); var togo = fluid.promise(); + var results = {}; // Hack for GPII-4087, the high-contrast is being set to "off" even when it's already "off". This causes Windows // to reload the default theme (or last saved?) - causing any custom wallpaper to be lost. // This hack just gets the current high-contrast state and does nothing if it's already turned off. + var applyPromise; if (payload.settings.HighContrastOn && !payload.settings.HighContrastOn.value) { var currentState = gpii.windows.spi.getImpl(fluid.copy(payload)); if (!currentState.HighContrastOn.value) { - togo.resolve({ - HighContrastOn: { oldValue: payload.settings.HighContrastOn, newValue: payload.settings.HighContrastOn } - }); + applyPromise = fluid.promise().resolve(); } } - if (!togo.disposition) { - var results = {}; - gpii.windows.spi.populateResults(payload, false, false, results); + gpii.windows.spi.populateResults(payload, false, false, results); + if (!applyPromise) { gpii.windows.spi.applySettings(payload) - .then(function () { - gpii.windows.spi.populateResults(payload, true, false, results); + } - gpii.windows.spi.GPII1873_HighContrastBug("SET", payload, results); + applyPromise.then(function () { + gpii.windows.spi.populateResults(payload, true, false, results); - // transform results here oldValue: x --> oldValue: { value: x, path: ... } - fluid.each(results, function (value, setting) { - results[setting].oldValue = {"value": value.oldValue, "path": payload.settings[setting].path}; - results[setting].newValue = {"value": value.newValue, "path": payload.settings[setting].path}; - }); + gpii.windows.spi.GPII1873_HighContrastBug("SET", payload, results); - fluid.log("SPI settings handler SET returning results ", results); + // transform results here oldValue: x --> oldValue: { value: x, path: ... } + fluid.each(results, function (value, setting) { + results[setting].oldValue = {"value": value.oldValue, "path": payload.settings[setting].path}; + results[setting].newValue = {"value": value.newValue, "path": payload.settings[setting].path}; + }); - togo.resolve(results); - }, togo.reject); - } + fluid.log("SPI settings handler SET returning results ", results); + + togo.resolve(results); + }, togo.reject); return togo; }; From b9e5226c59bdbf384ce90eeef86dc0739b152d2f Mon Sep 17 00:00:00 2001 From: ste Date: Sat, 17 Aug 2019 22:54:14 +0100 Subject: [PATCH 042/123] GPII-4087: Applying high-contrast if the theme is specified. --- .../src/SpiSettingsHandler.js | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index 8315c50af..45592501b 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -413,7 +413,8 @@ gpii.windows.spi.setImpl = function (payload) { // to reload the default theme (or last saved?) - causing any custom wallpaper to be lost. // This hack just gets the current high-contrast state and does nothing if it's already turned off. var applyPromise; - if (payload.settings.HighContrastOn && !payload.settings.HighContrastOn.value) { + if (payload.settings.HighContrastOn && !payload.settings.HighContrastOn.value && + !payload.settings.HighContrastTheme) { var currentState = gpii.windows.spi.getImpl(fluid.copy(payload)); if (!currentState.HighContrastOn.value) { applyPromise = fluid.promise().resolve(); @@ -423,24 +424,24 @@ gpii.windows.spi.setImpl = function (payload) { gpii.windows.spi.populateResults(payload, false, false, results); if (!applyPromise) { - gpii.windows.spi.applySettings(payload) + applyPromise = gpii.windows.spi.applySettings(payload); } applyPromise.then(function () { - gpii.windows.spi.populateResults(payload, true, false, results); + gpii.windows.spi.populateResults(payload, true, false, results); - gpii.windows.spi.GPII1873_HighContrastBug("SET", payload, results); + gpii.windows.spi.GPII1873_HighContrastBug("SET", payload, results); - // transform results here oldValue: x --> oldValue: { value: x, path: ... } - fluid.each(results, function (value, setting) { - results[setting].oldValue = {"value": value.oldValue, "path": payload.settings[setting].path}; - results[setting].newValue = {"value": value.newValue, "path": payload.settings[setting].path}; - }); + // transform results here oldValue: x --> oldValue: { value: x, path: ... } + fluid.each(results, function (value, setting) { + results[setting].oldValue = {"value": value.oldValue, "path": payload.settings[setting].path}; + results[setting].newValue = {"value": value.newValue, "path": payload.settings[setting].path}; + }); - fluid.log("SPI settings handler SET returning results ", results); + fluid.log("SPI settings handler SET returning results ", results); - togo.resolve(results); - }, togo.reject); + togo.resolve(results); + }, togo.reject); return togo; }; From a3aa0c4fef76bf3c115a14ba9a9dc1f65a737984 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Mon, 19 Aug 2019 22:17:58 +0200 Subject: [PATCH 043/123] GPII-4087: Workaround that prevents deleting system cached wallpaper unless high-contrast is being set --- .../WindowsUtilities/WindowsUtilities.js | 58 +++++++++++-------- .../src/SpiSettingsHandler.js | 32 ++++++++-- 2 files changed, 62 insertions(+), 28 deletions(-) diff --git a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js index 8d4576a82..b7614dd3d 100644 --- a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js +++ b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js @@ -1927,46 +1927,56 @@ gpii.windows.getUserUsbDrives = function (usbDrives) { * @param {Object} [options] Options * @param {Boolean} options.recursive Flag indicating if the element is a directory and should be removed recursively. * @param {Boolean} options.silent Flag for a silent fail in case the element doesn't exist. + * @param {Boolean} options.local Flag that if not true disables normal behavior and does nothing. TODO: This is a hack + * related with GPII-4087. * @return {Promise} A promise that resolves on success or rejects on failure holding a object with the error * information. */ gpii.windows.rm = function (path, options) { var defaultOptions = { recursive: false, - silent: false + silent: false, + local: false }; + options = fluid.extend(defaultOptions, options); var result = fluid.promise(); - if (fs.existsSync(path)) { - if (options.recursive) { - try { - rimraf.sync(path); - result.resolve(); - } catch (err) { - result.reject({ - code: err.code, - message: "Removing dir failed with error '" + err + "'" - }); + // GPII-4087 Hack. Do nothing if options.local option is not set. + if (options.local === true) { + if (fs.existsSync(path)) { + if (options.recursive) { + try { + rimraf.sync(path); + result.resolve(); + } catch (err) { + result.reject({ + code: err.code, + message: "Removing dir failed with error '" + err + "'" + }); + } + } else { + try { + fs.unlinkSync(path); + } catch (err) { + result.reject({ + code: err.code, + message: "Removing file failed with error '" + err + "'" + }); + } } } else { - try { - fs.unlinkSync(path); + if (options.silent) { result.resolve(); - } catch (err) { - result.reject({ - code: err.code, - message: "Removing file failed with error '" + err + "'" - }); + fluid.log("Warning: Trying to remove non-existent element."); + } else { + result.reject({code: "ENOENT", message: "Invalid path '" + path + "'."}); } } + + return result; } else { - if (options.silent) { - result.resolve(); - fluid.log("Warning: Trying to remove non-existent element."); - } else { - result.reject({code: "ENOENT", message: "Invalid path '" + path + "'."}); - } + result.resolve(); } return result; diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index 45592501b..ef00f1566 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -413,11 +413,35 @@ gpii.windows.spi.setImpl = function (payload) { // to reload the default theme (or last saved?) - causing any custom wallpaper to be lost. // This hack just gets the current high-contrast state and does nothing if it's already turned off. var applyPromise; - if (payload.settings.HighContrastOn && !payload.settings.HighContrastOn.value && - !payload.settings.HighContrastTheme) { - var currentState = gpii.windows.spi.getImpl(fluid.copy(payload)); - if (!currentState.HighContrastOn.value) { + if (payload.settings.HighContrastOn) { + var doNothing = false; + if (!payload.settings.HighContrastOn.value && !payload.settings.HighContrastTheme) { + var currentState = gpii.windows.spi.getImpl(fluid.copy(payload)); + if (!currentState.HighContrastOn.value) { + doNothing = true; + } + } + + if (doNothing) { applyPromise = fluid.promise().resolve(); + } else { + var cachedFilesPathEnv = "%APPDATA%\\Microsoft\\Windows\\Themes\\CachedFiles"; + var cachedFilesPath = cachedFilesPathEnv.replace(/%([^%]+)%/g, function (_,n) { return process.env[n] }); + var optionsCached = { + "recursive": true, + "silent": true, + "local": true + }; + + var tWallpaperEnv = "%APPDATA%\\Microsoft\\Windows\\Themes\\TranscodedWallpaper"; + var tWallpaper = tWallpaperEnv.replace(/%([^%]+)%/g, function (_,n) { return process.env[n] }); + var optionsCached = { + "silent": true, + "local": true + }; + + gpii.windows.rm(cachedFilesPath, optionsCached); + gpii.windows.rm(tWallpaper, optionsCached); } } From 805aa81a4ea1e1b0d825506249dc4abcdc86e2d5 Mon Sep 17 00:00:00 2001 From: ste Date: Tue, 20 Aug 2019 16:11:29 +0100 Subject: [PATCH 044/123] GPII-4092: Volume handler returns 0 on failure, instead of rejecting. --- .../nativeSettingsHandler/src/nativeSettingsHandler.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gpii/node_modules/nativeSettingsHandler/src/nativeSettingsHandler.js b/gpii/node_modules/nativeSettingsHandler/src/nativeSettingsHandler.js index 78624d29d..353c2706f 100644 --- a/gpii/node_modules/nativeSettingsHandler/src/nativeSettingsHandler.js +++ b/gpii/node_modules/nativeSettingsHandler/src/nativeSettingsHandler.js @@ -166,7 +166,7 @@ windows.nativeSettingsHandler.SetDoubleClickHeight = function (num) { * * @param {String} mode The operation mode, could be either "Set" or "Get". * @param {Number} [num] The value to set as the current system volume. Range is normalized from 0 to 1. - * @return {Promise} A promise that resolves on success or holds a object with the error information. + * @return {Promise} A promise that resolves with the current volume (0 if there was an error) */ windows.nativeSettingsHandler.VolumeHandler = function (mode, num) { var promise = fluid.promise(); @@ -188,10 +188,12 @@ windows.nativeSettingsHandler.VolumeHandler = function (mode, num) { if (value) { promise.resolve(parseFloat(value)); } else { - promise.reject(jsonRes); + // To stop the QSS from crashing if there's no sound card, return zero. When the QSS is able to handle + // rejections, this can be a changed to a reject. [GPII-4092] + promise.resolve(0); } } catch (err) { - promise.reject(err); + promise.resolve(0); } return promise; From e43360ffdb7dfbf07baece9bbf187ad934c6e3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Tue, 20 Aug 2019 18:16:25 +0200 Subject: [PATCH 045/123] GPII-4087: Simplified path creation --- .../src/SpiSettingsHandler.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index ef00f1566..86ad6c41c 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -425,23 +425,14 @@ gpii.windows.spi.setImpl = function (payload) { if (doNothing) { applyPromise = fluid.promise().resolve(); } else { - var cachedFilesPathEnv = "%APPDATA%\\Microsoft\\Windows\\Themes\\CachedFiles"; - var cachedFilesPath = cachedFilesPathEnv.replace(/%([^%]+)%/g, function (_,n) { return process.env[n] }); - var optionsCached = { - "recursive": true, - "silent": true, - "local": true - }; + var cachedFilesPath = path.join(env.APPDATA, "Microsoft\\Windows\\Themes\\CachedFiles"); + var optionsCached = { "recursive": true, "silent": true, "local": true }; - var tWallpaperEnv = "%APPDATA%\\Microsoft\\Windows\\Themes\\TranscodedWallpaper"; - var tWallpaper = tWallpaperEnv.replace(/%([^%]+)%/g, function (_,n) { return process.env[n] }); - var optionsCached = { - "silent": true, - "local": true - }; + var tWallpaperPath = path.join(env.APPDATA, "Microsoft\\Windows\\Themes\\TranscodedWallpaper"); + var optionsCached = { "silent": true, "local": true }; gpii.windows.rm(cachedFilesPath, optionsCached); - gpii.windows.rm(tWallpaper, optionsCached); + gpii.windows.rm(tWallpaperPath, optionsCached); } } From 3cbf5aee97c27f79d60a39b5c93b061ab946304a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Tue, 20 Aug 2019 18:26:41 +0200 Subject: [PATCH 046/123] GPII-4087: Deleted forgotten extra space --- gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index 86ad6c41c..4cdf5c218 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -426,7 +426,7 @@ gpii.windows.spi.setImpl = function (payload) { applyPromise = fluid.promise().resolve(); } else { var cachedFilesPath = path.join(env.APPDATA, "Microsoft\\Windows\\Themes\\CachedFiles"); - var optionsCached = { "recursive": true, "silent": true, "local": true }; + var optionsCached = { "recursive": true, "silent": true, "local": true }; var tWallpaperPath = path.join(env.APPDATA, "Microsoft\\Windows\\Themes\\TranscodedWallpaper"); var optionsCached = { "silent": true, "local": true }; From a7369c5b22a2023eba16cd9b79f7cb1199faa755 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Wed, 21 Aug 2019 16:50:34 +0200 Subject: [PATCH 047/123] GPII-4087: Fixed invalid reference to 'process.env' --- .../node_modules/spiSettingsHandler/src/SpiSettingsHandler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index 4cdf5c218..b7a338b16 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -425,10 +425,10 @@ gpii.windows.spi.setImpl = function (payload) { if (doNothing) { applyPromise = fluid.promise().resolve(); } else { - var cachedFilesPath = path.join(env.APPDATA, "Microsoft\\Windows\\Themes\\CachedFiles"); + var cachedFilesPath = path.join(process.env.APPDATA, "Microsoft\\Windows\\Themes\\CachedFiles"); var optionsCached = { "recursive": true, "silent": true, "local": true }; - var tWallpaperPath = path.join(env.APPDATA, "Microsoft\\Windows\\Themes\\TranscodedWallpaper"); + var tWallpaperPath = path.join(process.env.APPDATA, "Microsoft\\Windows\\Themes\\TranscodedWallpaper"); var optionsCached = { "silent": true, "local": true }; gpii.windows.rm(cachedFilesPath, optionsCached); From aab42958b5ef3d5fa481d7810332006513651c24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Wed, 21 Aug 2019 17:02:59 +0200 Subject: [PATCH 048/123] GPII-4087: Renamed options for deleting wallpaper files fixing redefinition --- .../spiSettingsHandler/src/SpiSettingsHandler.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index b7a338b16..f9c9e23fe 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -426,13 +426,13 @@ gpii.windows.spi.setImpl = function (payload) { applyPromise = fluid.promise().resolve(); } else { var cachedFilesPath = path.join(process.env.APPDATA, "Microsoft\\Windows\\Themes\\CachedFiles"); - var optionsCached = { "recursive": true, "silent": true, "local": true }; + var optsCachedFiles = { "recursive": true, "silent": true, "local": true }; var tWallpaperPath = path.join(process.env.APPDATA, "Microsoft\\Windows\\Themes\\TranscodedWallpaper"); - var optionsCached = { "silent": true, "local": true }; + var optsWallpaper = { "silent": true, "local": true }; - gpii.windows.rm(cachedFilesPath, optionsCached); - gpii.windows.rm(tWallpaperPath, optionsCached); + gpii.windows.rm(cachedFilesPath, optsCachedFiles); + gpii.windows.rm(tWallpaperPath, optsWallpaper); } } From 5bbfd99d51b6e7957663b2cd8436a66a1aa09e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Wed, 21 Aug 2019 18:24:55 +0200 Subject: [PATCH 049/123] GPII-4087: Added accidentally removed resolve for promise --- gpii/node_modules/WindowsUtilities/WindowsUtilities.js | 1 + 1 file changed, 1 insertion(+) diff --git a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js index b7614dd3d..2cf480798 100644 --- a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js +++ b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js @@ -1958,6 +1958,7 @@ gpii.windows.rm = function (path, options) { } else { try { fs.unlinkSync(path); + result.resolve(); } catch (err) { result.reject({ code: err.code, From 423c62cd74437ee57c971602dc8729cc46517c87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Wed, 21 Aug 2019 18:26:08 +0200 Subject: [PATCH 050/123] GPII-4087: Added new hacky option for making 'gpii.windows.rm' tests pass --- .../test/WindowsUtilitiesTests.js | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/gpii/node_modules/WindowsUtilities/test/WindowsUtilitiesTests.js b/gpii/node_modules/WindowsUtilities/test/WindowsUtilitiesTests.js index f8eea72b2..066b7142b 100644 --- a/gpii/node_modules/WindowsUtilities/test/WindowsUtilitiesTests.js +++ b/gpii/node_modules/WindowsUtilities/test/WindowsUtilitiesTests.js @@ -451,8 +451,9 @@ jqUnit.test("Testing removeFiles", function () { }; // Trying to remove directory as file should fail with EPERM - var pr1 = gpii.windows.rm(nonEmptyDirectory, { recursive: false, silent: false }); - var pr2 = gpii.windows.rm(nonEmptyDirectory, { recursive: false, silent: true }); + // HACK: Added new 'local' option due to GPII-4087 + var pr1 = gpii.windows.rm(nonEmptyDirectory, { recursive: false, silent: false, local: true }); + var pr2 = gpii.windows.rm(nonEmptyDirectory, { recursive: false, silent: true, local: true }); checkRemoveAsFile(pr1); checkRemoveAsFile(pr2); @@ -468,10 +469,11 @@ jqUnit.test("Testing removeFiles", function () { ); }; + // HACK: Added new 'local' option due to GPII-4087 // Trying to remove non-empty directory should succeed. - pr1 = gpii.windows.rm(nonEmptyDirectory, { recursive: true, silent: false }); + pr1 = gpii.windows.rm(nonEmptyDirectory, { recursive: true, silent: false, local: true }); nonEmptyDirectory = createNonEmptyDir(); - pr2 = gpii.windows.rm(nonEmptyDirectory, { recursive: true, silent: true }); + pr2 = gpii.windows.rm(nonEmptyDirectory, { recursive: true, silent: true, local: true }); checkRemoveNonEmpty(pr1); checkRemoveNonEmpty(pr2); @@ -493,20 +495,23 @@ jqUnit.test("Testing removeFiles", function () { // Trying to remove file should succeed. var f1 = createTestFile(); - pr1 = gpii.windows.rm(f1, { recursive: false, silent: false }); + // HACK: Added new 'local' option due to GPII-4087 + pr1 = gpii.windows.rm(f1, { recursive: false, silent: false, local: true }); checkRemoveFile(pr1); f1 = createTestFile(); - pr1 = gpii.windows.rm(f1, { recursive: false, silent: true }); + pr1 = gpii.windows.rm(f1, { recursive: false, silent: true, local: true }); checkRemoveFile(pr1); // Trying to remove file recursively should also succeed. f1 = createTestFile(); - pr1 = gpii.windows.rm(f1, { recursive: true, silent: false }); + // HACK: Added new 'local' option due to GPII-4087 + pr1 = gpii.windows.rm(f1, { recursive: true, silent: false, local: true }); checkRemoveFile(pr1); f1 = createTestFile(); - pr1 = gpii.windows.rm(f1, { recursive: true, silent: true }); + // HACK: Added new 'local' option due to GPII-4087 + pr1 = gpii.windows.rm(f1, { recursive: true, silent: true, local: true }); checkRemoveFile(pr1); var checkRemoveNonExistent = function (promise) { @@ -532,8 +537,9 @@ jqUnit.test("Testing removeFiles", function () { var nonExistingFile = path.join(testRoot, "nonExistingFile.txt"); var nonExistingDir = path.join(testRoot, "nonExistingDir"); - pr1 = gpii.windows.rm(nonExistingDir, {recursive: true, silent: false}); - pr2 = gpii.windows.rm(nonExistingFile, {recursive: true, silent: false}); + // HACK: Added new 'local' option due to GPII-4087 + pr1 = gpii.windows.rm(nonExistingDir, {recursive: true, silent: false, local: true}); + pr2 = gpii.windows.rm(nonExistingFile, {recursive: true, silent: false, local: true}); checkRemoveNonExistent(pr1); checkRemoveNonExistent(pr2); @@ -558,8 +564,9 @@ jqUnit.test("Testing removeFiles", function () { ); }; - pr1 = gpii.windows.rm(nonExistingDir, {recursive: true, silent: false}); - pr2 = gpii.windows.rm(nonExistingFile, {recursive: true, silent: false}); + // HACK: Added new 'local' option due to GPII-4087 + pr1 = gpii.windows.rm(nonExistingDir, {recursive: true, silent: false, local: true}); + pr2 = gpii.windows.rm(nonExistingFile, {recursive: true, silent: false, local: true}); checkRemoveSilent(pr1); checkRemoveSilent(pr2); From 958824e5927bb3e94efcba75ee17ae64b7962e5b Mon Sep 17 00:00:00 2001 From: ste Date: Thu, 17 Oct 2019 10:28:53 +0100 Subject: [PATCH 051/123] GPII-4186: Using windows certificate store for certificates. --- index.js | 7 +++++++ package.json | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 2eab92473..c1f9facb7 100644 --- a/index.js +++ b/index.js @@ -30,6 +30,13 @@ fluid.contextAware.makeChecks({ var binPath = path.join(__dirname, "bin"); process.env.path = binPath + ";" + process.env.path; +// Use certificates from the Windows certificate stores [GPII-4186] +var ca = require("win-ca/api"); +ca({ + store: ["MY", "Root", "Trust", "CA"], + inject: "+" +}); + require("./gpii/node_modules/WindowsUtilities/WindowsUtilities.js"); require("./gpii/node_modules/processHandling/processHandling.js"); require("./gpii/node_modules/displaySettingsHandler"); diff --git a/package.json b/package.json index 4206dde9a..dac21a065 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "ref-struct": "1.1.0", "ref-array": "1.1.2", "string-argv": "0.0.2", - "rimraf": "2.6.2" + "rimraf": "2.6.2", + "win-ca": "^3.1.0" }, "devDependencies": { "eslint-config-fluid": "1.3.0", From 4fe32f609920151ae87f359998a1d61baec355ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Hern=C3=A1ndez?= Date: Thu, 7 Nov 2019 20:26:59 +0100 Subject: [PATCH 052/123] GPII-4214: Updated universal reference --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 002d3066e..a0f51e8ff 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "dependencies": { "edge-js": "10.3.1", "ffi-napi": "2.4.3", - "gpii-universal": "stegru/universal#GPII-2338", + "gpii-universal": "javihernandez/universal#85e8de4e70087188170712e296083e9437b1b120", "@pokusew/pcsclite": "0.4.18", "ref": "1.3.4", "ref-struct": "1.1.0", From 0974bf3dfee1b3cb9544df33205f2171faf9530b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 15 Nov 2019 10:04:16 +0100 Subject: [PATCH 053/123] GPII-4152: Replaced old-managed settingsHelper with new native implementation --- settingsHelper/.editorconfig | 1 + settingsHelper/.gitignore | 390 +++++++++- settingsHelper/README.md | 99 --- settingsHelper/SettingsHelper.sln | 72 +- settingsHelper/SettingsHelper/ISettingItem.cs | 140 ---- .../SettingsHelper/Properties/AssemblyInfo.cs | 35 - .../SettingsHelper/SettingHandler.cs | 278 -------- settingsHelper/SettingsHelper/SettingItem.cs | 344 --------- .../SettingsHelper/SettingsHelper.cs | 167 ----- .../SettingsHelper/SettingsHelper.csproj | 92 --- settingsHelper/SettingsHelper/app.manifest | 76 -- .../SettingsHelperApp/SettingsHelperApp.cpp | 32 + .../SettingsHelperApp.vcxproj | 183 +++++ settingsHelper/SettingsHelperApp/pch.cpp | 5 + settingsHelper/SettingsHelperApp/pch.h | 14 + .../SettingsHelperLib/BaseSettingItem.cpp | 255 +++++++ .../SettingsHelperLib/BaseSettingItem.h | 173 +++++ .../SettingsHelperLib/Constants.cpp | 67 ++ settingsHelper/SettingsHelperLib/Constants.h | 49 ++ .../SettingsHelperLib/DbSettingItem.cpp | 5 + .../SettingsHelperLib/DbSettingItem.h | 14 + .../DynamicSettingDatabase.cpp | 193 +++++ .../DynamicSettingsDatabase.h | 33 + .../IDynamicSettingsDatabase.h | 18 + .../SettingsHelperLib/IPropertyValueUtils.cpp | 308 ++++++++ .../SettingsHelperLib/IPropertyValueUtils.h | 56 ++ .../SettingsHelperLib/ISettingItem.h | 77 ++ .../SettingsHelperLib/ISettingsCollection.h | 62 ++ settingsHelper/SettingsHelperLib/Payload.cpp | 531 ++++++++++++++ settingsHelper/SettingsHelperLib/Payload.h | 125 ++++ .../SettingsHelperLib/PayloadProc.cpp | 363 ++++++++++ .../SettingsHelperLib/PayloadProc.h | 182 +++++ .../SettingsHelperLib/SettingItem.cpp | 210 ++++++ .../SettingsHelperLib/SettingItem.h | 100 +++ .../SettingItemEventHandler.cpp | 122 ++++ .../SettingItemEventHandler.h | 68 ++ .../SettingsHelperLib/SettingUtils.cpp | 672 ++++++++++++++++++ .../SettingsHelperLib/SettingUtils.h | 150 ++++ .../SettingsHelperLib.vcxproj | 203 ++++++ .../SettingsHelperLib.vcxproj.filters | 108 +++ .../SettingsHelperLib/SettingsIIDs.h | 119 ++++ .../SettingsHelperLib/StringConversion.cpp | 18 + .../SettingsHelperLib/StringConversion.h | 21 + settingsHelper/SettingsHelperLib/stdafx.cpp | 1 + settingsHelper/SettingsHelperLib/stdafx.h | 13 + settingsHelper/SettingsHelperLib/targetver.h | 8 + .../SettingsHelperTests/GlobalEnvironment.h | 21 + .../SettingsHelperTests/ParsingTests.cpp | 100 +++ .../SettingsHelperTests/SettingItemTests.cpp | 578 +++++++++++++++ .../SettingsHelperTests/SettingUtilsTests.cpp | 185 +++++ .../SettingsHelperTests.vcxproj | 155 ++++ .../SettingsHelperTests/TestsMain.cpp | 12 + .../SettingsHelperTests/packages.config | 4 + settingsHelper/SettingsHelperTests/pch.cpp | 6 + settingsHelper/SettingsHelperTests/pch.h | 8 + settingsHelper/examples/README.md | 8 - settingsHelper/examples/magnifier.json | 11 - settingsHelper/examples/night-light.json | 6 - settingsHelper/examples/taskbar.json | 11 - 59 files changed, 6057 insertions(+), 1300 deletions(-) create mode 100644 settingsHelper/.editorconfig delete mode 100644 settingsHelper/README.md delete mode 100644 settingsHelper/SettingsHelper/ISettingItem.cs delete mode 100644 settingsHelper/SettingsHelper/Properties/AssemblyInfo.cs delete mode 100644 settingsHelper/SettingsHelper/SettingHandler.cs delete mode 100644 settingsHelper/SettingsHelper/SettingItem.cs delete mode 100644 settingsHelper/SettingsHelper/SettingsHelper.cs delete mode 100644 settingsHelper/SettingsHelper/SettingsHelper.csproj delete mode 100644 settingsHelper/SettingsHelper/app.manifest create mode 100644 settingsHelper/SettingsHelperApp/SettingsHelperApp.cpp create mode 100644 settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj create mode 100644 settingsHelper/SettingsHelperApp/pch.cpp create mode 100644 settingsHelper/SettingsHelperApp/pch.h create mode 100644 settingsHelper/SettingsHelperLib/BaseSettingItem.cpp create mode 100644 settingsHelper/SettingsHelperLib/BaseSettingItem.h create mode 100644 settingsHelper/SettingsHelperLib/Constants.cpp create mode 100644 settingsHelper/SettingsHelperLib/Constants.h create mode 100644 settingsHelper/SettingsHelperLib/DbSettingItem.cpp create mode 100644 settingsHelper/SettingsHelperLib/DbSettingItem.h create mode 100644 settingsHelper/SettingsHelperLib/DynamicSettingDatabase.cpp create mode 100644 settingsHelper/SettingsHelperLib/DynamicSettingsDatabase.h create mode 100644 settingsHelper/SettingsHelperLib/IDynamicSettingsDatabase.h create mode 100644 settingsHelper/SettingsHelperLib/IPropertyValueUtils.cpp create mode 100644 settingsHelper/SettingsHelperLib/IPropertyValueUtils.h create mode 100644 settingsHelper/SettingsHelperLib/ISettingItem.h create mode 100644 settingsHelper/SettingsHelperLib/ISettingsCollection.h create mode 100644 settingsHelper/SettingsHelperLib/Payload.cpp create mode 100644 settingsHelper/SettingsHelperLib/Payload.h create mode 100644 settingsHelper/SettingsHelperLib/PayloadProc.cpp create mode 100644 settingsHelper/SettingsHelperLib/PayloadProc.h create mode 100644 settingsHelper/SettingsHelperLib/SettingItem.cpp create mode 100644 settingsHelper/SettingsHelperLib/SettingItem.h create mode 100644 settingsHelper/SettingsHelperLib/SettingItemEventHandler.cpp create mode 100644 settingsHelper/SettingsHelperLib/SettingItemEventHandler.h create mode 100644 settingsHelper/SettingsHelperLib/SettingUtils.cpp create mode 100644 settingsHelper/SettingsHelperLib/SettingUtils.h create mode 100644 settingsHelper/SettingsHelperLib/SettingsHelperLib.vcxproj create mode 100644 settingsHelper/SettingsHelperLib/SettingsHelperLib.vcxproj.filters create mode 100644 settingsHelper/SettingsHelperLib/SettingsIIDs.h create mode 100644 settingsHelper/SettingsHelperLib/StringConversion.cpp create mode 100644 settingsHelper/SettingsHelperLib/StringConversion.h create mode 100644 settingsHelper/SettingsHelperLib/stdafx.cpp create mode 100644 settingsHelper/SettingsHelperLib/stdafx.h create mode 100644 settingsHelper/SettingsHelperLib/targetver.h create mode 100644 settingsHelper/SettingsHelperTests/GlobalEnvironment.h create mode 100644 settingsHelper/SettingsHelperTests/ParsingTests.cpp create mode 100644 settingsHelper/SettingsHelperTests/SettingItemTests.cpp create mode 100644 settingsHelper/SettingsHelperTests/SettingUtilsTests.cpp create mode 100644 settingsHelper/SettingsHelperTests/SettingsHelperTests.vcxproj create mode 100644 settingsHelper/SettingsHelperTests/TestsMain.cpp create mode 100644 settingsHelper/SettingsHelperTests/packages.config create mode 100644 settingsHelper/SettingsHelperTests/pch.cpp create mode 100644 settingsHelper/SettingsHelperTests/pch.h delete mode 100644 settingsHelper/examples/README.md delete mode 100644 settingsHelper/examples/magnifier.json delete mode 100644 settingsHelper/examples/night-light.json delete mode 100644 settingsHelper/examples/taskbar.json diff --git a/settingsHelper/.editorconfig b/settingsHelper/.editorconfig new file mode 100644 index 000000000..fcdd6a065 --- /dev/null +++ b/settingsHelper/.editorconfig @@ -0,0 +1 @@ +end_of_line = lf \ No newline at end of file diff --git a/settingsHelper/.gitignore b/settingsHelper/.gitignore index 725026666..8d9442414 100644 --- a/settingsHelper/.gitignore +++ b/settingsHelper/.gitignore @@ -1,5 +1,387 @@ +# Created by https://www.gitignore.io/api/c++,visualstudio +# Edit at https://www.gitignore.io/?templates=c++,visualstudio + +### C++ ### +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +### VisualStudio ### +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory .vs/ -bin/ -obj/ -*.csproj.user -~* +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.iobj +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# End of https://www.gitignore.io/api/c++,visualstudio diff --git a/settingsHelper/README.md b/settingsHelper/README.md deleted file mode 100644 index 83b495f9d..000000000 --- a/settingsHelper/README.md +++ /dev/null @@ -1,99 +0,0 @@ -# Windows system settings handler helper process - -Small utility that sets Windows settings via the back-end used by the Windows 10 setting app. - -## Usage - -Reads a JSON array of payloads from stdin: - - SettingsHelper < payload.json - -List all possible settings for the current system: - - SettingsHelper -list - -## Payload JSON - -The payload sent to the application's standard input looks like this: - -```json -[ - { - "settingID": "SystemSettings_Accessibility_Magnifier_IsEnabled", - "method": "SetValue", - "parameters": [ true ] - } -] -``` - -* `settingID`: The setting ID. See `WindowsSettings -list` -* `method`: The method of to invoke. -* `parameters`: Parameters to pass (optional). - -There can be several items, allowing more than one setting to be applied. - -See [examples](./examples) - -### Methods - -| Method | | -| --- | --- | -| `GetValue [name]` | Gets a value, returning the result. `name` is used if there are different values. | -| `SetValue [name,] value` | Sets the value. | -| `Invoke` | Invokes an `Action` setting. | -| `GetPossibleValues` | Returns a list of settings allowed for `List` settings | -| `IsEnabled` | Returns a boolean indicating if the setting is enabled. | -| `IsApplicable` | Returns a boolean indicating if the setting is applicable. | - -These are the exposed methods of the [SettingItem](WindowsSettings/SettingItem.cs) class. - -### Setting Types and methods - -The type of a setting can be retrieved with `WindowsSettings -list`. - -#### `Boolean`, `String`, `List`, `LabeledString`, and `Range` - -The `GetValue` method returns the value, and `SetValue` sets it. - -* `Boolean` and `String` settings are straightforward. -* `List` settings accept one of several possible values (or the index, depending on the setting), returned by `GetPossibleValues`. -* `Range` appears to be numeric, determining the min and max value is currently unknown. -* `LabeledString` is read-only. - -#### `Action` - -Settings of this type perform an action when the `Invoke` method is used. - -#### `Custom`, `DisplayString` and `SettingCollection` - -Unsupported - needs further investigation. - -## Response - -Each payload item yields a response: - -```json - { - "returnValue": 5, - "isError": true, - "errorMessage": "oops" - } -``` - -* `returnValue`: The return value of the method (optional). -* `isError`: true if there was an error (optional). -* `errorMessage`: The error message (optional). - -## Limitations - -* Relies heavily on undocumented behaviour. -* Some settings crash. -* The availability of settings depends on the exact Windows version. - -## Building - - msbuild WindowsSettings.sln /t:Rebuild /p:Configuration=Release /p:Platform="Any CPU" - -## How it works - -Magic. diff --git a/settingsHelper/SettingsHelper.sln b/settingsHelper/SettingsHelper.sln index 0de49fc5b..b06db8ea8 100644 --- a/settingsHelper/SettingsHelper.sln +++ b/settingsHelper/SettingsHelper.sln @@ -1,38 +1,52 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.27004.2009 +VisualStudioVersion = 15.0.28307.645 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SettingsHelper", "SettingsHelper\SettingsHelper.csproj", "{82260B87-860B-4AD4-B508-5AFAA72B7FF0}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SettingsHelperLib", "SettingsHelperLib\SettingsHelperLib.vcxproj", "{5950CFD8-254D-41B9-A743-ECA34C2AD788}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7F9BE9EE-5BC0-43C4-B5B1-0D080433778F}" - ProjectSection(SolutionItems) = preProject - README.md = README.md - EndProjectSection +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SettingsHelperTests", "SettingsHelperTests\SettingsHelperTests.vcxproj", "{D938B6BB-3085-4A39-AF75-8F4E339CC23D}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "examples", "examples", "{E72D6C81-93F1-4B13-BEAD-93AB8B96572A}" - ProjectSection(SolutionItems) = preProject - examples\magnifier.json = examples\magnifier.json - examples\night-light.json = examples\night-light.json - examples\orientation.json = examples\orientation.json - examples\taskbar.json = examples\taskbar.json - EndProjectSection +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SettingsHelperApp", "SettingsHelperApp\SettingsHelperApp.vcxproj", "{23C9A7E4-FEC8-4948-A232-809044EB871B}" EndProject Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {82260B87-860B-4AD4-B508-5AFAA72B7FF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {82260B87-860B-4AD4-B508-5AFAA72B7FF0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {82260B87-860B-4AD4-B508-5AFAA72B7FF0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {82260B87-860B-4AD4-B508-5AFAA72B7FF0}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {DE3A50A9-67A8-42F5-AA26-9A7A74496DB7} - EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {5950CFD8-254D-41B9-A743-ECA34C2AD788}.Debug|x64.ActiveCfg = Debug|x64 + {5950CFD8-254D-41B9-A743-ECA34C2AD788}.Debug|x64.Build.0 = Debug|x64 + {5950CFD8-254D-41B9-A743-ECA34C2AD788}.Debug|x86.ActiveCfg = Debug|Win32 + {5950CFD8-254D-41B9-A743-ECA34C2AD788}.Debug|x86.Build.0 = Debug|Win32 + {5950CFD8-254D-41B9-A743-ECA34C2AD788}.Release|x64.ActiveCfg = Release|x64 + {5950CFD8-254D-41B9-A743-ECA34C2AD788}.Release|x64.Build.0 = Release|x64 + {5950CFD8-254D-41B9-A743-ECA34C2AD788}.Release|x86.ActiveCfg = Release|Win32 + {5950CFD8-254D-41B9-A743-ECA34C2AD788}.Release|x86.Build.0 = Release|Win32 + {D938B6BB-3085-4A39-AF75-8F4E339CC23D}.Debug|x64.ActiveCfg = Debug|x64 + {D938B6BB-3085-4A39-AF75-8F4E339CC23D}.Debug|x64.Build.0 = Debug|x64 + {D938B6BB-3085-4A39-AF75-8F4E339CC23D}.Debug|x86.ActiveCfg = Debug|Win32 + {D938B6BB-3085-4A39-AF75-8F4E339CC23D}.Debug|x86.Build.0 = Debug|Win32 + {D938B6BB-3085-4A39-AF75-8F4E339CC23D}.Release|x64.ActiveCfg = Release|x64 + {D938B6BB-3085-4A39-AF75-8F4E339CC23D}.Release|x64.Build.0 = Release|x64 + {D938B6BB-3085-4A39-AF75-8F4E339CC23D}.Release|x86.ActiveCfg = Release|Win32 + {D938B6BB-3085-4A39-AF75-8F4E339CC23D}.Release|x86.Build.0 = Release|Win32 + {23C9A7E4-FEC8-4948-A232-809044EB871B}.Debug|x64.ActiveCfg = Debug|x64 + {23C9A7E4-FEC8-4948-A232-809044EB871B}.Debug|x64.Build.0 = Debug|x64 + {23C9A7E4-FEC8-4948-A232-809044EB871B}.Debug|x86.ActiveCfg = Debug|Win32 + {23C9A7E4-FEC8-4948-A232-809044EB871B}.Debug|x86.Build.0 = Debug|Win32 + {23C9A7E4-FEC8-4948-A232-809044EB871B}.Release|x64.ActiveCfg = Release|x64 + {23C9A7E4-FEC8-4948-A232-809044EB871B}.Release|x64.Build.0 = Release|x64 + {23C9A7E4-FEC8-4948-A232-809044EB871B}.Release|x86.ActiveCfg = Release|Win32 + {23C9A7E4-FEC8-4948-A232-809044EB871B}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {7E8828CD-C502-44DC-934A-803E13AA5090} + SolutionGuid = {234D8C77-BC56-4A33-9214-B9391D1606FF} + EndGlobalSection EndGlobal diff --git a/settingsHelper/SettingsHelper/ISettingItem.cs b/settingsHelper/SettingsHelper/ISettingItem.cs deleted file mode 100644 index d8bed568c..000000000 --- a/settingsHelper/SettingsHelper/ISettingItem.cs +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Interface for the SettingItem Windows Runtime object. - * - * Copyright 2018 Raising the Floor - International - * - * Licensed under the New BSD license. You may not use this file except in - * compliance with this License. - * - * The research leading to these results has received funding from the European Union's - * Seventh Framework Programme (FP7/2007-2013) - * under grant agreement no. 289016. - * - * You may obtain a copy of the License at - * https://github.com/GPII/universal/blob/master/LICENSE.txt - */ - - namespace SettingsHelper -{ - using System; - using System.Collections.Generic; - using System.Runtime.InteropServices; - - /// - /// Interface for the settings classes, instantiated by the GetSettings exports. - /// - /// - /// Most of the information was taken from the debug symbols (PDB) for the relevent DLLs. The symbols - /// don't describe the interface, just the classes that implement it (the "vtable"). This contains the - /// method names (and order), and vague information on the parameters (no names, and, er, de-macro'd types). - /// - /// Visual Studio was used to obtain the names by first creating a method with any name, then stepping into the - /// native code from the call with the debugger where the function name will be displayed in the disassembled code. - /// - /// The binding of methods isn't by name, but by order, which is why the "unknown" methods must remain. - /// Not all methods work for some type of setting. - /// - [ComImport, Guid("40C037CC-D8BF-489E-8697-D66BAA3221BF"), InterfaceType(ComInterfaceType.InterfaceIsIInspectable)] - public interface ISettingItem - { - int Id { get; } - SettingType Type { get; } - bool IsSetByGroupPolicy { get; } - bool IsEnabled { get; } - bool IsApplicable { get; } - - // Not always available, sometimes looks like a resource ID - string Description - { - [return: MarshalAs(UnmanagedType.HString)] - get; - } - - // Unknown - bool IsUpdating { get; } - - // For Type = Boolean, List, Range, String - [return: MarshalAs(UnmanagedType.IInspectable)] - object GetValue( - // Normally "Value" - [MarshalAs(UnmanagedType.HString)] string name); - - int SetValue( - // Normally "Value" - [MarshalAs(UnmanagedType.HString)] string name, - [MarshalAs(UnmanagedType.IInspectable)] object value); - - // Unknown usage - int GetProperty(string name); - int SetProperty(string name, object value); - - // For Type = Action - performs the action. - IntPtr Invoke(IntPtr a, Rect b); - - // SettingChanged event - event EventHandler SettingChanged; - - // Unknown - setter for IsUpdating - bool IsUpdating2 { set; } - - // Unknown - int GetInitializationResult(); - int DoGenericAsyncWork(); - int StartGenericAsyncWork(); - int SetSkipConcurrentOperations(bool flag); - - // These appear to be base implementations overridden by the above. - bool GetValue2 { get; } - IntPtr unknown_SetValue1(); - IntPtr unknown_SetValue2(); - IntPtr unknown_SetValue3(); - - // Unknown usage - IntPtr GetNamedValue( - [MarshalAs(UnmanagedType.HString)] string name - //[MarshalAs(UnmanagedType.IInspectable)] object unknown - ); - - IntPtr SetNullValue(); - - // For Type=List: - IntPtr GetPossibleValues(out IList value); - - // There are more unknown methods. - } - - /// The type of setting. - public enum SettingType - { - // Needs investigating - Custom = 0, - - // Read-only - DisplayString = 1, - LabeledString = 2, - - // Values (use GetValue/SetValue) - Boolean = 3, - Range = 4, - String = 5, - List = 6, - - // Performs an action - Action = 7, - - // Needs investigating - SettingCollection = 8, - } - - /// - /// Used by ISettingsItem.Invoke (reason unknown). - /// - public struct Rect - { - public float X; - public float Y; - public float Width; - public float Height; - } - -} diff --git a/settingsHelper/SettingsHelper/Properties/AssemblyInfo.cs b/settingsHelper/SettingsHelper/Properties/AssemblyInfo.cs deleted file mode 100644 index fcd6ad8fe..000000000 --- a/settingsHelper/SettingsHelper/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("SettingsHelper")] -[assembly: AssemblyDescription("Settings helper for GPII")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("SettingsHelper")] -[assembly: AssemblyCopyright("Copyright © Raising the Floor 2017")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("82260b87-860b-4ad4-b508-5afaa72b7ff0")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/settingsHelper/SettingsHelper/SettingHandler.cs b/settingsHelper/SettingsHelper/SettingHandler.cs deleted file mode 100644 index 1a9601947..000000000 --- a/settingsHelper/SettingsHelper/SettingHandler.cs +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Helper for the System Settings Handler. - * - * Copyright 2018 Raising the Floor - International - * - * Licensed under the New BSD license. You may not use this file except in - * compliance with this License. - * - * The research leading to these results has received funding from the European Union's - * Seventh Framework Programme (FP7/2007-2013) - * under grant agreement no. 289016. - * - * You may obtain a copy of the License at - * https://github.com/GPII/universal/blob/master/LICENSE.txt - */ - -namespace SettingsHelper -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Runtime.Serialization; - using System.Runtime.Serialization.Json; - using System.Threading.Tasks; - - public struct SetInfo { - public MethodInfo method { get; set; } - public Type[] paramTypes { get; set; } - - public SetInfo(MethodInfo m, Type[] p) : this() { - this.method = m; - this.paramTypes = p; - } - } - - public class SettingHandler - { - private static Dictionary settingCache = new Dictionary(); - - /// If true, don't apply any settings (but still do everything else). - public static bool DryRun { get; set; } - - /// Returns the desired method to call and it's parameters types, as specified in the payload parameter. - /// The payload specifying the parameters and the method that wants to be called. - /// The flags that will be used for trying to get the desired method through reflection. - /// The object from which obtain the method to be called. - /// A SettingInfo object with the method to be called and an array with it's type parameters". - public static SetInfo GetDesiredMethod(Payload payload, BindingFlags bindingFlags, SettingItem settingItem) - { - // Get the parameter types to get the right overload. - Type[] paramTypes = Type.EmptyTypes; - if (payload.Parameters != null) - { - paramTypes = payload.Parameters.Select(p => p.GetType()).ToArray(); - } - - MethodInfo method = - settingItem.GetType().GetMethod(payload.Method, bindingFlags, null, paramTypes, null); - - if (method == null) - { - // Method can't be found matching the parameter types, get any method with the same name - // and let .Invoke worry about the parameters. - try - { - method = settingItem.GetType().GetMethod(payload.Method, bindingFlags); - } - catch (AmbiguousMatchException) - { - method = null; - } - - if (method == null) - { - throw new SettingFailedException("Unknown method " + payload.Method); - } - } - - if (!method.IsExposed()) - { - // Only use those with the "Exposed" attribute. - throw new SettingFailedException("Not an exposed method " + payload.Method); - } - - return new SetInfo(method, paramTypes); - } - - /// - /// Calls the method specified in the payload if possible. - /// - /// The payload. - /// The setting which method is going to be called. - /// The value stored by the setting prior to this operation. - /// The place to store the result of this operation. - public static void CallDesiredMethod(Payload payload, SettingItem settingItem, object oldValue, ref Result result) - { - BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase; - - // Get the proper method to call - SetInfo setInfo = GetDesiredMethod(payload, bindingFlags, settingItem); - - object expectedValue = null; - if (payload.Parameters.Length == 1 && payload.Method == "SetValue") - { - Type paramType = setInfo.paramTypes[0]; - expectedValue = Convert.ChangeType(payload.Parameters[0], paramType); - } - - // Try to set the new value just in case of the expected value not matching the - // old one, or trying to call a method different to "SetValue". - if (!expectedValue.Equals(oldValue)) - { - result.ReturnValue = setInfo.method.Invoke(settingItem, payload.Parameters); - - // Wait for completion in case of setting being Async. - if (payload.Async ?? false) - { - settingItem.WaitForCompletion(5); - } - - // Check if the new setting value matches the expected one in case of being requested. - if (payload.CheckResult ?? false) { - var newValue = settingItem.GetValue(); - - if (!expectedValue.Equals(newValue)) - { - throw new SettingFailedException("Unable to set the provided setting value"); - } - } - } - else - { - result.ReturnValue = oldValue; - } - } - - /// - /// Applies a payload, by invoking the method identified in payload. - /// - /// The payload. - /// The result. - public static Result Apply(Payload payload) - { - Result result = new Result(payload); - try - { - if (string.IsNullOrEmpty(payload.SettingId)) - { - throw new SettingFailedException("settingID is required."); - } - - if (string.IsNullOrEmpty(payload.Method)) - { - throw new SettingFailedException("method is required."); - } - - SettingItem settingItem; - - // Cache the instance, incase it's re-used. - if (!settingCache.TryGetValue(payload.SettingId, out settingItem)) - { - var async = payload.Async ?? false; - settingItem = new SettingItem(payload.SettingId, SettingHandler.DryRun, async); - settingCache[payload.SettingId] = settingItem; - } - - payload.SettingItem = settingItem; - - var oldValue = settingItem.GetValue(); - - // In case of setting a new value, verify that the setting value has changed - if (payload.Method == "GetValue") - { - result.ReturnValue = oldValue; - } - else - { - try - { - CallDesiredMethod(payload, settingItem, oldValue, ref result); - } - catch (Exception e) - { - // Catching general exceptions is ok with .NET 4 because corrupted state exceptions aren't caught. - throw new SettingFailedException(null, e.InnerException ?? e); - } - } - } - catch (SettingFailedException e) - { - result.IsError = true; - result.ErrorMessage = e.Message; - } - - return result; - } - } - - /// A setting payload. - [DataContract] - public class Payload - { - /// The Setting ID - [DataMember(Name = "settingID")] - public string SettingId { get; set; } - - /// The method of SettingItem to call. - [DataMember(Name = "method")] - public string Method { get; set; } - - /// The parameters to pass to the method. - [DataMember(Name = "parameters")] - public object[] Parameters { get; private set; } - - /// The expected value for the setting after performing the set operation. - [DataMember(Name = "checkResult")] - public bool? CheckResult { get; private set; } - - /// False to wait for this setting to complete. - [DataMember(Name = "async")] - public bool? Async { get; private set; } - - /// The setting item (set after it has been applied). - internal SettingItem SettingItem { get; set; } - - /// Instantiates some payloads from the given stream of JSON. - /// The input data. - /// Enumeration of payloads. - public static IEnumerable FromStream(Stream input) - { - // The built-in JSON library is crap, but it saves needing a dependency. - DataContractJsonSerializer json = new DataContractJsonSerializer(typeof(Payload[])); - return json.ReadObject(input) as Payload[] ?? Enumerable.Empty(); - } - } - - /// A payload result. - [DataContract] - public class Result - { - /// The setting ID. - [DataMember(Name = "settingID", EmitDefaultValue = false)] - public string SettingId { get; set; } - - /// true if there was an error. - [DataMember(Name = "isError", EmitDefaultValue = false)] - public bool IsError { get; set; } - - /// The error message. - [DataMember(Name = "errorMessage", EmitDefaultValue = false)] - public string ErrorMessage { get; set; } - - /// The return value. - [DataMember(Name = "returnValue", EmitDefaultValue = true)] - public object ReturnValue { get; set; } - - public Result(Payload payload) - { - this.SettingId = payload.SettingId; - } - - public override string ToString() - { - DataContractJsonSerializer json = new DataContractJsonSerializer(typeof(Result)); - using (MemoryStream buffer = new MemoryStream()) - { - json.WriteObject(buffer, this); - buffer.Position = 0; - using (StreamReader reader = new StreamReader(buffer)) - { - return reader.ReadToEnd(); - } - } - } - } -} diff --git a/settingsHelper/SettingsHelper/SettingItem.cs b/settingsHelper/SettingsHelper/SettingItem.cs deleted file mode 100644 index 7aa9e4b6b..000000000 --- a/settingsHelper/SettingsHelper/SettingItem.cs +++ /dev/null @@ -1,344 +0,0 @@ -/* - * Handles a setting - a wrapper for ISettingItem. - * - * Copyright 2018 Raising the Floor - International - * - * Licensed under the New BSD license. You may not use this file except in - * compliance with this License. - * - * The research leading to these results has received funding from the European Union's - * Seventh Framework Programme (FP7/2007-2013) - * under grant agreement no. 289016. - * - * You may obtain a copy of the License at - * https://github.com/GPII/universal/blob/master/LICENSE.txt - */ - -namespace SettingsHelper -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Runtime.InteropServices; - using System.Runtime.InteropServices.WindowsRuntime; - using Microsoft.Win32; - using System.Threading; - - /// - /// Handles a setting (a wrapper for ISettingItem). - /// - public class SettingItem - { - /// Location of the setting definitions in the registry. - internal const string RegistryPath = @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\SystemSettings\SettingId"; - - /// The name of the GetSetting export. - private const string GetSettingExport = "GetSetting"; - - /// An ISettingItem class that this class is wrapping. - private ISettingItem settingItem; - - /// True to make SetValue and Invoke do nothing. - private bool dryRun; - private bool gotValue = false; - - /// True to make the setting behaves as an "Async" setting. - private bool async; - - /// - [DllImport("kernel32.dll", SetLastError = true)] - private static extern IntPtr LoadLibrary(string lpFileName); - - /// - [DllImport("kernel32", SetLastError = true)] - private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); - - /// Points to a GetSetting export. - /// Setting ID - /// Returns the instance. - /// Unknown. - /// Zero on success. - [UnmanagedFunctionPointer(CallingConvention.Winapi)] - private delegate IntPtr GetSettingFunc( - [MarshalAs(UnmanagedType.HString)] string settingId, - out ISettingItem settingItem, - IntPtr n); - - /// The type of this setting. - public SettingType SettingType { get; private set; } - - /// - /// Initializes a new instance of the SettingItem class. - /// - /// The setting ID. - /// true to make Invoke and SetValue methods do nothing. - public SettingItem(string settingId, bool dryRun = false, bool async = false) - { - this.async = async; - this.dryRun = dryRun; - string dllPath = this.GetSettingDll(settingId); - if (dllPath == null) - { - throw new SettingFailedException("No such setting"); - } - - this.settingItem = this.GetSettingItem(settingId, dllPath); - this.SettingType = this.settingItem.Type; - } - - /// Gets the setting's value. - /// The value. - [Expose] - public object GetValue() - { - return this.GetValue("Value"); - } - - /// Gets the setting's value. - /// Value name (normally "Value") - /// The value. - [Expose] - public object GetValue(string valueName) - { - int timer = 5000, delay = 200; - - // This function is neccessary to be called so the async operations - // for getting the actual ISettingItem value are triggered. - this.settingItem.GetValue(valueName); - while (this.async && !this.gotValue && (timer -= delay) > 0) - { - Thread.Sleep(delay); - } - - return this.settingItem.GetValue(valueName); - } - - private void SettingItem_SettingChanged(object sender, string s) - { - if (s == "Value") - { - this.gotValue = true; - } - } - - /// Sets the setting's value. - /// The new value. - /// The previous value. - [Expose] - public object SetValue(object newValue) - { - return this.SetValue("Value", newValue); - } - - /// Sets the setting's value. - /// Value name (normally "Value") - /// The new value. - /// The previous value. - [Expose] - public object SetValue(string valueName, object newValue) - { - object old = this.GetValue(valueName); - if (newValue != old && !this.dryRun) - { - this.settingItem.SetValue(valueName, newValue); - } - WaitForCompletion(5); - - return old; - } - - /// Gets a list of possible values. - /// An enumeration of possible values. - [Expose] - public IEnumerable GetPossibleValues() - { - IList values; - this.settingItem.GetPossibleValues(out values); - return values; - } - - /// Invokes an Action setting. - /// The return value of the action function. - [Expose] - public long Invoke() - { - return this.Invoke(IntPtr.Zero); - } - - /// Invokes an Action setting. - /// The return value of the action function. - [Expose] - public long Invoke(long n) - { - return this.Invoke(new IntPtr(n)); - } - - /// Invokes an Action setting. - /// The return value of the action function. - [Expose] - public long Invoke(string s) - { - IntPtr hstring = WindowsRuntimeMarshal.StringToHString(s); - try - { - return this.Invoke(hstring); - } - finally - { - WindowsRuntimeMarshal.FreeHString(hstring); - } - } - - /// Invokes an Action setting. - /// The return value of the action function. - public long Invoke(IntPtr n) - { - if (this.dryRun) - { - return 0; - } - else - { - return this.settingItem.Invoke(n, new Rect()).ToInt64(); - } - } - - /// Gets the "IsEnabled" value. - /// The value of "IsEnabled". - [Expose] - public bool IsEnabled() - { - return this.settingItem.IsEnabled; - } - - /// Gets the "IsApplicable" value. - /// The value of "IsApplicable". - [Expose] - public bool IsApplicable() - { - return this.settingItem.IsApplicable; - } - - /// Waits for the setting to finish updating (IsUpdating is false) - /// Timeout in seconds. - /// IsUpdating value - so true means it timed-out - public bool WaitForCompletion(int timeout) - { - const int interval = 100; - timeout *= 1000 / interval; - while (this.settingItem.IsUpdating) - { - if (--timeout < 0) - { - break; - } - - System.Threading.Thread.Sleep(interval); - } - - return this.settingItem.IsUpdating; - } - - /// Gets the DLL file that contains the class for the setting. - /// The setting ID. - /// The path of the DLL file containing the setting class, null if the setting doesn't exist. - private string GetSettingDll(string settingId) - { - object value = null; - if (!string.IsNullOrEmpty(settingId)) - { - string path = Path.Combine(RegistryPath, settingId); - value = Registry.GetValue(path, "DllPath", null); - } - - return value == null ? null : value.ToString(); - } - - /// Get an instance of ISettingItem for the given setting. - /// The setting. - /// The dll containing the class. - /// An ISettingItem instance for the setting. - private ISettingItem GetSettingItem(string settingId, string dllPath) - { - // Load the dll. - IntPtr lib = LoadLibrary(dllPath); - if (lib == IntPtr.Zero) - { - throw new SettingFailedException("Unable to load library " + dllPath, true); - } - - // Get the address of the function within the dll. - IntPtr proc = GetProcAddress(lib, GetSettingExport); - if (proc == IntPtr.Zero) - { - throw new SettingFailedException( - string.Format("Unable get address of {0}!{1}", dllPath, GetSettingExport), true); - } - - // Create a function from the address. - GetSettingFunc getSetting = Marshal.GetDelegateForFunctionPointer(proc); - - // Call it. - ISettingItem item; - IntPtr result = getSetting(settingId, out item, IntPtr.Zero); - if (result != IntPtr.Zero || item == null) - { - throw new SettingFailedException("Unable to instantiate setting class", true); - } - item.SettingChanged += SettingItem_SettingChanged; - - return item; - } - } - - /// Thrown when a setting class was unable to have been initialised. - [Serializable] - public class SettingFailedException : Exception - { - public SettingFailedException() { } - public SettingFailedException(string message) - : base(FormatMessage(message)) { } - - public SettingFailedException(string message, bool win32) - : base(FormatMessage(message, win32)) { } - - public SettingFailedException(string message, Exception inner) - : base(FormatMessage(message ?? inner.Message), inner) { } - - protected SettingFailedException( - System.Runtime.Serialization.SerializationInfo info, - System.Runtime.Serialization.StreamingContext context) - : base(info, context) { } - - private static string FormatMessage(string message, bool win32 = false) - { - int lastError = win32 ? Marshal.GetLastWin32Error() : 0; - return (lastError == 0) - ? message - : string.Format("{0} (win32 error {1})", message, lastError); - } - } - - /// - /// A method with this attribute is exposed for use by the payload JSON. - /// - [System.AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = true)] - sealed public class ExposeAttribute : Attribute - { - } - - internal static class ExposeExtension - { - /// - /// Determines if the method is exposed, by having the Exposed attribute. - /// - /// The method to check. - /// true if the method has the Exposed attribute. - public static bool IsExposed(this MethodInfo method) - { - return method.GetCustomAttributes().Any(); - } - } -} diff --git a/settingsHelper/SettingsHelper/SettingsHelper.cs b/settingsHelper/SettingsHelper/SettingsHelper.cs deleted file mode 100644 index e7ce5ed24..000000000 --- a/settingsHelper/SettingsHelper/SettingsHelper.cs +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Helper for the System Settings Handler. - * - * Copyright 2018 Raising the Floor - International - * - * Licensed under the New BSD license. You may not use this file except in - * compliance with this License. - * - * The research leading to these results has received funding from the European Union's - * Seventh Framework Programme (FP7/2007-2013) - * under grant agreement no. 289016. - * - * You may obtain a copy of the License at - * https://github.com/GPII/universal/blob/master/LICENSE.txt - */ - -namespace SettingsHelper -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Globalization; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Runtime.Serialization; - using Microsoft.Win32; - - /// - /// Defines the - /// - public class SettingsHelper - { - /// Entry point - /// The - internal static void Main(string[] args) - { - string inputFile = null; - - if (args != null && args.Length > 0) - { - bool proceed = false; - switch (args[0]) - { - case "-test": - SettingHandler.DryRun = true; - proceed = true; - break; - - case "-list-all": - case "-list": - bool listAll = args[0] == "-list-all"; - string path = SettingItem.RegistryPath.Replace("HKEY_LOCAL_MACHINE\\", string.Empty); - - using (RegistryKey regKey = Registry.LocalMachine.OpenSubKey(path, false)) - { - foreach (string key in regKey.GetSubKeyNames()) - { - string settingPath = Path.Combine(SettingItem.RegistryPath, key); - string typeName = Registry.GetValue(settingPath, "Type", null) as string; - SettingType type; - if (Enum.TryParse(typeName, true, out type)) - { - if (listAll || (type != SettingType.Custom && type != SettingType.SettingCollection)) - { - Console.WriteLine("{0}: {1}", key, type); - } - } - } - } - - break; - - case "-methods": - MethodInfo[] methods = typeof(SettingItem).GetMethods( - BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy); - foreach (MethodInfo method in methods) - { - if (method.IsExposed()) - { - IEnumerable paras = method.GetParameters().Select(p => - { - return string.Format(CultureInfo.InvariantCulture, "{1}: {0}", p.ParameterType.Name, p.Name); - }); - Console.WriteLine("{1}({2}): {0}", method.ReturnType.Name, method.Name, string.Join(", ", paras)); - } - } - - break; - - case "-file": - inputFile = string.Join(" ", args.Skip(1)); - proceed = true; - break; - - default: - Console.Error.WriteLine("Unknown option '{0}'", args); - break; - } - - if (!proceed) - { - return; - } - } - - Stream input; - if (inputFile == null) - { - input = Console.OpenStandardInput(); - } - else - { - input = File.OpenRead(inputFile); - } - - IEnumerable payloads = null; - - using (input) - { - try - { - payloads = Payload.FromStream(input); - } - catch (SerializationException e) - { - Console.Error.Write("Invalid JSON: "); - Console.Error.WriteLine((e.InnerException ?? e).Message); - Environment.ExitCode = 1; - return; - } - } - - bool first = true; - Console.Write("["); - foreach (Payload payload in payloads) - { - if (!first) - { - Console.WriteLine(","); - } - - first = false; - - Result result = SettingHandler.Apply(payload); - Console.Write(result.ToString()); - } - - Console.Write("]"); - - // Wait for them all to complete - 5 seconds should be plenty of time. - const long Timeout = 5000; - Stopwatch timer = new Stopwatch(); - timer.Start(); - foreach (Payload payload in payloads.Where(p => p != null && p.SettingItem != null)) - { - int t = (int)(Timeout - timer.ElapsedMilliseconds); - if (t <= 0) - { - break; - } - - payload.SettingItem.WaitForCompletion(t); - } - } - } -} diff --git a/settingsHelper/SettingsHelper/SettingsHelper.csproj b/settingsHelper/SettingsHelper/SettingsHelper.csproj deleted file mode 100644 index d3689d6e8..000000000 --- a/settingsHelper/SettingsHelper/SettingsHelper.csproj +++ /dev/null @@ -1,92 +0,0 @@ - - - - - Debug - AnyCPU - {82260B87-860B-4AD4-B508-5AFAA72B7FF0} - Exe - SettingsHelper - SettingsHelper - v4.5.1 - 512 - true - publish\ - true - Disk - false - Foreground - 7 - Days - false - false - true - 0 - 1.0.0.%2a - false - false - true - - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - false - - - AnyCPU - none - true - ..\..\bin\ - TRACE - prompt - 4 - false - - - - app.manifest - - - - - - - - - - - - - - - - - - - - - - False - Microsoft .NET Framework 4.6.1 %28x86 and x64%29 - true - - - False - .NET Framework 3.5 SP1 - false - - - - - Designer - - - - \ No newline at end of file diff --git a/settingsHelper/SettingsHelper/app.manifest b/settingsHelper/SettingsHelper/app.manifest deleted file mode 100644 index a6b46bb75..000000000 --- a/settingsHelper/SettingsHelper/app.manifest +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/settingsHelper/SettingsHelperApp/SettingsHelperApp.cpp b/settingsHelper/SettingsHelperApp/SettingsHelperApp.cpp new file mode 100644 index 000000000..d86195a08 --- /dev/null +++ b/settingsHelper/SettingsHelperApp/SettingsHelperApp.cpp @@ -0,0 +1,32 @@ +#include "pch.h" +#include + +#include + +#include "PayloadProc.h" + +using std::wstring; +using std::vector; +using std::pair; + +#pragma comment (lib, "WindowsApp.lib") + +[System::STAThread] +int wmain(int argc, wchar_t* argv[]) { + pair payload { argc, argv }; + DWORD threadID { 0 }; + HANDLE thHandle { NULL }; + + thHandle = CreateThread( + NULL, + 0, + (LPTHREAD_START_ROUTINE)handlePayload, + (LPVOID)&payload, + 0, + &threadID + ); + + WaitForSingleObject(thHandle, INFINITE); + + return 0; +} diff --git a/settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj b/settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj new file mode 100644 index 000000000..5cbbd159b --- /dev/null +++ b/settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj @@ -0,0 +1,183 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {23C9A7E4-FEC8-4948-A232-809044EB871B} + Win32Proj + SettingsHelperApp + 10.0.10240.0 + true + + + + Application + true + v140 + Unicode + + + Application + false + v140 + true + Unicode + + + Application + true + v140 + Unicode + true + + + Application + false + v140 + false + Unicode + true + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)..\bin + + + true + + + false + + + false + + + + Use + Level3 + Disabled + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + pch.h + $(SolutionDir)SettingsHelperLib;%(AdditionalIncludeDirectories) + true + + + Console + true + + + + + Use + Level3 + Disabled + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + true + + + Console + true + + + + + Use + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + true + + + Console + true + true + true + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + pch.h + $(SolutionDir)SettingsHelperLib;%(AdditionalIncludeDirectories) + true + + + Console + true + true + true + + + + + + + + Create + Create + Create + Create + + + + + + {5950cfd8-254d-41b9-a743-eca34c2ad788} + + + + + + \ No newline at end of file diff --git a/settingsHelper/SettingsHelperApp/pch.cpp b/settingsHelper/SettingsHelperApp/pch.cpp new file mode 100644 index 000000000..3a3d12b5a --- /dev/null +++ b/settingsHelper/SettingsHelperApp/pch.cpp @@ -0,0 +1,5 @@ +// pch.cpp: source file corresponding to pre-compiled header; necessary for compilation to succeed + +#include "pch.h" + +// In general, ignore this file, but keep it around if you are using pre-compiled headers. diff --git a/settingsHelper/SettingsHelperApp/pch.h b/settingsHelper/SettingsHelperApp/pch.h new file mode 100644 index 000000000..0ddf6ec24 --- /dev/null +++ b/settingsHelper/SettingsHelperApp/pch.h @@ -0,0 +1,14 @@ +// Tips for Getting Started: +// 1. Use the Solution Explorer window to add/manage files +// 2. Use the Team Explorer window to connect to source control +// 3. Use the Output window to see build output and other messages +// 4. Use the Error List window to view errors +// 5. Go to Project > Add New Item to create new code files, or Project > Add Existing Item to add existing code files to the project +// 6. In the future, to open this project again, go to File > Open > Project and select the .sln file + +#ifndef PCH_H +#define PCH_H + +// TODO: add headers that you want to pre-compile here + +#endif //PCH_H diff --git a/settingsHelper/SettingsHelperLib/BaseSettingItem.cpp b/settingsHelper/SettingsHelperLib/BaseSettingItem.cpp new file mode 100644 index 000000000..492bcb1f4 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/BaseSettingItem.cpp @@ -0,0 +1,255 @@ +#include "stdafx.h" +#include "BaseSettingItem.h" +#include "ISettingsCollection.h" +#include "DynamicSettingsDatabase.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#pragma comment (lib, "WindowsApp.lib") + +using namespace ATL; + +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::UI::Core; + +using std::wstring; +using std::istringstream; +using std::pair; + +BaseSettingItem::BaseSettingItem() {} + +BaseSettingItem::BaseSettingItem(wstring settingId, ATL::CComPtr BaseSettingItem) { + this->settingId = settingId; + this->setting = BaseSettingItem; +} + +BaseSettingItem::BaseSettingItem(const BaseSettingItem & other) { + this->setting = other.setting; + this->settingId = other.settingId; +} + +BaseSettingItem& BaseSettingItem::operator=(const BaseSettingItem& other) { + this->setting = other.setting; + this->settingId = other.settingId; + + return *this; +} + +UINT BaseSettingItem::GetId(std::wstring & id) const { + if (this->setting == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + + HSTRING hId = NULL; + HRESULT res = this->setting->get_Id(&hId); + + if (res == ERROR_SUCCESS && hId != NULL) { + UINT hIdLenght = 0; + PCWSTR rawStrBuf = WindowsGetStringRawBuffer(hId, &hIdLenght); + + if (rawStrBuf != NULL) { + id = std::wstring(rawStrBuf); + } + } + + // Release resources + WindowsDeleteString(hId); + + return res; +} + +UINT BaseSettingItem::GetSettingType(SettingType * val) const { + if (this->setting == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + + return this->setting->get_SettingType(val); +} + +UINT BaseSettingItem::GetIsSetByGroupPolicy(BOOL* val) const { + if (this->setting == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + + return this->setting->get_IsSetByGroupPolicy(val); +} + +UINT BaseSettingItem::GetIsEnabled(BOOL* val) const { + if (this->setting == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + + return this->setting->get_IsEnabled(val); +} + +UINT BaseSettingItem::GetIsApplicable(BOOL* val) const { + if (this->setting == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + + return this->setting->get_IsApplicable(val); +} + +UINT BaseSettingItem::GetDescription(std::wstring& desc) const { + if (this->setting == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + + HSTRING hDesc = NULL; + HRESULT res = this->setting->get_Description(&hDesc); + + if (res == ERROR_SUCCESS && hDesc != NULL) { + UINT hIdLenght = 0; + PCWSTR rawStrBuf = WindowsGetStringRawBuffer(hDesc, &hIdLenght); + + if (rawStrBuf != NULL) { + desc = std::wstring(rawStrBuf); + } + } + + // Release resources + WindowsDeleteString(hDesc); + + return res; +} + +UINT BaseSettingItem::GetIsUpdating(BOOL* val) const { + if (this->setting == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + + return this->setting->get_IsUpdating(val); +} + +UINT BaseSettingItem::GetValue(wstring id, ATL::CComPtr& item) { + if (this->setting == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + if (id.empty()) { return E_INVALIDARG; }; + + HRESULT res = ERROR_SUCCESS; + BOOL isUpdating = false; + + HSTRING hId = NULL; + res = WindowsCreateString(id.c_str(), static_cast(id.size()), &hId); + + IInspectable* curValue = NULL; + if (res == ERROR_SUCCESS) { + // Access the simple value from the setting + res = this->setting->GetValue(hId, &curValue); + + if (res == ERROR_SUCCESS) { + res = this->setting->get_IsUpdating(&isUpdating); + + if (res == ERROR_SUCCESS) { + UINT it = 0; + UINT wait = timeout / retries; + + while (isUpdating == TRUE && res == ERROR_SUCCESS) { + System::Threading::Thread::Sleep(10); + res = this->setting->get_IsUpdating(&isUpdating); + + if (res != ERROR_SUCCESS) { + break; + } + } + + HSTRING otherStr = NULL; + res = WindowsCreateString(id.c_str(), static_cast(id.size()), &otherStr); + + curValue->Release(); + + // TODO: This second get is necessary for some settings, like + // "Collection" settings. For this ones the second get guarantees + // that the real value is the one received. + res = this->setting->GetValue(otherStr, &curValue); + if (res == ERROR_SUCCESS) { + item.Attach(curValue); + } else { + curValue->Release(); + } + } + } + } + + return res; +} + +HRESULT BaseSettingItem::SetValue(const wstring& id, ATL::CComPtr& item) { + if (this->setting == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + + HRESULT res { ERROR_SUCCESS }; + HSTRING hId { NULL }; + res = WindowsCreateString(id.c_str(), static_cast(id.size()), &hId); + + if (res == ERROR_SUCCESS) { + res = this->setting->SetValue(hId, static_cast(item)); + } + + WindowsDeleteString(hId); + + return res; +} + +UINT BaseSettingItem::GetProperty(wstring id, IInspectable** value) const { + if (this->setting == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + + HRESULT res = ERROR_SUCCESS; + HSTRING hId = NULL; + res = WindowsCreateString(id.c_str(), static_cast(id.size()), &hId); + + if (res == ERROR_SUCCESS) { + res = this->setting->GetProperty(hId, value); + } + + return res; +} + +UINT BaseSettingItem::SetProperty(wstring id, IInspectable* value) { + if (this->setting == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + + HRESULT res = ERROR_SUCCESS; + HSTRING hId = NULL; + res = WindowsCreateString(id.c_str(), static_cast(id.size()), &hId); + + if (res == ERROR_SUCCESS) { + res = this->setting->SetProperty(hId, value); + } + + return res; +} + +UINT BaseSettingItem::Invoke() { + if (this->setting == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + + IPropertyValueStatics* propValueFactory; + HSTRING rTimeClass; + WindowsCreateString( + RuntimeClass_Windows_Foundation_PropertyValue, + static_cast(wcslen(RuntimeClass_Windows_Foundation_PropertyValue)), + &rTimeClass + ); + HRESULT result = GetActivationFactory(rTimeClass, &propValueFactory); + + IInspectable* rec; + Rect baseRect { 0,0,0,0 }; + propValueFactory->CreateRect(baseRect, &rec); + + ICoreWindow* core = NULL; + ICoreWindowStatic* spCoreWindowStatic; + ICoreWindow* spCoreWindow; + ICoreWindowInterop* spCoreWindowInterop; + + HSTRING strIWindowClassId; + WindowsCreateString( + RuntimeClass_Windows_UI_Core_CoreWindow, + static_cast(wcslen(RuntimeClass_Windows_UI_Core_CoreWindow)), + &strIWindowClassId + ); + HRESULT hr; + + //Get the activation factory + hr = (Windows::Foundation::GetActivationFactory(strIWindowClassId, &spCoreWindowStatic)); + if (FAILED(hr)) return true; + + //Get the current thread's object + hr = spCoreWindowStatic->GetForCurrentThread(&spCoreWindow); + if (FAILED(hr)) return true; + + hr = this->setting->Invoke(spCoreWindow, rec); + + return hr; +} diff --git a/settingsHelper/SettingsHelperLib/BaseSettingItem.h b/settingsHelper/SettingsHelperLib/BaseSettingItem.h new file mode 100644 index 000000000..88d1bff19 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/BaseSettingItem.h @@ -0,0 +1,173 @@ +#pragma once + +#ifndef S__BASESETTINGITEM_H +#define S__BASESETTINGITEM_H + +#include "ISettingItem.h" + +#include +#include +#include + +#include +#include +#include + +using std::wstring; +using std::vector; +using std::pair; + +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; + +struct BaseSettingItem { +protected: + UINT timeout = 50; + UINT retries = 5; + +public: + + /// + /// The id of the setting being hold. + /// + wstring settingId {}; + /// + /// Pointer to the inner setting. + /// + ATL::CComPtr setting { NULL }; + + /// + /// Empty constructor. + /// + BaseSettingItem(); + /// + /// Constructs the BaseSettingItem using a pointer to IBaseSettingItem. + /// + /// + BaseSettingItem(wstring settingId, ATL::CComPtr BaseSettingItem); + /// + /// Copy constructor. + /// + /// The setting item to be copied. + BaseSettingItem(const BaseSettingItem& other); + /// + /// Copy assignment operator. + /// + BaseSettingItem& operator=(const BaseSettingItem& other); + /// + /// Copy constructor. + /// + /// The setting item to be copied. + /// + /// Destructor that frees the encapsulated IBaseSettingItem. + /// + /// ~BaseSettingItem(); + /// + /// Gets the IBaseSettingItem identifier. + /// + /// A reference to a wstring to be filled with the current setting Id. + /// An HRESULT error if the operation failed or ERROR_SUCCESS. + UINT GetId(std::wstring& id) const; + /// + /// Gets the current setting Type. + /// + /// A pointer to a SettingType to be filled with the current type. + /// An HRESULT error if the operation failed or ERROR_SUCCESS. + UINT GetSettingType(SettingType* val) const; + /// + /// Gets if the setting is set by a group policy. + /// + /// A pointer ot a bool to be filled with the current value. + /// An HRESULT error if the operation failed or ERROR_SUCCESS. + UINT GetIsSetByGroupPolicy(BOOL* val) const; + /// + /// Gets if the setting is set by a group policy. + /// + /// A pointer ot a bool to be filled with the current value. + /// An HRESULT error if the operation failed or ERROR_SUCCESS. + UINT GetIsEnabled(BOOL* val) const; + /// + /// Gets if the settings is applicable. + /// + /// A pointer ot a bool to be filled with the current value. + /// An HRESULT error if the operation failed or ERROR_SUCCESS. + UINT GetIsApplicable(BOOL* val) const; + /// + /// Gets a description of the setting. + /// + /// Note: This value can be null, if not, many time this value is used to + /// present a description of the setting in the UI. + /// + /// A reference to a wstring to be filled with the current setting description. + /// An HRESULT error if the operation failed or ERROR_SUCCESS. + UINT GetDescription(std::wstring& desc) const; + /// + /// Gets if the current setting value is being updated, if not, it means that + /// it already holds the correct value, and it's ready for a 'get operation'. + /// + /// A pointer ot a bool to be filled with the current value. + /// An HRESULT error if the operation failed or ERROR_SUCCESS. + UINT GetIsUpdating(BOOL* val) const; + /// + /// Gets the value of the current stored setting that matches with the supplied + /// identifier. + /// + /// The identifier of the setting to be retrieved, most of the + /// times this just "Value". + /// A pointer to a IInspectable* that will hold the current value, if the + /// operation doesn't succeed, "item" will be set to NULL. + /// + /// An HRESULT error if the operation failed or ERROR_SUCCESS. Possible errors: + /// - E_NOTIMPL: The setting doesn't have implemented the method; this can be + /// caused because the setting doesn't support this method, and doesn't contains + /// any value. + /// + UINT GetValue(wstring id, ATL::CComPtr& item); + /// + /// Sets the current value for the setting that matches the supplied identifier. + /// + /// The identifier of the setting to be set, most of the times this + /// just "Value". + /// A pointer to a IInspecable that holds the current value to be set, + /// most of the times this should be a IPropertyValue. + /// + /// An HRESULT error if the operation failed or ERROR_SUCCESS. Possible errors: + /// - TYPE_E_TYPEMISMATCH: The IPropertyValue supplied withing the 'item' parameter + /// is not the proper type for the setting. + /// - E_NOTIMPL: The setting doesn't have implemented the method; this can be + /// caused because the setting doesn't support this method, and doesn't contains + /// any value. + /// + HRESULT SetValue(const wstring& id, ATL::CComPtr& value); + /// + /// Gets the value of the current stored property that matches with the supplied + /// identifier. + /// + /// The identifier of the setting to be retrieved, most of the times this is + /// just "Value". + /// A pointer to a IInspectable* that will hold the current value, if the + /// operation doesn't succeed, "item" will be set to NULL. + /// An HRESULT error if the operation failed or ERROR_SUCCESS. + UINT GetProperty(wstring id, IInspectable** item) const; + /// + /// Sets the current value for the property that matches the supplied identifier. + /// + /// The identifier of the setting to be set, most of the times this is + /// just "Value". + /// A pointer to a IInspecable that holds the current value to be set, + /// most of the times this should be a IPropertyValue. + /// An HRESULT error if the operation failed or ERROR_SUCCESS. + UINT SetProperty(wstring id, IInspectable* item); + /// + /// In case of the setting being of "Action" kind, executes the action associated + /// with it. + /// + /// NOTE: Sometimes this action will have no effect, since it sometimes requires + /// a pointer to the ICoreWindow of the application that will display the action. + /// + /// An HRESULT error if the operation failed or ERROR_SUCCESS. + UINT Invoke(); +}; + + +#endif // S__BASESETTINGITEM_H \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/Constants.cpp b/settingsHelper/SettingsHelperLib/Constants.cpp new file mode 100644 index 000000000..abe785f7a --- /dev/null +++ b/settingsHelper/SettingsHelperLib/Constants.cpp @@ -0,0 +1,67 @@ +#include "stdafx.h" + +#include "Constants.h" + +namespace constants { + const wstring& BaseRegPath() { + static const wstring str { L"SOFTWARE\\Microsoft\\SystemSettings\\SettingId" }; + return str; + } + const wstring& BaseLibPath() { + static const wstring str { L"C:\\Windows\\System32\\SettingsHandlers_nt.dll" }; + return str; + } + const vector& KnownFaultyLibs() { + static const vector libPaths { + L"C:\\Windows\\System32\\SettingsHandlers_WorkAccess.dll" + }; + + return libPaths; + } + const map>& CoupledLibs() + { + static const map> map { + { + L"C:\\Windows\\System32\\SettingsHandlers_Display.dll", + { + L"C:\\Windows\\System32\\SettingsHandlers_PCDisplay.dll" + }, + }, + { + L"C:\\Windows\\System32\\SettingsHandlers_PCDisplay.dll", + { + L"C:\\Windows\\System32\\SettingsHandlers_Display.dll" + } + } + }; + + return map; + } + const vector& KnownFaultySettings() { + static const vector settingIds { + L"SystemSettings_Accessibility_Narrator_OffWithTouchHintText", + L"SystemSettings_BatterySaver_UsagePage_AppSource", + L"SystemSettings_BatterySaver_UsagePage_AppsBreakdown", + L"SystemSettings_Connections_Ethernet_Adapter_List_MUA", + L"SystemSettings_Connections_MobileBroadband_ConnectionProfileSelection", + L"SystemSettings_Connections_MobileBroadband_ESim_AddProfile", + L"SystemSettings_Display_Status_SaveError", + L"SystemSettings_Devices_RadialController_Add_CustomAppTool", + L"SystemSettings_FindMyDevice_Error_NoMSA", + L"SystemSettings_Gaming_BroadcastAudio_AutoEchoCancellation", + L"SystemSettings_Notifications_HideNotificationContent", + L"SystemSettings_Personalize_Color_ColorPrevalence", + L"SystemSettings_Personalize_Font_Advanced_Metadata", + L"SystemSettings_Personalize_Font_Uninstall", + L"SystemSettings_Personalize_Font_VariableFont_Instances", + // DLL index isn't updated, GetProcAddress fails returning an invalid address + L"SystemSettings_QuickActions_Launcher", + L"SystemSettings_Video_Preview_Calibration", + L"SystemSettings_Video_Preview_HDR", + L"SystemSettings_Video_Preview_SDR", + L"SystemSettings_XLinks_CPL_Display_Link" + }; + + return settingIds; + } +} \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/Constants.h b/settingsHelper/SettingsHelperLib/Constants.h new file mode 100644 index 000000000..83c04cc25 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/Constants.h @@ -0,0 +1,49 @@ +#pragma once + +#ifndef _SAPI_CONSTANTS_HL�AKJ +#define _SAPI_CONSTANTS_HL�AKJ + +#include +#include +#include + +#include + +using std::wstring; +using std::vector; +using std::map; + +namespace constants { + /// + /// Maximum length for a registry key name. + /// + const static UINT32 MAX_KEY_LENGTH { 255 }; + /// + /// Maximum length for a registry key value. + /// + const static UINT32 MAX_VALUE_NAME { 16383 }; + /// + /// Base registry key that holds all the settings ids. + /// + const wstring& BaseRegPath(); + /// + /// Path to the base library that needs to be loaded in order to get the API proper + /// functionality. + /// + const wstring& BaseLibPath(); + /// + /// List of known settings that doesn't work in the target platform. + /// + const vector& KnownFaultySettings(); + /// + /// List of known libs that present problems for being loaded. + /// + const vector& KnownFaultyLibs(); + /// + /// This libraries are dependent and should be loaded toguether in order for + /// proper operation and later unloading. + /// + const map>& CoupledLibs(); +} + +#endif // _SAPI_CONSTANTS \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/DbSettingItem.cpp b/settingsHelper/SettingsHelperLib/DbSettingItem.cpp new file mode 100644 index 000000000..dc09f9be4 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/DbSettingItem.cpp @@ -0,0 +1,5 @@ +#include "stdafx.h" + +#include "DbSettingItem.h" + +DbSettingItem::DbSettingItem() : BaseSettingItem::BaseSettingItem() {}; \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/DbSettingItem.h b/settingsHelper/SettingsHelperLib/DbSettingItem.h new file mode 100644 index 000000000..9d78c732c --- /dev/null +++ b/settingsHelper/SettingsHelperLib/DbSettingItem.h @@ -0,0 +1,14 @@ +#pragma once + +#ifndef S__IDBSETTINGITEM_H +#define S__IDBSETTINGITEM_H + +#include "BaseSettingItem.h" + +struct DbSettingItem : public BaseSettingItem { + using BaseSettingItem::BaseSettingItem; + + DbSettingItem(); +}; + +#endif // S__IDBSETTINGITEM_H \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/DynamicSettingDatabase.cpp b/settingsHelper/SettingsHelperLib/DynamicSettingDatabase.cpp new file mode 100644 index 000000000..cb496a70e --- /dev/null +++ b/settingsHelper/SettingsHelperLib/DynamicSettingDatabase.cpp @@ -0,0 +1,193 @@ +#include "stdafx.h" +#include "DynamicSettingsDatabase.h" + +#include + +using std::pair; + +const vector>> supportedDynamicSettings { + // SystemSettings.Notifications.QuietMomentsDynamicDatabase + { + L"SystemSettings_QuietMoments_On_Scheduled_Mode", + { + L"SystemSettings_QuietMoments_Scheduled_Mode_Enabled", + L"SystemSettings_QuietMoments_Scheduled_Mode_StartTime", + L"SystemSettings_QuietMoments_Scheduled_Mode_EndTime", + L"SystemSettings_QuietMoments_Scheduled_Mode_Frequency", + L"SystemSettings_QuietMoments_Scheduled_Mode_Profile", + L"SystemSettings_QuietMoments_Scheduled_Mode_ShouldShowNotification" + }, + }, + { + L"SystemSettings_QuietMoments_On_Full_Screen_Mode", + { + L"SystemSettings_QuietMoments_Presentation_Mode_Enabled", + L"SystemSettings_QuietMoments_Presentation_Mode_Profile", + L"SystemSettings_QuietMoments_Presentation_Mode_ShouldShowNotification" + }, + }, + { + L"SystemSettings_QuietMoments_On_Game_Mode", + { + L"SystemSettings_QuietMoments_Presentation_Mode_Enabled", + L"SystemSettings_QuietMoments_Presentation_Mode_Profile", + L"SystemSettings_QuietMoments_Presentation_Mode_ShouldShowNotification" + } + }, + { + L"SystemSettings_QuietMoments_On_Home_Mode", + { + L"SystemSettings_QuietMoments_Home_Mode_Enabled", + L"SystemSettings_QuietMoments_Home_Mode_Profile", + L"SystemSettings_QuietMoments_Home_Mode_ShouldShowNotification", + L"SystemSettings_QuietMoments_Home_Mode_ShouldShowNotification", + L"SystemSettings_QuietMoments_Home_Mode_ChangeAddress" + } + }, + { + L"SystemSettings_QuietMoments_On_Presentation_Mode", + { + L"SystemSettings_QuietMoments_Presentation_Mode_Enabled", + L"SystemSettings_QuietMoments_Presentation_Mode_Profile", + L"SystemSettings_QuietMoments_Presentation_Mode_ShouldShowNotification" + } + }, + { + L"SystemSettings_QuietMoments_On_Full_Screen_Mode", + { + L"SystemSettings_QuietMoments_Full_Screen_Mode_Enabled", + L"SystemSettings_QuietMoments_Full_Screen_Mode_Profile", + L"SystemSettings_QuietMoments_Full_Screen_Mode_ShouldShowNotification" + } + }, + // SystemSettings.NotificationsDataModel.AppSettingsDynamicDatabase + { + // Settings present in every element inside the settings collection + L"SystemSettings_Notifications_AppList", + { + L"SystemSettings_Notifications_AppNotificationKeepContentAboveLockPrivate", + L"SystemSettings_Notifications_AppNotificationBanners", + L"SystemSettings_Notifications_AppNotificationLed", + L"SystemSettings_Notifications_AppNotificationMaxCollapsedGroupItemCountSetting", + L"SystemSettings_Notifications_TopPriorityCommandSetting", + L"SystemSettings_Notifications_AppNotificationPrioritizationSetting", + L"SystemSettings_Notifications_AppShowNotificationsInActionCenter", + L"SystemSettings_Notifications_AppNotificationSoundToggle", + L"SystemSettings_Notifications_AppNotifications", + L"SystemSettings_Notifications_AppNotificationVibrate" + } + }, + { + // TODO: Check 'kind', collection? + L"SystemSettings_Notifications_QuietHours_Profile", + { + L"SystemSettings_Notifications_QuietHoursProfile_AddApp", + L"SystemSettings_Notifications_QuietHoursProfile_AddPeople", + L"SystemSettings_Notifications_QuietHoursProfile_AllowedApps", + L"SystemSettings_Notifications_QuietHoursProfile_AllowedPeople", + L"SystemSettings_Notifications_QuietHoursProfile_AllowAllCalls_CortanaEnabled", + L"SystemSettings_Notifications_QuietHoursProfile_AllowAllCalls_CortanaDisabled", + L"SystemSettings_Notifications_QuietHoursProfile_AllowAllPeople", + L"SystemSettings_Notifications_QuietHoursProfile_AllowAllReminders", + L"SystemSettings_Notifications_QuietHoursProfile_AllowAllTexts", + L"SystemSettings_Notifications_QuietHoursProfile_AllowRepeatCalls", + L"SystemSettings_Notifications_QuietHoursProfile_Subtitle" + } + } +}; + +DynamicSettingDatabase::DynamicSettingDatabase(wstring dbSettingsName, ATL::CComPtr settingDatabase) + : _settingDatabase(settingDatabase), dbSettingsName(dbSettingsName) {} + + +BOOL isSupportedDb(const wstring& settingId) { + BOOL result { false }; + + for (const auto& supportedDb : supportedDynamicSettings) { + if (supportedDb.first == settingId) { + result = true; + } + } + + return result; +} + +HRESULT getSupportedDbSettings(const DynamicSettingDatabase& database, vector& settingIds) { + HRESULT result { ERROR_NOT_SUPPORTED }; + + for (const auto& elem : supportedDynamicSettings) { + if (elem.first == database.dbSettingsName) { + settingIds = elem.second; + result = ERROR_SUCCESS; + } + } + + return result; +} + +HRESULT loadSettingDatabase(const wstring& settingId, SettingItem& settingItem, DynamicSettingDatabase& _dynSettingDatabase) { + HRESULT res { ERROR_SUCCESS }; + wstring propId { L"DynamicSettingsDatabaseValue" }; + + if (isSupportedDb(settingId)) { + ATL::CComPtr propSettingDatabase = NULL; + res = settingItem.GetProperty(propId, reinterpret_cast(&propSettingDatabase)); + + if (res == ERROR_SUCCESS) { + DynamicSettingDatabase dynSettingDatabase {settingId, propSettingDatabase}; + _dynSettingDatabase = dynSettingDatabase; + } else if (res == E_NOTIMPL) { + ATL::CComPtr iValSettingDatabase = NULL; + res = settingItem.GetValue(propId, iValSettingDatabase); + + if (res == ERROR_SUCCESS) { + // Set the IDynamicSettingsDatabase with the queried value + ATL::CComPtr valSettingDatabase { + reinterpret_cast(iValSettingDatabase.Detach()) + }; + DynamicSettingDatabase dynSettingDatabase { settingId, valSettingDatabase }; + _dynSettingDatabase = dynSettingDatabase; + } + } else { + // There is no other way of accessing the inner database + // the requested database shouldn't exist + res = E_INVALIDARG; + } + } else { + res = ERROR_NOT_SUPPORTED; + } + + return res; +} + +HRESULT DynamicSettingDatabase::GetDatabaseSettings(vector& dbSettings) const { + HRESULT res { ERROR_SUCCESS }; + vector dbSettingsIds {}; + vector _dbSettings {}; + + res = getSupportedDbSettings(*this, dbSettingsIds); + + if (res == ERROR_SUCCESS) { + for (const auto& settingId : dbSettingsIds) { + ATL::CComPtr pSettingItem = NULL; + HSTRING hSettingId = NULL; + + WindowsCreateString(settingId.c_str(), static_cast(settingId.size()), &hSettingId); + res = this->_settingDatabase->GetSetting(hSettingId, &pSettingItem); + + if (res == ERROR_SUCCESS) { + DbSettingItem setting { settingId, pSettingItem }; + _dbSettings.push_back(setting); + } + + WindowsDeleteString(hSettingId); + } + } + + if (res == ERROR_SUCCESS) { + dbSettings = _dbSettings; + } + + return res; +} + diff --git a/settingsHelper/SettingsHelperLib/DynamicSettingsDatabase.h b/settingsHelper/SettingsHelperLib/DynamicSettingsDatabase.h new file mode 100644 index 000000000..7520b9c1b --- /dev/null +++ b/settingsHelper/SettingsHelperLib/DynamicSettingsDatabase.h @@ -0,0 +1,33 @@ +#pragma once + +#ifndef S__DYNAMICSETTINGSDATABASE_H +#define S__DYNAMICSETTINGSDATABASE_H + +#include "IDynamicSettingsDatabase.h" +#include "SettingItem.h" + +#include +#include +#include + +using std::wstring; +using std::vector; + +struct DynamicSettingDatabase { +private: + ATL::CComPtr _settingDatabase; + +public: + wstring dbSettingsName {}; + + DynamicSettingDatabase() {} + DynamicSettingDatabase(wstring dbSettingsName, ATL::CComPtr settingDatabase); + + HRESULT GetDatabaseSettings(vector& settings) const; +}; + +BOOL isSupportedDb(const wstring& settingId); +HRESULT loadSettingDatabase(const wstring& settingId, SettingItem& settingItem, DynamicSettingDatabase& dynSettingDatabase); +HRESULT getSupportedDbSettings(const DynamicSettingDatabase& database, vector& settingIds); + +#endif // S__DYNAMICSETTINGSDATABASE_H diff --git a/settingsHelper/SettingsHelperLib/IDynamicSettingsDatabase.h b/settingsHelper/SettingsHelperLib/IDynamicSettingsDatabase.h new file mode 100644 index 000000000..eba1722b5 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/IDynamicSettingsDatabase.h @@ -0,0 +1,18 @@ +#pragma once + +#ifndef I__IDynamicSettingsDatabase +#define I__IDynamicSettingsDatabase + +#include "ISettingItem.h" + +#include +#include + +// SystemSettings::NotificationsDataModel::AppSettingsDynamicDatabase::GetSetting(struct HSTRING__ *,struct SystemSettings::DataModel::ISettingItem * *) + +__interface __declspec(uuid("2bea7562-66a9-47a1-aebd-aa188e3a1b57")) +IDynamicSettingsDatabase : public IInspectable { + HRESULT GetSetting(HSTRING id, ISettingItem** setting); +}; + +#endif // !I__IDynamicSettingsDatabase diff --git a/settingsHelper/SettingsHelperLib/IPropertyValueUtils.cpp b/settingsHelper/SettingsHelperLib/IPropertyValueUtils.cpp new file mode 100644 index 000000000..e6f4cfc39 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/IPropertyValueUtils.cpp @@ -0,0 +1,308 @@ +#include "stdafx.h" + +#include "IPropertyValueUtils.h" +#include "StringConversion.h" + +#include + +#pragma comment (lib, "WindowsApp.lib") + +using std::wstring; + +HRESULT createPropertyValue(const VARIANT& value, ATL::CComPtr& rValue) { + HRESULT res = { ERROR_SUCCESS }; + IPropertyValueStatics* propValueFactory = NULL; + HSTRING rTimeClass = NULL; + + // IPropertyValue to be created + IPropertyValue* cPropValue = NULL; + + res = WindowsCreateString( + RuntimeClass_Windows_Foundation_PropertyValue, + static_cast(wcslen(RuntimeClass_Windows_Foundation_PropertyValue)), + &rTimeClass + ); + if (res != ERROR_SUCCESS) { goto cleanup; } + res = GetActivationFactory(rTimeClass, &propValueFactory); + if (res != ERROR_SUCCESS) { goto cleanup; } + + if (value.vt == VARENUM::VT_BOOL) { + res = propValueFactory->CreateBoolean(static_cast(value.boolVal), reinterpret_cast(&cPropValue)); + } else if (value.vt == VARENUM::VT_BSTR) { + BSTR bStrValue = value.bstrVal; + BOOL processed = false; + + try { + System::String^ timeSpanStr = gcnew System::String(bStrValue); + System::Globalization::CultureInfo^ culture = gcnew System::Globalization::CultureInfo { L"en-US" }; + System::TimeSpan^ timeSpan = System::TimeSpan::Parse(timeSpanStr, culture); + + // Get a representation that can be stored in a IPropertyValue + UINT64 duration = timeSpan->Ticks; + ABI::Windows::Foundation::TimeSpan abiTimeSpan; + abiTimeSpan.Duration = duration; + + propValueFactory->CreateTimeSpan(abiTimeSpan, reinterpret_cast(&cPropValue)); + + // Set the processed flag + processed = true; + } catch (System::Exception^) {} + + if (!processed) { + try { + // If it's not a TimeSpan, check if it's a Date + System::String^ timeDateStr = gcnew System::String(bStrValue); + System::Globalization::CultureInfo^ culture = gcnew System::Globalization::CultureInfo { L"en-US" }; + + System::DateTime^ dateTime = System::DateTime::Parse(timeDateStr, culture, System::Globalization::DateTimeStyles::AssumeUniversal); + ABI::Windows::Foundation::DateTime abiDateTime; + abiDateTime.UniversalTime = dateTime->Ticks; + + propValueFactory->CreateDateTime(abiDateTime, reinterpret_cast(&cPropValue)); + // Set the processed flag + processed = true; + } catch (System::Exception^) {} + } + + if (!processed) { + HSTRING newHValue = NULL; + res = WindowsCreateString(bStrValue, static_cast(wcslen(bStrValue)), &newHValue); + + if (res == ERROR_SUCCESS) { + res = propValueFactory->CreateString(newHValue, reinterpret_cast(&cPropValue)); + } + WindowsDeleteString(newHValue); + } + + } else if (value.vt == VARENUM::VT_UINT) { + res = propValueFactory->CreateUInt32(value.uintVal, reinterpret_cast(&cPropValue)); + } else if (value.vt == VARENUM::VT_R8) { + res = propValueFactory->CreateDouble(value.dblVal, reinterpret_cast(&cPropValue)); + } else if (value.vt == VARENUM::VT_I8) { + res = propValueFactory->CreateInt64(value.llVal, reinterpret_cast(&cPropValue)); + } else if (value.vt == VARENUM::VT_EMPTY) { + res = propValueFactory->CreateEmpty(reinterpret_cast(&cPropValue)); + } else { + res = E_INVALIDARG; + } + + if (res == ERROR_SUCCESS) { + rValue.Attach(cPropValue); + } + +cleanup: + if (rTimeClass != NULL) { WindowsDeleteString(rTimeClass); } + if (propValueFactory != NULL) { propValueFactory->Release(); } + + return res; +} + +HRESULT createValueVariant(const wstring& value, PropertyType type, VARIANT& rVariant) { + if (value == L"" || type == PropertyType::PropertyType_Empty) { return E_INVALIDARG; } + + HRESULT res { ERROR_SUCCESS }; + + bool stringType = + type == PropertyType::PropertyType_String || + type == PropertyType::PropertyType_DateTime || + type == PropertyType::PropertyType_TimeSpan; + + if (type == PropertyType::PropertyType_Boolean) { + boolean varValue = false; + + // TODO: Check if this operation can except + if (value == L"true" || value == L"false") { + rVariant.vt = VARENUM::VT_BOOL; + + if (value == L"true") { + rVariant.boolVal = true; + } else { + rVariant.boolVal = false; + } + } else { + res = E_INVALIDARG; + } + } else if (type == PropertyType::PropertyType_Double) { + DOUBLE propValue { 0 }; + const wchar_t* strStart = value.c_str(); + wchar_t* strEnd = NULL; + + propValue = wcstod(strStart, &strEnd); + + if (errno == ERANGE || (value != L"0" && propValue == 0)) { + res = E_INVALIDARG; + } else { + rVariant.vt = VARENUM::VT_R8; + rVariant.dblVal = propValue; + } + } else if (type == PropertyType::PropertyType_Int64) { + INT64 propValue { 0 }; + const wchar_t* strStart = value.c_str(); + std::size_t strPos = NULL; + + try { + propValue = std::stoll(strStart, &strPos); + } catch (std::invalid_argument) { + res = E_INVALIDARG; + } catch (std::out_of_range) { + res = E_INVALIDARG; + } + + if (res == ERROR_SUCCESS) { + rVariant.vt = VARENUM::VT_I8; + rVariant.llVal = propValue; + } + } else if (stringType) { + wchar_t* valueStr = _wcsdup(value.c_str()); + + rVariant.vt = VARENUM::VT_BSTR; + rVariant.bstrVal = valueStr; + } else { + res = E_INVALIDARG; + } + + return res; +} + +HRESULT equals(ATL::CComPtr fstProp, ATL::CComPtr sndProp, BOOL& rResult) { + if (fstProp == NULL || sndProp == NULL) { return false; } + + PropertyType fstPropType { PropertyType::PropertyType_Empty }; + PropertyType sndPropType { PropertyType::PropertyType_Empty }; + + HRESULT errCode { ERROR_SUCCESS }; + BOOL res { false }; + + errCode = fstProp->get_Type(&fstPropType); + + if (errCode == ERROR_SUCCESS) { + errCode = sndProp->get_Type(&sndPropType); + + bool nonEmptyArgs = + fstPropType != PropertyType::PropertyType_Empty && + sndPropType != PropertyType::PropertyType_Empty; + + if ((nonEmptyArgs || fstPropType == sndPropType) && errCode == ERROR_SUCCESS) { + if (fstPropType == PropertyType::PropertyType_Boolean) { + boolean fstPropVal { false }; + boolean sndPropVal { false }; + + errCode = fstProp->GetBoolean(&fstPropVal); + + if (errCode == ERROR_SUCCESS) { + errCode = sndProp->GetBoolean(&sndPropVal); + + if (errCode == ERROR_SUCCESS) { + res = fstPropVal == sndPropVal; + } + } + } else if (fstPropType == PropertyType::PropertyType_Double) { + DOUBLE fstPropVal {}; + DOUBLE sndPropVal {}; + + errCode = fstProp->GetDouble(&fstPropVal); + + if (errCode == ERROR_SUCCESS) { + errCode = sndProp->GetDouble(&sndPropVal); + + if (errCode == ERROR_SUCCESS) { + res = fstPropVal == sndPropVal; + } + } + } else if (fstPropType == PropertyType::PropertyType_Int64) { + INT64 fstPropVal {}; + INT64 sndPropVal {}; + + errCode = fstProp->GetInt64(&fstPropVal); + + if (errCode == ERROR_SUCCESS) { + errCode = sndProp->GetInt64(&sndPropVal); + + if (errCode == ERROR_SUCCESS) { + res = fstPropVal == sndPropVal; + } + } + } else if (fstPropType == PropertyType::PropertyType_Int32) { + INT32 fstPropVal {}; + INT32 sndPropVal {}; + + errCode = fstProp->GetInt32(&fstPropVal); + + if (errCode == ERROR_SUCCESS) { + errCode = sndProp->GetInt32(&sndPropVal); + + if (errCode == ERROR_SUCCESS) { + res = fstPropVal == sndPropVal; + } + } + } else if (fstPropType == PropertyType::PropertyType_UInt64) { + UINT64 fstPropVal {}; + UINT64 sndPropVal {}; + + errCode = fstProp->GetUInt64(&fstPropVal); + + if (errCode == ERROR_SUCCESS) { + errCode = sndProp->GetUInt64(&sndPropVal); + + if (errCode == ERROR_SUCCESS) { + res = fstPropVal == sndPropVal; + } + } + } else if (fstPropType == PropertyType::PropertyType_UInt32) { + UINT32 fstPropVal {}; + UINT32 sndPropVal {}; + + errCode = fstProp->GetUInt32(&fstPropVal); + + if (errCode == ERROR_SUCCESS) { + errCode = sndProp->GetUInt32(&sndPropVal); + + if (errCode == ERROR_SUCCESS) { + res = fstPropVal == sndPropVal; + } + } + } else if (fstPropType == PropertyType::PropertyType_DateTime) { + DateTime fstPropVal {}; + DateTime sndPropVal {}; + + errCode = fstProp->GetDateTime(&fstPropVal); + + if (errCode == ERROR_SUCCESS) { + try { + System::DateTime^ fstDateTime = gcnew System::DateTime(fstPropVal.UniversalTime); + System::DateTime^ sndDateTime = gcnew System::DateTime(sndPropVal.UniversalTime); + + res = fstDateTime->Equals(sndDateTime); + } catch (System::Exception^) { + errCode = E_INVALIDARG; + } + } + } else if (fstPropType == PropertyType::PropertyType_TimeSpan) { + TimeSpan fstPropVal {}; + TimeSpan sndPropVal {}; + + errCode = fstProp->GetTimeSpan(&fstPropVal); + + if (errCode == ERROR_SUCCESS) { + try { + System::TimeSpan^ fstTimeSpan = gcnew System::TimeSpan(fstPropVal.Duration); + System::TimeSpan^ sndTimeSpan = gcnew System::TimeSpan(sndPropVal.Duration); + + res = fstTimeSpan->Equals(sndTimeSpan); + } catch (System::Exception^) { + errCode = E_INVALIDARG; + } + } + } else { + // TODO: Improve error code + errCode = E_NOTIMPL; + } + } + } + + if (errCode == ERROR_SUCCESS) { + rResult = res; + } + + return errCode; +} diff --git a/settingsHelper/SettingsHelperLib/IPropertyValueUtils.h b/settingsHelper/SettingsHelperLib/IPropertyValueUtils.h new file mode 100644 index 000000000..bf0a6385e --- /dev/null +++ b/settingsHelper/SettingsHelperLib/IPropertyValueUtils.h @@ -0,0 +1,56 @@ +#pragma once + +#ifndef F__IPropertyValueUtils_H +#define F__IPropertyValueUtils_H + +#include +#include + +#include + +using namespace ABI::Windows::Foundation; +using std::wstring; + +/// +/// Create an IPropertyValue from a VARIANT. +/// +/// The VARIANT from which the IPropertyValue is going to be created. +/// A reference to an IProperty value to be filled with the new created one. +/// +/// ERROR_SUCCESS in case of success or one of the following error codes: +/// - E_OUTOFMEMORY from failed string creation or failed IPropertyValue creation. +/// - REGDB_E_CLASSNOTREG from failed activation factory. +/// - E_INVALIDARG the supplied variant value isn't supported to be converted into a IPropertyValue. +/// +HRESULT createPropertyValue(const VARIANT& value, ATL::CComPtr& rValue); +/// +/// Create a VARIANT holding the value contained in the string 'value' parameter, +/// after converting it to the target 'PropertyType' supplied in the parameter 'type'. +/// +/// +/// The string value to be converted into the specified target 'PropertyType'. +/// +/// +/// The target type for the parameter 'value' to be converted into. +/// +/// +/// A reference to the resulting VARIANT to be filled. +/// +/// +/// ERROR_SUCCESS in case of success or E_INVALIDARG if the conversion can't be performed. +/// +HRESULT createValueVariant(const wstring& value, PropertyType type, VARIANT& rVariant); +/// +/// Compares if two IPropertyValue values are equal. +/// +/// The first IPropertyValue to be compared. +/// The second IPropertyValue to be compared. +/// A reference to a bool containing the result of the operation. +/// ERROR_SUCCESS in case of success or one of the following error codes: +/// - E_INVALIDARG if one of the IPropertyValues isn't properly initialized +/// its contents can't be retrieved. +/// - E_NOTIMPL if the comparison if requested for non-supported IPropertyValue contents. +/// +HRESULT equals(ATL::CComPtr fstProp, ATL::CComPtr sndProp, BOOL& result); + +#endif // S__IPropertyValueUtils_H \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/ISettingItem.h b/settingsHelper/SettingsHelperLib/ISettingItem.h new file mode 100644 index 000000000..97ff4b509 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/ISettingItem.h @@ -0,0 +1,77 @@ +#pragma once + +#ifndef I__ISettingItem_H +#define I__ISettingItem_H + +#include +#include +#include + +/** + * RESEARCH DATA: This is the RAW pointer table found for the interface ISettingItem + * ============================================================================ + * + * 6 -> SystemSettings::DataModel::CSettingBase::get_Id(struct HSTRING__ * *) + * 7 -> SettingsHandlers_Notifications.dll!SystemSettings::DataModel::CSettingBase::get_Type(enum SystemSettings::DataModel::SettingType *) + * 9 -> SettingsHandlers_Notifications.dll!SystemSettings::DataModel::CSettingBase::get_IsEnabled(unsigned char *) + * 13 -> SystemSettings::DataModel::CDataSettingBase::GetValue(struct HSTRING__ *, struct IInspectable * *) + * 14 -> SystemSettings::NotificationsDataModel::CustomizableQuickActions::SetValue(struct HSTRING__ *, struct IInspectable *) + * 15 -> SystemSettings::DataModel::CSettingBase::GetProperty(struct HSTRING__ *, struct IInspectable * *) + * 16 -> SystemSettings::DataModel::CSettingBase::SetProperty(struct HSTRING__ *, struct IInspectable *) + * 17 -> SystemSettings::DataModel::CSettingBase::Invoke(struct Windows::UI::Core::ICoreWindow *,struct Windows::Foundation::Rect) + * 18 -> SystemSettings::DataModel::CSettingBase::add_SettingChanged(struct Windows::Foundation::ITypedEventHandler *, struct EventRegistrationToken *) + * 19 -> SystemSettings::DataModel::CSettingBase::remove_SettingChanged(struct EventRegistrationToken) + * 20 -> SystemSettings::DataModel::CGetValueAsyncHelper::`vector deleting destructor'(unsigned int) + * 21 -> SystemSettings::DataModel::CSettingBase::SetIsUpdating(unsigned char) + */ + +/// +/// Represents different kind of settings. +/// +enum class SettingType { + Empty = -1, + Custom = 0, + + // Read-only + DisplayString = 1, + LabeledString = 2, + + // Values (use GetValue/SetValue) + Boolean = 3, + Range = 4, + String = 5, + List = 6, + + // Perform the setting action + Action = 7, + + // Is a ISettingCollection + SettingCollection = 8 +}; + +/// +/// Raw interface representing a Setting, should not be directly accessed. +/// +__interface __declspec(uuid("40C037CC-D8BF-489E-8697-D66BAA3221BF")) +ISettingItem : public IInspectable { +public: + int get_Id(HSTRING* id); + int get_SettingType(SettingType* val); + int get_IsSetByGroupPolicy(BOOL* val); + int get_IsEnabled(BOOL* val); + int get_IsApplicable(BOOL* val); + int get_Description(HSTRING* desc); + + int get_IsUpdating(BOOL* val); + int GetValue(HSTRING__* name, IInspectable** item); + HRESULT SetValue(HSTRING__* name, IInspectable* item); + + int GetProperty(HSTRING__*, IInspectable**); + int SetProperty(HSTRING__*, IInspectable*); + + int Invoke(ABI::Windows::UI::Core::ICoreWindow* wnd, IInspectable* rect); + int add_SettingChanged(ABI::Windows::Foundation::ITypedEventHandler* eventHnd, EventRegistrationToken* token); + int remove_SettingChanged(EventRegistrationToken token); +}; + +#endif // !I__ISettingItem_H diff --git a/settingsHelper/SettingsHelperLib/ISettingsCollection.h b/settingsHelper/SettingsHelperLib/ISettingsCollection.h new file mode 100644 index 000000000..91b1476f0 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/ISettingsCollection.h @@ -0,0 +1,62 @@ +#pragma once + +#include "ISettingItem.h" + +/** + * RESEARCH DATA: This is the RAW pointer table found for the interface ISettingsCollection + * ============================================================================ + * + * 22 -> SystemSettings::DataModel::CSettingBase::GetInitializationResult(void) + * 23 -> SystemSettings::DataModel::CDataSettingBase::DoGenericAsyncWork(void) + * 24 -> SystemSettings::DataModel::CGenericAsyncHelper::StartGenericAsyncWork(class SystemSettings::DataModel::CSettingBase *) + * 25 -> SystemSettings::DataModel::CGenericAsyncHelper::SetSkipConcurrentOperations(bool) + * 26 -> SystemSettings::NotificationsDataModel::NotificationsAppList::GetValue(struct IInspectable * *) + * 27 -> SystemSettings::DataModel::CDataSettingBase::SetValue(bool) + * 28 -> SystemSettings::DataModel::CDataSettingBase::SetValue(struct HSTRING__ *) + * 29 -> SystemSettings::DataModel::CDataSettingBase::SetValue(struct Windows::Foundation::IPropertyValue *) + * 30 -> SystemSettings::NotificationsDataModel::NotificationsAppList::GetNamedValue(struct HSTRING__ *,struct IInspectable * *) + * 31 -> SystemSettings::DataModel::CDataSettingBase::SetNullValue(void) + * 32 -> SystemSettings::DataModel::CDataSettingBase::SetNullValueForUser(struct HSTRING__ *) + * 33 -> SystemSettings::NotificationsDataModel::NotificationsAppList::GetSettingsCollection(void) + * 34 -> SystemSettings::NotificationsDataModel::NotificationsAppList::DoGetValueAsyncWork(void) + * 35 -> SystemSettings::DataModel::CGetValueAsyncHelper >::StartGetValueAsyncWork(class SystemSettings::DataModel::CSettingBase *) + * 36 -> SystemSettings::DataModel::CGetValueAsyncHelper::StartGetValueAsyncWork(unsigned short const *,class SystemSettings::DataModel::CSettingBase *) + * 37 -> SystemSettings::DataModel::CGetValueAsyncHelper::ResetValueQueried(void) + */ + +/// +/// This is the RAW interface obtained representing the settings collections, +/// this interface should not be accessed directly. +/// +__interface __declspec(uuid("62948522-8857-4C63-9B85-8FFF20128F6A")) +ISettingsCollection : ISettingItem { + int GetInitializationResult(void); + int GetInitializationResult1(void); + int DoGenericAsyncWork(void); + // Unknown -- Private method + int StartGenericAsyncWork(IInspectable*); + // ======= + + int SetSkipConcurrentOperations(bool); + int GetValue(IInspectable** item); + int SetValue(bool); + int SetValue(HSTRING); + int SetValue(ABI::Windows::Foundation::IPropertyValue*); + int SetNullValue(void); + int SetNullValueForUser(HSTRING); + + // Unknown + IInspectable* GetSettingsCollection(void); + IInspectable* GetSettingsCollection2(void); + // ======= + + int DoGetValueAsyncWork(void); + int DoGetValueAsyncWork2(void); + int DoGetValueAsyncWork3(void); + // Unknown -- Private method + int StartGetValueAsyncWork(IInspectable*); + int StartGetValueAsyncWork(unsigned short const *, IInspectable*); + // ======= + + int ResetValueQueried(void); +}; \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/Payload.cpp b/settingsHelper/SettingsHelperLib/Payload.cpp new file mode 100644 index 000000000..335a93bd0 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/Payload.cpp @@ -0,0 +1,531 @@ +#include "stdafx.h" +#include "Payload.h" +#include "IPropertyValueUtils.h" + +#include +#include +#include +#include +#include + +#include + +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::Data::Json; + +using std::pair; + +#pragma comment (lib, "WindowsApp.lib") + +// ----------------------------------------------------------------------------- +// Parameter +// ----------------------------------------------------------------------------- + +// --------------------------- Public --------------------------------------- + +Parameter::Parameter() {} + +Parameter::Parameter(pair> _objIdVal) : + oIdVal(_objIdVal), isObject(true), isEmpty(false) {} + +Parameter::Parameter(ATL::CComPtr _iPropVal) : + iPropVal(_iPropVal), isObject(false), isEmpty(false) {} + +// ----------------------------------------------------------------------------- +// Action +// ----------------------------------------------------------------------------- + +/// +/// Check that the members with which an action is going to be constructed are +/// valid. +/// +/// The method for the action. +/// The parameters for the action +/// +/// ERROR_SUCCESS in case of success or E_INVALIDARG in case of invalid +/// action members. +/// +HRESULT checkActionMembers(wstring method, vector params) { + if (method != L"GetValue" && method != L"SetValue") { + return E_INVALIDARG; + } + + HRESULT errCode { ERROR_SUCCESS }; + + if (params.size() > 1) { + for (const auto& param : params) { + bool validParam = + param.isEmpty == false && + param.isObject == true && + param.oIdVal.first.empty() == false; + + if (validParam == false) { + errCode = E_INVALIDARG; + break; + } + } + } + + return errCode; +} + +/// +/// Helper function to create an action that ensure that it doens't contradict +/// the expected payload format. +/// +/// The action target setting id. +/// The action target method. +/// The action parameters +/// A reference to the action to be filled. +/// +/// ERROR_SUCCESS in case of success or E_INVALIDARG in case of parameters +/// not passing format checking. +/// +HRESULT createAction(wstring sId, wstring sMethod, vector params, Action& rAction) { + HRESULT errCode { checkActionMembers(sMethod, params) }; + + if (errCode == ERROR_SUCCESS) { + rAction = Action { sId, sMethod, params }; + } + + return errCode; +} + +// ----------------------------------------------------------------------------- +// Parsing & Serialization Functions +// ----------------------------------------------------------------------------- + +// --------------------------- Parsing -------------------------------------- + +enum class OpType { + Get, + Set +}; + +HRESULT getValueParam(ATL::CComPtr jValue, ATL::CComPtr& jPropVal) { + HRESULT res = ERROR_SUCCESS; + + ATL::CComPtr pValueFactory; + HSTRING rTimeClass = NULL; + JsonValueType jElemValueType = JsonValueType::JsonValueType_Null; + + res = WindowsCreateString( + RuntimeClass_Windows_Foundation_PropertyValue, + static_cast(wcslen(RuntimeClass_Windows_Foundation_PropertyValue)), + &rTimeClass + ); + + if (res != ERROR_SUCCESS) goto cleanup; + res = GetActivationFactory(rTimeClass, &pValueFactory); + if (res != ERROR_SUCCESS) goto cleanup; + + res = jValue->get_ValueType(&jElemValueType); + if (res != ERROR_SUCCESS) goto cleanup; + + if (jElemValueType == JsonValueType::JsonValueType_Boolean) { + boolean elemValue; + ATL::CComPtr value; + res = jValue->GetBoolean(&elemValue); + + if (res == ERROR_SUCCESS) { + res = pValueFactory->CreateBoolean(elemValue, reinterpret_cast(&value)); + if (res == ERROR_SUCCESS) { + jPropVal = value; + } + } + } else if (jElemValueType == JsonValueType::JsonValueType_String) { + HSTRING elemValue; + ATL::CComPtr value; + res = jValue->GetString(&elemValue); + + if (res == ERROR_SUCCESS) { + UINT32 bufSize { 0 }; + PCWSTR bufWSTR { WindowsGetStringRawBuffer(elemValue, &bufSize) }; + wstring elemValueStr { bufWSTR, bufSize }; + + VARIANT vElemVal; + vElemVal.vt = VARENUM::VT_BSTR; + vElemVal.bstrVal = const_cast( elemValueStr.c_str() ); + + res = createPropertyValue(vElemVal, value); + if (res == ERROR_SUCCESS) { + jPropVal = value; + } + } + } else if (jElemValueType == JsonValueType::JsonValueType_Number) { + DOUBLE elemValue; + ATL::CComPtr value; + res = jValue->GetNumber(&elemValue); + + if (res == ERROR_SUCCESS) { + res = pValueFactory->CreateDouble(elemValue, reinterpret_cast(&value)); + if (res == ERROR_SUCCESS) { + jPropVal = value; + } + } + } else { + res = E_INVALIDARG; + } + +cleanup: + + return res; +} + +HRESULT getObjectParam(OpType opType, ATL::CComPtr jObject, Parameter& param) { + HRESULT res = ERROR_SUCCESS; + + // Tags + HSTRING hElemIdTag = NULL; + PCWSTR pElemIdTag = L"elemId"; + HSTRING hElemValTag = NULL; + PCWSTR pElemValTag = L"elemVal"; + + // Values + UINT32 hElemIdSize = 0; + HSTRING hElemId = NULL; + ATL::CComPtr jElemVal; + ATL::CComPtr pElemVal; + + res = WindowsCreateString(pElemIdTag, static_cast(wcslen(pElemIdTag)), &hElemIdTag); + if (res != ERROR_SUCCESS) goto cleanup; + res = WindowsCreateString(pElemValTag, static_cast(wcslen(pElemValTag)), &hElemValTag); + if (res != ERROR_SUCCESS) goto cleanup; + + res = jObject->GetNamedString(hElemIdTag, &hElemId); + if (res != ERROR_SUCCESS) goto cleanup; + + if (opType == OpType::Get) { + VARIANT emptyVar; + emptyVar.vt = VARENUM::VT_EMPTY; + + res = createPropertyValue(emptyVar, pElemVal); + } else { + res = jObject->GetNamedValue(hElemValTag, &jElemVal); + + if (res == ERROR_SUCCESS) { + res = getValueParam(jElemVal, pElemVal); + } + } + + if (res == ERROR_SUCCESS) { + param = Parameter { + pair> { + wstring { WindowsGetStringRawBuffer(hElemId, &hElemIdSize) }, + pElemVal + } + }; + } + +cleanup: + if (hElemIdTag != NULL) WindowsDeleteString(hElemIdTag); + if (hElemValTag != NULL) WindowsDeleteString(hElemValTag); + if (hElemId != NULL) WindowsDeleteString(hElemId); + + return res; +} + +HRESULT getMatchingType(OpType type, const ATL::CComPtr arrayObj, const UINT32 index, Parameter& param) { + HRESULT res = ERROR_SUCCESS; + + ATL::CComPtr> jVectorValue; + ATL::CComPtr jValue; + JsonValueType jElemValueType = JsonValueType::JsonValueType_Null; + boolean isValueType = false; + + res = arrayObj->QueryInterface(__uuidof(__FIVector_1_Windows__CData__CJson__CIJsonValue_t), reinterpret_cast(&jVectorValue)); + if (res != ERROR_SUCCESS) goto cleanup; + res = jVectorValue->GetAt(index, &jValue); + if (res != ERROR_SUCCESS) goto cleanup; + res = jValue->get_ValueType(&jElemValueType); + if (res != ERROR_SUCCESS) goto cleanup; + + isValueType = jElemValueType == JsonValueType::JsonValueType_Boolean || + jElemValueType == JsonValueType::JsonValueType_Number || + jElemValueType == JsonValueType::JsonValueType_String; + + if (isValueType) { + ATL::CComPtr jPropVal; + res = getValueParam(jValue, jPropVal); + + if (res == ERROR_SUCCESS) { + param = Parameter{ jPropVal }; + } + } else if (jElemValueType == JsonValueType::JsonValueType_Object) { + ATL::CComPtr jObjVal; + res = jValue->GetObject(&jObjVal); + + if (res == ERROR_SUCCESS) { + res = getObjectParam(type, jObjVal, param); + } + } else { + res = E_INVALIDARG; + } + +cleanup: + + return res; +} + +HRESULT parseParameters(OpType type, const ATL::CComPtr arrayObj, vector& params) { + if (arrayObj == NULL) { return E_INVALIDARG; }; + + HRESULT res = ERROR_SUCCESS; + + HRESULT nextElemErr = ERROR_SUCCESS; + UINT32 jElemIndex = 0; + vector _params {}; + + while (nextElemErr == ERROR_SUCCESS) { + Parameter curParam {}; + nextElemErr = getMatchingType(type, arrayObj, jElemIndex, curParam); + + if (curParam.isEmpty == false && nextElemErr == ERROR_SUCCESS) { + _params.push_back(curParam); + } else if (nextElemErr != E_BOUNDS) { + res = nextElemErr; + } + + jElemIndex++; + } + + if (res == ERROR_SUCCESS) { + params = _params; + } + + return res; +} + +HRESULT parseAction(const ATL::CComPtr elemObj, Action& action) { + if (elemObj == NULL) { return E_INVALIDARG; } + + HRESULT errCode = ERROR_SUCCESS; + UINT32 bufLength = 0; + + // Required fields vars + // ======================================================================== + + HSTRING hSettingId = NULL; + PCWSTR pSettingId = L"settingID"; + PCWSTR pSettingRawBuf = NULL; + + HSTRING hMethod = NULL; + PCWSTR pMethod = L"method"; + PCWSTR pMethodRawBuf = NULL; + + HSTRING hSettingIdVal = NULL; + HSTRING hMethodVal = NULL; + + wstring sSettingId {}; + wstring sMethod {}; + vector params {}; + + // Optional fields vars + // ======================================================================== + + HRESULT getArrayErr = ERROR_SUCCESS; + HSTRING hParams = NULL; + PCWSTR pParams = L"parameters"; + ATL::CComPtr jParamsArray = NULL; + + // Extract required fields + // ======================================================================== + + errCode = WindowsCreateString(pSettingId, static_cast(wcslen(pSettingId)), &hSettingId); + if (errCode != ERROR_SUCCESS) goto cleanup; + errCode = WindowsCreateString(pMethod, static_cast(wcslen(pMethod)), &hMethod); + if (errCode != ERROR_SUCCESS) goto cleanup; + + errCode = elemObj->GetNamedString(hSettingId, &hSettingIdVal); + if (errCode != ERROR_SUCCESS) goto cleanup; + + errCode = elemObj->GetNamedString(hMethod, &hMethodVal); + if (errCode != ERROR_SUCCESS) goto cleanup; + + pSettingRawBuf = WindowsGetStringRawBuffer(hSettingIdVal, &bufLength); + pMethodRawBuf = WindowsGetStringRawBuffer(hMethodVal, &bufLength); + + if (pSettingRawBuf != NULL && pMethodRawBuf != NULL) { + sSettingId = wstring(pSettingRawBuf); + sMethod = wstring(pMethodRawBuf); + } else { + errCode = E_INVALIDARG; + goto cleanup; + } + + // Extract optional fields + // ======================================================================== + + errCode = WindowsCreateString(pParams, static_cast(wcslen(pParams)), &hParams); + if (errCode != ERROR_SUCCESS) goto cleanup; + + getArrayErr = elemObj->GetNamedArray(hParams, &jParamsArray); + + // Check that the payload ins't of type "SetValue" if "parameters" isn't present. + if (getArrayErr != ERROR_SUCCESS) { + if (sMethod == L"SetValue") { + errCode = WEB_E_JSON_VALUE_NOT_FOUND; + goto cleanup; + } else if (sMethod == L"GetValue") { + action = Action { sSettingId, sMethod, params }; + } else { + // TODO: Change with a more meaningful message + errCode = E_INVALIDARG; + } + } else { + if (sMethod == L"SetValue") { + errCode = parseParameters(OpType::Set, jParamsArray, params); + + if (errCode == ERROR_SUCCESS) { + Action _action {}; + errCode = createAction(sSettingId, sMethod, params, _action); + + if (errCode == ERROR_SUCCESS) { + action = _action; + } + } + } else { + errCode = parseParameters(OpType::Get, jParamsArray, params); + + if (errCode == ERROR_SUCCESS) { + Action _action {}; + errCode = createAction(sSettingId, sMethod, params, _action); + + if (errCode == ERROR_SUCCESS) { + action = _action; + } + } + } + } + +cleanup: + if (hSettingId != NULL) { WindowsDeleteString(hSettingId); } + if (hMethod != NULL) { WindowsDeleteString(hMethod); } + if (hSettingIdVal != NULL) { WindowsDeleteString(hSettingIdVal); } + if (hMethodVal != NULL ) { WindowsDeleteString(hMethodVal); } + if (hParams != NULL) { WindowsDeleteString(hParams); } + + return errCode; +} + +HRESULT parsePayload(const wstring & payload, vector>& actions) { + HRESULT res = ERROR_SUCCESS; + vector> _actions {}; + + ATL::CComPtr jsonArrayFactory = NULL; + HSTRING rJSONClass = NULL; + res = WindowsCreateString( + RuntimeClass_Windows_Data_Json_JsonArray, + static_cast(wcslen(RuntimeClass_Windows_Data_Json_JsonArray)), + &rJSONClass + ); + + GetActivationFactory(rJSONClass, &jsonArrayFactory); + + HSTRING hPayload = NULL; + WindowsCreateString(payload.c_str(), static_cast(payload.size()), &hPayload); + + ATL::CComPtr jActionsArray = NULL; + HRESULT nextElemErr = ERROR_SUCCESS; + UINT32 jElemIndex = 0; + + res = jsonArrayFactory->Parse(hPayload, &jActionsArray); + if (res != ERROR_SUCCESS) goto cleanup; + + while (nextElemErr == ERROR_SUCCESS) { + IJsonObject* curElem = NULL; + nextElemErr = jActionsArray->GetObjectAt(jElemIndex, &curElem); + + if (curElem != NULL && nextElemErr == ERROR_SUCCESS) { + Action curAction {}; + ATL::CComPtr cCurElem { curElem }; + HRESULT errCode = parseAction(cCurElem, curAction); + + if (errCode == ERROR_SUCCESS) { + _actions.push_back({ curAction, ERROR_SUCCESS }); + } else { + _actions.push_back({ Action {}, errCode }); + } + }; + + jElemIndex++; + } + + for (const auto& action : _actions) { + if (action.second != ERROR_SUCCESS) { + res = E_INVALIDARG; + + break; + } + } + + actions = _actions; + +cleanup: + if (rJSONClass) { + WindowsDeleteString(rJSONClass); + } + if (hPayload) { + WindowsDeleteString(hPayload); + } + + return res; +} + +// ------------------------ Serialization ---------------------------------- + +HRESULT serializeResult(const Result& result, std::wstring& str) { + std::wstring resultStr {}; + HRESULT res = ERROR_SUCCESS; + + try { + // JSON object start + resultStr.append(L"{"); + + // SettingId + resultStr.append(L"\"settingID\": "); + if (result.settingID.empty()) { + resultStr.append(L"null"); + } else { + resultStr.append(L"\"" + result.settingID + L"\""); + } + resultStr.append(L", "); + + // IsError + resultStr.append(L"\"isError\": "); + if (result.isError) { + resultStr.append(L"true"); + } else { + resultStr.append(L"false"); + } + resultStr.append(L", "); + + // ErrorMessage + resultStr.append(L"\"errorMessage\": "); + if (result.errorMessage.empty()) { + resultStr.append(L"null"); + } else { + resultStr.append(L"\"" + result.errorMessage + L"\""); + } + resultStr.append(L", "); + + // ReturnValue + resultStr.append(L"\"returnValue\": "); + if (result.returnValue.empty()) { + resultStr.append(L"null"); + } else { + resultStr.append(result.returnValue); + } + + // JSON object end + resultStr.append(L"}"); + + // Communicate back the result + str = resultStr; + } catch(std::bad_alloc&) { + res = E_OUTOFMEMORY; + } + + return res; +} \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/Payload.h b/settingsHelper/SettingsHelperLib/Payload.h new file mode 100644 index 000000000..2cc58ac4a --- /dev/null +++ b/settingsHelper/SettingsHelperLib/Payload.h @@ -0,0 +1,125 @@ +#pragma once + +#include "stdafx.h" +#include "SettingItem.h" + +#include +#include + +#include +#include +#include + +using std::wstring; +using std::vector; +using std::pair; + +using namespace ABI::Windows::Foundation; + +/// +/// Structure holding the values required to perform a particular action over +/// a setting. Parameters can represent either a JSON object, which holds a pair +/// <'Identifier', 'Value'>, or a simple value. +/// +struct Parameter { + /// + /// A pair <'Identifier', 'Value'> used when the parameter is of kind 'Object'. + /// + pair> oIdVal; + /// + /// A IPropertyValue representing a value when the Parameter isn't of kind + /// 'Object'. + /// + ATL::CComPtr iPropVal; + /// + /// Property to identify how the parameter was constructed as an 'Object'. + /// + bool isObject { false }; + /// + /// Property to identify if the parameter was empty constructed. + /// + bool isEmpty { true }; + + /// + /// Empty constructor, used to represent a empty parameter. + /// + Parameter(); + /// + /// Constructor for constructing the parameter as an object. + /// + /// + /// A pair with which the Parameter is going to initialized. + /// + Parameter(pair> _objIdVal); + /// + /// Constructor for constructing the parameter as a value. + /// + /// + /// A smart pointer to an IPropertyValue with which the Parameter is going to initialized. + /// + Parameter(ATL::CComPtr _iPropVal); +}; + +/// +/// Action that is going to be performed over a setting. +/// +struct Action { + /// + /// The setting id in which the action needs to be performed. + /// + wstring settingID; + /// + /// The setting method to be called. + /// + wstring method; + /// + /// The parameters to be passed to the method that is going to be called. + /// + vector params; +}; + +/// +/// The result of each 'Action' requested to the application. +/// +struct Result { + /// + /// The id of the setting that was the target of the action. + /// + wstring settingID; + /// + /// Flag identifying if the operation was a success or not. + /// + BOOL isError; + /// + /// Human readable message describing the problem encountered + /// during the operation. Empty in case of success. + /// + wstring errorMessage; + /// + /// The value that is returned as a result of the operation. + /// + wstring returnValue; +}; + +/// +/// Parse the input payload and returns a secuence of actions to be applied. +/// +/// The payload to be parsed. +/// The sequence of actions to be filled with the payload. +/// +/// An HRESULT error if the operation failed or ERROR_SUCCESS. Possible errors: +/// - WEB_E_INVALID_JSON_STRING: If the JSON from the payload is invalid. +/// - WEB_E_JSON_VALUE_NOT_FOUND: If one of the required JSON fields isn't present in the payload. +/// - E_OUTOFMEMORY: If the system runs out of memory and WindowsCreateString fails. +/// +HRESULT parsePayload(const wstring & payload, vector>& actions); +/// +/// Serialize the result of the operation to communicate it back to the caller. +/// +/// The result of the operation. +/// The string to be filled with the result serialization. +/// +/// An HRESULT error if the operation failed or ERROR_SUCCESS. Possible errors: +/// - E_OUTOFMEMORY: If the system runs out of memory. +/// +HRESULT serializeResult(const Result& result, std::wstring& str); \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/PayloadProc.cpp b/settingsHelper/SettingsHelperLib/PayloadProc.cpp new file mode 100644 index 000000000..9e781e2a3 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/PayloadProc.cpp @@ -0,0 +1,363 @@ +#include "stdafx.h" +#include "PayloadProc.h" + +HRESULT handleSettingAction( + const wstring& valueId, + const Action& action, + SettingItem& setting, + wstring& rVal +) { + HRESULT errCode { ERROR_SUCCESS }; + + // Inner settings values should be accessed just after loading the database + // otherwise, last loaded setting is the one being accessed. + ATL::CComPtr iValue { NULL }; + errCode = setting.GetValue(valueId, iValue); + + if (errCode == ERROR_SUCCESS) { + ATL::CComPtr propValue { + static_cast(iValue.Detach()) + }; + wstring resValueStr {}; + + if (action.method == L"SetValue") { + BOOL equalProps { false }; + ATL::CComPtr paramValue { NULL }; + + for (const auto& param : action.params) { + if (param.isObject == true) { + if (param.oIdVal.first == setting.settingId) { + paramValue = param.oIdVal.second; + } + } else { + paramValue = param.iPropVal; + } + } + + errCode = equals(propValue, paramValue, equalProps); + + if (errCode == ERROR_SUCCESS) { + if (!equalProps) { + errCode = setting.SetValue(valueId, paramValue); + } + } + + if (errCode == ERROR_SUCCESS) { + errCode = toString(propValue, resValueStr); + + if (errCode == ERROR_SUCCESS) { + rVal = resValueStr; + } + } + } else { + errCode = toString(propValue, resValueStr); + + if (errCode == ERROR_SUCCESS) { + rVal = resValueStr; + } + } + } + + return errCode; +} + +wstring serializeReturnValues(vector> settingsValues) { + wstring valueResult {}; + + valueResult.append(L"[ "); + + for (const auto& setting : settingsValues) { + // Create result value + valueResult.append(L"{ "); + valueResult.append(L"idElem: \""); + valueResult.append(setting.first); + valueResult.append(L"\", "); + valueResult.append(L"elemVal: "); + valueResult.append(setting.second); + valueResult.append(L" }"); + + if (&setting != &settingsValues.back()) { + valueResult.append(L", "); + } + } + + valueResult.append(L" ]"); + + return valueResult; +} + +HRESULT handleCollectionAction( + SettingAPI& sAPI, + const wstring& valueId, + const Action& action, + SettingItem& setting, + Result& rResult +) { + SettingType type { SettingType::Empty }; + setting.GetSettingType(&type); + + // Check that the supplied setting is of the proper type + if (type != SettingType::SettingCollection) { return E_INVALIDARG; } + + HRESULT errCode { ERROR_SUCCESS }; + + vector tgtElemIds {}; + for (const auto& tgtElem : action.params) { + tgtElemIds.push_back(tgtElem.oIdVal.first); + } + + vector> settingsValues {}; + vector colSettings {}; + errCode = sAPI.getCollectionSettings(tgtElemIds, setting, colSettings); + + if (errCode == ERROR_SUCCESS) { + for (auto& setting : colSettings) { + wstring rStrVal {}; + errCode = handleSettingAction(valueId, action, setting, rStrVal); + + if (errCode == ERROR_SUCCESS) { + settingsValues.push_back({ setting.settingId, rStrVal }); + } else { + std::wostringstream errCodeStr {}; + errCodeStr << std::hex << errCode; + + rResult = Result { + action.settingID, + true, + L"Action over collection setting '" + + setting.settingId + + L"' failed with error code '0x" + errCodeStr.str(), + L"" + }; + break; + } + } + } + + if (settingsValues.empty() == false && errCode == ERROR_SUCCESS) { + wstring valueResult { serializeReturnValues(settingsValues) }; + + if (errCode == ERROR_SUCCESS) { + rResult = Result { action.settingID, false, L"", valueResult }; + } else { + rResult = Result { action.settingID, true, L"Failed to get the collection settings.", L"" }; + } + } + + return errCode; +} + +HRESULT getSettingPath(const Action& action, SettingPath& rPath) { + HRESULT errCode { ERROR_SUCCESS }; + + vector settingIds {}; + errCode = splitSettingPath(action.settingID, settingIds); + + const auto& idsSize = settingIds.size(); + if (idsSize == 1) { + rPath = { settingIds.front(), L"Value" }; + } else if (idsSize == 2) { + rPath = { settingIds.front(), settingIds[1] }; + } else { + errCode = E_INVALIDARG; + } + + return errCode; +} + +HRESULT handleAction(SettingAPI& sAPI, Action action, Result& rResult) { + HRESULT errCode { ERROR_SUCCESS }; + Result result {}; + + SettingPath settingPath {}; + errCode = getSettingPath(action, settingPath); + + if (errCode == ERROR_SUCCESS) { + SettingItem baseSetting {}; + errCode = sAPI.loadBaseSetting(settingPath.first, baseSetting); + + if (errCode == ERROR_SUCCESS) { + SettingType baseType { SettingType::Empty }; + baseSetting.GetSettingType(&baseType); + + if (errCode == ERROR_SUCCESS) { + if (baseType == SettingType::SettingCollection) { + Result result {}; + errCode = handleCollectionAction(sAPI, settingPath.second, action, baseSetting, result); + + if (errCode == ERROR_SUCCESS) { + rResult = result; + } + } else { + wstring rVal {}; + errCode = handleSettingAction(settingPath.second, action, baseSetting, rVal); + + if (errCode == ERROR_SUCCESS) { + rResult = Result { action.settingID, false, L"", rVal }; + } else { + std::wostringstream errCodeStr {}; + errCodeStr << std::hex << errCode; + + rResult = Result { + action.settingID, + true, + L"Failed to apply setting - ErrorCode: '0x" + errCodeStr.str() + L"'", + L"" + }; + } + } + } + } + } + + return errCode; +} + +void readInputStream(wstring* payloadStr) { + if (payloadStr == NULL) { + ExitThread(E_INVALIDARG); + } + + wstring cinData {}; + std::vector buffer ( static_cast(512) ); + + auto& inputStream = std::wcin; + + while (true) { + inputStream.read(buffer.data(), buffer.size()); + auto read = inputStream.gcount(); + + if (read > 0) { + cinData.append(buffer.begin(), buffer.begin() + read); + } + + if (!inputStream.good()) { break; } + } + + *payloadStr = cinData; +} + +wstring buildOutputStr(const vector& results) { + wstring output {}; + + output.append(L"["); + + for (auto& result : results) { + wstring resultStr {}; + HRESULT serRes = serializeResult(result, resultStr); + output.append(resultStr); + + if (&result != &results.back()) { + output.append(L","); + } + } + + output.append(L"]"); + + return output; +} + +wstring invalidPayloadMsg(HRESULT errCode) { + std::wstring errMsg { L"Invalid payload - Parsing failed with error code: '0x" }; + std::wostringstream errCodeStr {}; + errCodeStr << std::hex << errCode; + + errMsg.append(errCodeStr.str()); + errMsg.append(L"'."); + + return errMsg; +} + +HRESULT getInputPayload(pair* pInput, wstring& rPayloadStr) { + HRESULT errCode { ERROR_SUCCESS }; + + int argc { pInput->first }; + wchar_t** argv { pInput->second }; + + if (argc == 3) { + wstring payloadType { argv[1] }; + wstring filePath { argv[2] }; + + if (payloadType == L"-file") { + std::wifstream fileStream { filePath }; + + if (fileStream) { + rPayloadStr = wstring { + std::istreambuf_iterator(fileStream), + std::istreambuf_iterator() + }; + } + } else { + errCode = E_INVALIDARG; + } + } else { + HANDLE hInputReader { NULL }; + DWORD threadID { 0 }; + wstring inputStream {}; + + hInputReader = CreateThread( + NULL, + 0, + (LPTHREAD_START_ROUTINE)readInputStream, + (LPVOID)&inputStream, + 0, + &threadID + ); + + WaitForSingleObject(hInputReader, 1000); + } + + return errCode; +} + +HRESULT handlePayload(pair* pInput) { + HRESULT res { ERROR_SUCCESS }; + vector results {}; + wstring payloadStr {}; + + res = getInputPayload(pInput, payloadStr); + + if (res == ERROR_SUCCESS) { + vector> operations {}; + parsePayload(payloadStr, operations); + + SettingAPI& sAPI { LoadSettingAPI(res) }; + + if (res == ERROR_SUCCESS) { + for (const auto& action : operations) { + if (action.second == ERROR_SUCCESS) { + SettingItem settingItem {}; + vector settingIds {}; + + res = splitSettingPath(action.first.settingID, settingIds); + + if (res == ERROR_SUCCESS) { + Result actionResult {}; + res = handleAction(sAPI, action.first, actionResult); + + // Result should contain the error in case of failure + results.push_back(actionResult); + } + } else { + wstring errMsg { invalidPayloadMsg(action.second) }; + + results.push_back( + Result { + L"", + true, + errMsg, + L"" + } + ); + } + } + } + + res = UnloadSettingsAPI(sAPI); + } + + auto output = buildOutputStr(results); + std::wcout << output << std::endl; + + return res; +} diff --git a/settingsHelper/SettingsHelperLib/PayloadProc.h b/settingsHelper/SettingsHelperLib/PayloadProc.h new file mode 100644 index 000000000..c8d0c0596 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/PayloadProc.h @@ -0,0 +1,182 @@ +#pragma once + +#ifndef _SPAYLOAD_PROCESSING_H +#define _SPAYLOAD_PROCESSING_H + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "SettingUtils.h" +#include "IPropertyValueUtils.h" +#include "SettingItemEventHandler.h" +#include "StringConversion.h" +#include "Payload.h" + +using std::wstring; +using std::vector; +using std::pair; + +#pragma comment (lib, "WindowsApp.lib") + +/// +/// Applies one setting action into a setting. +/// +/// The value identifier, in case of being a inner setting, this value is a setting id. +/// The action to be performed over the setting. +/// The setting that is going to receive the action. +/// A string containing the result of the operation. +/// +/// ERROR_SUCCESS in case of success or one of the following error codes: +/// - E_NOTIMPL: +/// + If the operation is not supported on the supplied setting. +/// + If the supplied IPropertyValue within the action can't be compared +/// with the contained IPropertyValue in the target setting. +/// - E_INVALIDARG: +/// + If the supplied 'valueId' isn't supported. +/// + If the supplied IPropertyValue withing the action can't be converted into a string. +/// +HRESULT handleSettingAction(const wstring& valueId, const Action& action, SettingItem& setting, wstring& rVal); +/// +/// Serializes a vector of pairs of setting ''. +/// +/// +/// The settings values to be serialized. +/// +/// +/// The string containing the serialization of the supplied +/// setting values. +/// +wstring serializeReturnValues(vector> settingsValues); +/// +/// Handle an action over a SettingCollection. +/// +/// Reference to the already loaded settings library. +/// The value id target by the action. +/// The action to be performed over the value id. +/// Reference to the setting that is going to receive the action. +/// +/// Reference to 'Result' to be filled with the outcome of the action. +/// +/// +/// ERROR_SUCCESS in case of success or one of the following error codes: +/// - 'handleSettingAction' error code if one of actions over the elements of the +/// collection fails. +/// - E_INVALIDARG: +/// + If the supplied setting isn't of type SettingsCollection. +/// + If obtaining the settings from the settings collection fails. +/// +HRESULT handleCollectionAction( + SettingAPI& sAPI, + const wstring& valueId, + const Action& action, + SettingItem& setting, + Result& rResult +); + +/// +/// Type alias for the base setting identifier. +/// +using BaseSettingId = wstring; +/// +/// Type alias for the inner setting identifier. +/// +using InnerSettingId = wstring; +/// +/// Type alias for the SettingPath. +/// +using SettingPath = pair; + +/// +/// Get the setting path holded in the Action request. +/// +/// The action that holds the setting id. +/// The path to the setting to be filled. +/// +/// ERROR_SUCCESS in case of success or E_INVALIDARG if the supplied setting id +/// can't be splitted into a valid SettingPath. +/// +HRESULT getSettingPath(const Action& action, SettingPath& rPath); +/// +/// Handles a action over a setting of collection kind. +/// +/// Reference to the already loaded settings library. +/// An action to be performed. +/// +/// The result of applying the action. +/// +/// +/// ERROR_SUCCESS in case of success or one of the following error codes: +/// - E_INVALIDARG if the setting isn't supported or failed to load. +/// - 'handleSettingAction' error code if the action supplied isn't of +/// SettingCollection type. +/// - 'handleCollectinoAction' error code if the action supplied is of +/// SettingCollection type. +/// +HRESULT handleAction(SettingAPI& sAPI, Action action, Result& rResult); +/// +/// Read the data in the input stream. +/// +/// NOTE: This function is blocking, and should be called from +/// it's own thread. +/// +/// +/// A pointer to a wstring to be filled with the contents of +/// the standard input. +/// +void readInputStream(wstring* payloadStr); +/// +/// Creates an ouput string from a vector of results. +/// +/// +/// A with the Result of operations. +/// +/// +/// A ouput string with the proper serialization of the supplied +/// results. +/// +wstring buildOutputStr(const vector& results); +/// +/// Creates an error message with the supplied error code. The error code +/// will appear in HEX format in the message, so no information about it is +/// lost. +/// +/// The error code that should appear in the message. +/// An error message. +wstring invalidPayloadMsg(HRESULT errCode); +/// +/// Get the input payload for the application, if the file switch is specified, +/// the input payload is get from the file specified in the command line input. +/// In other case, the input is taking from the standard input, if the input +/// isn't received within 1 second of the function call, no input is assumed. +/// +/// +/// The program input encapsulated into a pointer to a pair. +/// +/// +/// A reference to a string to be filled with the input payload. +/// +/// +/// ERROR_SUCCESS if everything went fine, otherwise one of this errors is returned: +/// - +/// +HRESULT getInputPayload(pair* pInput, wstring& rPayloadStr); +/// +/// Handle the complete input payload from the program and return a result. +/// +/// +/// Pointer to the program payload composed of two pointer +/// +/// +/// ERROR_SUCCESS in case of success or one of the following error codes: +/// +HRESULT handlePayload(pair* pInput); + +#endif // !_SPAYLOAD_PROCESSING_H diff --git a/settingsHelper/SettingsHelperLib/SettingItem.cpp b/settingsHelper/SettingsHelperLib/SettingItem.cpp new file mode 100644 index 000000000..8f0935211 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/SettingItem.cpp @@ -0,0 +1,210 @@ +#include "stdafx.h" +#include "SettingItem.h" +#include "ISettingsCollection.h" +#include "SettingItemEventHandler.h" +#include "DynamicSettingsDatabase.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#pragma comment (lib, "WindowsApp.lib") + +using namespace ATL; + +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::UI::Core; + +using std::wstring; +using std::istringstream; +using std::pair; + +SettingItem::SettingItem() : BaseSettingItem::BaseSettingItem() {}; + +SettingItem::SettingItem( + wstring settingId, ATL::CComPtr settingItem, vector assocSettings +) : BaseSettingItem::BaseSettingItem(settingId, settingItem), assocSettings(assocSettings) {} + +SettingItem::SettingItem( + wstring parentId, wstring settingId, ATL::CComPtr settingItem +) : BaseSettingItem::BaseSettingItem(settingId, settingItem), parentId(parentId) {} + +SettingItem::SettingItem(const SettingItem & other) { + this->parentId = other.parentId; + this->setting = other.setting; + this->settingId = other.settingId; + this->assocSettings = other.assocSettings; +} + +SettingItem& SettingItem::operator=(const SettingItem& other) { + this->parentId = other.parentId; + this->setting = other.setting; + this->settingId = other.settingId; + this->assocSettings = other.assocSettings; + + return *this; +} + +UINT SettingItem::GetValue(wstring id, ATL::CComPtr& item) { + if (this->setting == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + if (id.empty()) { return E_INVALIDARG; }; + + HRESULT res = ERROR_SUCCESS; + BOOL isUpdating = false; + + HSTRING hId = NULL; + res = WindowsCreateString(id.c_str(), static_cast(id.size()), &hId); + + IInspectable* curValue = NULL; + if (res == ERROR_SUCCESS) { + if (id == L"Value" || id == L"DynamicSettingsDatabaseValue") { + res = BaseSettingItem::GetValue(id, item); + } else { + // Access one of the inner Settings inside the DynamicSettingDatabase + // holded inside the setting. + wstring settingId {}; + if (isSupportedDb(this->parentId)) { + settingId = this->parentId; + } else { + settingId = this->settingId; + } + + if (isSupportedDb(settingId)) { + if (this->dbSettings.empty()) { + DynamicSettingDatabase dynSettingDb {}; + res = loadSettingDatabase(settingId, *this, dynSettingDb); + + if (res == ERROR_SUCCESS) { + res = dynSettingDb.GetDatabaseSettings(this->dbSettings); + } + } + + if (res == ERROR_SUCCESS) { + for (auto& setting : this->dbSettings) { + if (id == setting.settingId) { + res = setting.GetValue(L"Value", item); + break; + } + } + } + } else { + // TODO: Improve error code + res = E_INVALIDARG; + } + } + + } + + return res; +} + +HRESULT SettingItem::SetValue(const wstring& id, ATL::CComPtr& item) { + if (this->setting == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + + HRESULT res { ERROR_SUCCESS }; + + if (id == L"Value") { + BOOL completed = false; + BOOL isUpdating { true }; + UINT maxIt { 10 }; + + ATL::CComPtr> handler = + new ITypedEventHandler(&completed); + EventRegistrationToken token { 0 }; + + res = this->setting->add_SettingChanged(handler, &token); + + if (res == ERROR_SUCCESS) { + res = BaseSettingItem::SetValue(id, item); + + if (res == ERROR_SUCCESS) { + UINT it = 0; + while (completed != TRUE && isUpdating == TRUE) { + if (it < maxIt) { + this->setting->get_IsUpdating(&isUpdating); + System::Threading::Thread::Sleep(100); + + it++; + } else { + completed = true; + res = ERROR_TIMEOUT; + } + } + } + } + + this->setting->remove_SettingChanged(token); + } else { + // Access one of the inner Settings inside the DynamicSettingDatabase + // holded inside the setting. + wstring settingId {}; + if (isSupportedDb(this->parentId)) { + settingId = this->parentId; + } else { + settingId = this->settingId; + } + + if (isSupportedDb(settingId)) { + if (this->dbSettings.empty()) { + DynamicSettingDatabase dynSettingDb {}; + res = loadSettingDatabase(settingId, *this, dynSettingDb); + + if (res == ERROR_SUCCESS) { + res = dynSettingDb.GetDatabaseSettings(this->dbSettings); + } + } + + if (res == ERROR_SUCCESS) { + for (auto& setting : this->dbSettings) { + if (id == setting.settingId) { + BOOL isUpdating { true }; + BOOL completed { false }; + BOOL innerUpdating { true }; + UINT maxIt { 10 }; + + ATL::CComPtr> handler = + new ITypedEventHandler(&completed); + EventRegistrationToken token { 0 }; + + res = this->setting->add_SettingChanged(handler, &token); + + res = setting.SetValue(L"Value", item); + + if (res == ERROR_SUCCESS) { + UINT it = 0; + while (completed != TRUE || isUpdating == TRUE || innerUpdating == TRUE) { + if (it < maxIt) { + this->setting->get_IsUpdating(&isUpdating); + setting.GetIsUpdating(&innerUpdating); + + System::Threading::Thread::Sleep(100); + + it++; + } else { + completed = true; + res = ERROR_TIMEOUT; + } + } + } + + res = this->setting->remove_SettingChanged(token); + + break; + } + } + } + } else { + // TODO: Improve error code + res = E_INVALIDARG; + } + } + + return res; +} diff --git a/settingsHelper/SettingsHelperLib/SettingItem.h b/settingsHelper/SettingsHelperLib/SettingItem.h new file mode 100644 index 000000000..9008cff1a --- /dev/null +++ b/settingsHelper/SettingsHelperLib/SettingItem.h @@ -0,0 +1,100 @@ +#pragma once + +#ifndef S__SettingItem_H +#define S__SettingItem_H + +#include "BaseSettingItem.h" +#include "DbSettingItem.h" + +#include +#include +#include + +#include +#include +#include + +using std::wstring; +using std::vector; +using std::pair; + +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; + +struct SettingItem : BaseSettingItem { +private: + vector dbSettings {}; + /// + /// This id specifies the parent setting of the current setting. This id is specially + /// useful in situations where the setting is a setting from a Collection, in this case, + /// we want to specify this Id as a way to know if we support accesing the inner db settings + /// of this particular setting. + /// + wstring parentId {}; + +public: + using BaseSettingItem::BaseSettingItem; + + /// + /// Resources that needs to be loaded for the setting to be accessed. + /// + vector assocSettings {}; + + /// + /// Default constructor + /// + SettingItem(); + + SettingItem(wstring parentId, wstring settingId, ATL::CComPtr settingItem); + /// + /// Constructs a SettingItem with the loaded required settings that it needs to work. + /// + /// + /// + /// + SettingItem(wstring settingId, ATL::CComPtr settingItem, vector assocSettings); + /// + /// Copy constructor. + /// + /// The setting item to be copied. + SettingItem(const SettingItem& other); + /// + /// Copy assignment operator. + /// + SettingItem& operator=(const SettingItem& other); + + /// + /// Gets the value of the current stored setting that matches with the supplied + /// identifier. + /// + /// The identifier of the setting to be retrieved, most of the + /// times this just "Value". + /// A pointer to a IInspectable* that will hold the current value, if the + /// operation doesn't succeed, "item" will be set to NULL. + /// + /// An HRESULT error if the operation failed or ERROR_SUCCESS. Possible errors: + /// - E_NOTIMPL: The setting doesn't have implemented the method; this can be + /// caused because the setting doesn't support this method, and doesn't contains + /// any value. + /// + UINT GetValue(wstring id, ATL::CComPtr& item); + /// + /// Sets the current value for the setting that matches the supplied identifier. + /// + /// The identifier of the setting to be set, most of the times this + /// just "Value". + /// A pointer to a IInspecable that holds the current value to be set, + /// most of the times this should be a IPropertyValue. + /// + /// An HRESULT error if the operation failed or ERROR_SUCCESS. Possible errors: + /// - TYPE_E_TYPEMISMATCH: The IPropertyValue supplied withing the 'item' parameter + /// is not the proper type for the setting. + /// - E_NOTIMPL: The setting doesn't have implemented the method; this can be + /// caused because the setting doesn't support this method, and doesn't contains + /// any value. + /// + HRESULT SetValue(const wstring& id, ATL::CComPtr& value); +}; + + +#endif // S__SettingItem_H \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/SettingItemEventHandler.cpp b/settingsHelper/SettingsHelperLib/SettingItemEventHandler.cpp new file mode 100644 index 000000000..d4c0b898a --- /dev/null +++ b/settingsHelper/SettingsHelperLib/SettingItemEventHandler.cpp @@ -0,0 +1,122 @@ +#include "stdafx.h" +#include "SettingItemEventHandler.h" + +#include +#include + +#pragma comment (lib, "WindowsApp.lib") + +// ----------------------------------------------------------------------------- +// ITypedEventHandler +// ----------------------------------------------------------------------------- + +// --------------------------- Public --------------------------------------- + +ITypedEventHandler::ITypedEventHandler(BOOL* c) : completed(c), counter(0) {} + +// ---------------------- Public - Inherited --------------------------------- + +HRESULT __stdcall ITypedEventHandler::QueryInterface(REFIID riid, void** ppvObject) { + // Always set out parameter to NULL, validating it first. + if (!ppvObject) { + return E_INVALIDARG; + } + + *ppvObject = NULL; + if (riid == IID_IUnknown) { + // Increment the reference count and return the pointer. + *ppvObject = (LPVOID)this; + AddRef(); + return NOERROR; + } + return E_NOINTERFACE; +} + +ULONG __stdcall ITypedEventHandler::AddRef(void) { + ULONG ulRefCount = InterlockedIncrement(&counter); + return ulRefCount; +} + +ULONG __stdcall ITypedEventHandler::Release(void) { + // Decrement the object's internal counter. + ULONG ulRefCount = InterlockedDecrement(&counter); + + if (0 == ulRefCount) { + delete this; + } + + return ulRefCount; +} + +HRESULT __stdcall ITypedEventHandler::Invoke(IInspectable* sender, HSTRING arg) { + HRESULT res = ERROR_SUCCESS; + + HSTRING hValue = NULL; + PCWSTR pValue = L"Value"; + INT32 equal = 0; + + HRESULT createRes = WindowsCreateString(pValue, static_cast(wcslen(pValue)), &hValue); + HRESULT cmpRes = WindowsCompareStringOrdinal(arg, hValue, &equal); + + if (createRes == ERROR_SUCCESS && cmpRes == ERROR_SUCCESS && equal == 0) { + *(this->completed) = true; + } + + WindowsDeleteString(hValue); + + return res; +} + +// ----------------------------------------------------------------------------- +// VectorEventHandler +// ----------------------------------------------------------------------------- + +// --------------------------- Public --------------------------------------- + +VectorEventHandler::VectorEventHandler(BOOL* c) : changed(c), counter(0) {} + +// ---------------------- Public - Inherited --------------------------------- + +HRESULT __stdcall VectorEventHandler::QueryInterface(REFIID riid, void ** ppvObject) { + // Always set out parameter to NULL, validating it first. + if (!ppvObject) { + return E_INVALIDARG; + } + + *ppvObject = NULL; + if (riid == IID_IUnknown) { + // Increment the reference count and return the pointer. + *ppvObject = (LPVOID)this; + AddRef(); + return NOERROR; + } + return E_NOINTERFACE; +} + +ULONG __stdcall VectorEventHandler::AddRef(void) { + ULONG ulRefCount = InterlockedIncrement(&counter); + return ulRefCount; +} + +ULONG __stdcall VectorEventHandler::Release(void) { + // Decrement the object's internal counter. + ULONG ulRefCount = InterlockedDecrement(&counter); + + if (0 == ulRefCount) { + delete this; + } + + return ulRefCount;} + +HRESULT __stdcall VectorEventHandler::Invoke(IObservableVector* sender, IVectorChangedEventArgs * e) { + HRESULT res = ERROR_SUCCESS; + + CollectionChange typeOfChange {}; + e->get_CollectionChange(&typeOfChange); + + if (typeOfChange == CollectionChange::CollectionChange_ItemChanged) { + *(this->changed) = true; + } + + return res; +} diff --git a/settingsHelper/SettingsHelperLib/SettingItemEventHandler.h b/settingsHelper/SettingsHelperLib/SettingItemEventHandler.h new file mode 100644 index 000000000..3c4bf1f9f --- /dev/null +++ b/settingsHelper/SettingsHelperLib/SettingItemEventHandler.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include + +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; + + /// + /// Completion handler used to detect when the value of a setting has been changed. + /// +template <> +struct ITypedEventHandler : ITypedEventHandler_impl { +private: + /// + /// Flag used to be setted when the operation over the setting + /// has been completed. + /// + BOOL* completed; + /// + /// Counter used to keep track of the number of references created. + /// + LONG counter; + +public: + /// + /// Constructor taking a pointer to the flag used to report when the + /// operation is completed. + /// + /// Pointer to the flag. + ITypedEventHandler(BOOL* c); + + virtual HRESULT __stdcall QueryInterface(REFIID riid, void ** ppvObject) override; + virtual ULONG __stdcall AddRef(void) override; + virtual ULONG __stdcall Release(void) override; + virtual HRESULT __stdcall Invoke(IInspectable* sender, HSTRING arg) override; +}; + + /// + /// Handler used to detect when a ObservableVector that is used to hold the elements + /// of a SettingsCollection have been changed. + /// +struct VectorEventHandler : VectorChangedEventHandler { +private: + /// + /// Flag used to report when one of the elements of the vector + /// have been changed. + /// + BOOL* changed; + /// + /// Counter used to keep track of the number of references created. + /// + LONG counter; + +public: + /// + /// Constructor taking a pointer to the flag used to report when + /// one item of the vector have been changed. + /// + /// + VectorEventHandler(BOOL* c); + + virtual HRESULT __stdcall QueryInterface(REFIID riid, void ** ppvObject) override; + virtual ULONG __stdcall AddRef(void) override; + virtual ULONG __stdcall Release(void) override; + virtual HRESULT __stdcall Invoke(IObservableVector* sender, IVectorChangedEventArgs * e) override; +}; \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/SettingUtils.cpp b/settingsHelper/SettingsHelperLib/SettingUtils.cpp new file mode 100644 index 000000000..e11f92f30 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/SettingUtils.cpp @@ -0,0 +1,672 @@ +#include "stdafx.h" + +#include "Constants.h" +#include "SettingUtils.h" +#include "StringConversion.h" +#include "DynamicSettingsDatabase.h" + +#include +#include +#include + +#include + +// Provisional +#include + +#pragma comment (lib, "WindowsApp.lib") + +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Foundation::Collections; +using namespace ABI::Windows::UI::Core; + +typedef int(*GetSettingFunc)(HSTRING, ISettingItem**, UINT32); + +// ----------------------------------------- +// SettingAPI Helper Functions +// ----------------------------------------- + +BOOL isFaultySetting(const wstring& settingId) { + const auto& pos = + std::find( + std::begin(constants::KnownFaultySettings()), + std::end(constants::KnownFaultySettings()), + settingId + ); + + return pos != std::end(constants::KnownFaultySettings()); +} + +BOOL isFaultyLib(const wstring& libPath) { + const auto& pos = + std::find( + std::begin(constants::KnownFaultyLibs()), + std::end(constants::KnownFaultyLibs()), + libPath + ); + + return pos != std::end(constants::KnownFaultyLibs()); +} + +HRESULT getRegSubKeys(const HKEY& hKey, vector& rKeys) { + TCHAR achKey[constants::MAX_KEY_LENGTH]; // Buffer for subkey name + DWORD cbName; // Size of name string + TCHAR achClass[MAX_PATH] = TEXT(""); // Buffer for class name + DWORD cchClassName = MAX_PATH; // Size of class string + DWORD cSubKeys=0; // Number of subkeys + DWORD cbMaxSubKey; // Longest subkey size + DWORD cchMaxClass; // Longest class string + DWORD cValues; // Number of values for key + DWORD cchMaxValue; // Longest value name + DWORD cbMaxValueData; // Longest value data + DWORD cbSecurityDescriptor; // Size of security descriptor + FILETIME ftLastWriteTime; // Last write time + + DWORD i, retCode; + + // Get the class name and the value count. + retCode = RegQueryInfoKey( + hKey, // key handle + achClass, // buffer for class name + &cchClassName, // size of class string + NULL, // reserved + &cSubKeys, // number of subkeys + &cbMaxSubKey, // longest subkey size + &cchMaxClass, // longest class string + &cValues, // number of values for this key + &cchMaxValue, // longest value name + &cbMaxValueData, // longest value data + &cbSecurityDescriptor, // security descriptor + &ftLastWriteTime); // last write time + + // Enumerate the subkeys, until RegEnumKeyEx fails. + if (cSubKeys) { + for (i=0; i& propValue, wstring& rValueStr) { + if (propValue == NULL) { return E_INVALIDARG; } + + HRESULT res { ERROR_SUCCESS }; + PropertyType valueType { PropertyType::PropertyType_Empty }; + + res = propValue->get_Type(&valueType); + if (res == ERROR_SUCCESS) { + if (valueType == PropertyType::PropertyType_Boolean) { + boolean actualVal { false }; + res = propValue->GetBoolean(&actualVal); + + if (actualVal == false) { + rValueStr = L"false"; + } else { + rValueStr = L"true"; + } + } else if (valueType == PropertyType::PropertyType_Double) { + DOUBLE actualVal { 0 }; + res = propValue->GetDouble(&actualVal); + + if (res == ERROR_SUCCESS) { + rValueStr = std::to_wstring(actualVal); + } + } else if (valueType == PropertyType::PropertyType_UInt64) { + UINT64 actualVal { 0 }; + res = propValue->GetUInt64(&actualVal); + + if (res == ERROR_SUCCESS) { + rValueStr = std::to_wstring(actualVal); + } + } else if (valueType == PropertyType::PropertyType_UInt32) { + UINT32 actualVal { 0 }; + res = propValue->GetUInt32(&actualVal); + + if (res == ERROR_SUCCESS) { + rValueStr = std::to_wstring(actualVal); + } + } else if (valueType == PropertyType::PropertyType_Int64) { + INT64 actualVal { 0 }; + res = propValue->GetInt64(&actualVal); + + if (res == ERROR_SUCCESS) { + rValueStr = std::to_wstring(actualVal); + } + } else if (valueType == PropertyType::PropertyType_Int32) { + INT32 actualVal { 0 }; + res = propValue->GetInt32(&actualVal); + + if (res == ERROR_SUCCESS) { + rValueStr = std::to_wstring(actualVal); + } + } else if (valueType == PropertyType::PropertyType_DateTime) { + DateTime actualVal {}; + res = propValue->GetDateTime(&actualVal); + + if (res == ERROR_SUCCESS) { + try { + System::DateTime^ dateTime = gcnew System::DateTime(actualVal.UniversalTime); + System::String^ sysDateTimeStr = dateTime->ToString(); + + wstring dateTimeStr {}; + res = getInnerString(sysDateTimeStr, dateTimeStr); + + if (res == ERROR_SUCCESS) { + rValueStr = dateTimeStr; + } + } catch (System::Exception^) { + res = E_INVALIDARG; + } + } + } else if (valueType == PropertyType::PropertyType_TimeSpan) { + TimeSpan actualVal {}; + res = propValue->GetTimeSpan(&actualVal); + + if (res == ERROR_SUCCESS) { + try { + System::TimeSpan^ timeSpan = gcnew System::TimeSpan(actualVal.Duration); + System::String^ sysTimeSpanStr = timeSpan->ToString(); + + wstring timeSpanStr {}; + res = getInnerString(sysTimeSpanStr, timeSpanStr); + + if (res == ERROR_SUCCESS) { + rValueStr = timeSpanStr; + } + } catch (System::Exception^) { + res = E_INVALIDARG; + } + } + } else { + res = E_INVALIDARG; + } + } + + return res; +} + +LONG getStringRegKey(HKEY hKey, const std::wstring &strValueName, std::wstring &strValue) { + WCHAR szBuffer[512]; + DWORD dwBufferSize = sizeof(szBuffer); + ULONG nError = RegQueryValueExW(hKey, strValueName.c_str(), 0, NULL, (LPBYTE)szBuffer, &dwBufferSize); + + if (ERROR_SUCCESS == nError) { + strValue = szBuffer; + } + + return nError; +} + +vector split(const wstring& str, const wstring& delim, const wstring& esc_sec) { + vector tokens {}; + size_t s_offset = 0, v_offset = 0, pos = 0, delimSize = delim.size(); + size_t esc_pos = 0; + + while (pos != wstring::npos) { + pos = str.find(delim, s_offset); + if (esc_pos != wstring::npos) { + esc_pos = str.find(esc_sec, s_offset); + } + s_offset = pos + delimSize; + + if (esc_pos != (pos - 1)) { + tokens.push_back(str.substr(v_offset, pos - v_offset)); + v_offset = s_offset; + } + } + + return tokens; +} + +HRESULT splitSettingPath(const wstring& settingPath, vector& rIdsPath) { + if (settingPath.empty()) { return E_INVALIDARG; } + + HRESULT res = ERROR_SUCCESS; + + vector idsPath = split(settingPath, wstring{ L'.' }, wstring{ L'\\' }); + + auto frontEmpty = idsPath.front().empty(); + auto backEmpty = idsPath.back().empty(); + + if (idsPath.empty() || frontEmpty || backEmpty) { + res = E_INVALIDARG; + } else { + res = ERROR_SUCCESS; + rIdsPath = idsPath; + } + + return res; +} + +HRESULT getSettingDLL(const std::wstring& settingId, std::wstring& settingDLL) { + HRESULT result = ERROR_SUCCESS; + + if (!settingId.empty()) { + std::wstring settingIdPath = constants::BaseRegPath() + L"\\" + settingId; + + // Get registry key + HKEY hKey; + LONG lRes = RegOpenKeyExW(HKEY_LOCAL_MACHINE, settingIdPath.c_str(), 0, KEY_READ, &hKey); + + if (lRes == ERROR_SUCCESS) { + result = getStringRegKey(hKey, L"DllPath", settingDLL); + } else { + result = ERROR_OPEN_FAILED; + } + } + + return result; +} + +BOOL checkEmptyIds(const vector& ids) { + BOOL empty { false }; + + for (const auto& id : ids) { + if (id.empty()) { + empty = true; + break; + } + } + + return empty; +} + +// ----------------------------------------------------------------------------- +// SettingAPI Functions +// ----------------------------------------------------------------------------- + +// --------------------------- Private -------------------------------------- + +SettingAPI& LoadSettingAPI(HRESULT& rErrCode) { + static SettingAPI sAPI {}; + HRESULT errCode { ERROR_SUCCESS }; + + if (sAPI.baseLibrary == NULL) { + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + HMODULE baseLibrary { LoadLibrary(L"C:\\Windows\\System32\\SettingsHandlers_nt.dll") }; + + if (baseLibrary != NULL) { + sAPI.baseLibrary = baseLibrary; + } else { + errCode = ERROR_ASSERTION_FAILURE; + } + } else { + } + + rErrCode = errCode; + + return sAPI; +} + +HRESULT UnloadSettingsAPI(SettingAPI& sAPI) { + CoFreeUnusedLibrariesEx(0, NULL); + CoUninitialize(); + + return ERROR_SUCCESS; +} + +SettingAPI::SettingAPI() {} + +BOOL SettingAPI::isLibraryLoaded(wstring libraryPath) { + BOOL res { false }; + + for (const auto& library : this->loadLibraries) { + if (library.first == libraryPath) { + res = true; + break; + } + } + + return res; +} + +HRESULT SettingAPI::getLoadedLibrary(const wstring& libPath, HMODULE& rLib) { + HRESULT errCode { ERROR_SUCCESS }; + BOOL found { false }; + HMODULE lib { NULL }; + + for (const auto& libEntry : this->loadLibraries) { + if (libEntry.first == libPath) { + lib = libEntry.second; + found = true; + + break; + } + } + + if (found) { + rLib = lib; + } else { + errCode = ERROR_NOT_FOUND; + } + + return errCode; +} + +HRESULT SettingAPI::loadSettingLibrary(const wstring& settingId, HMODULE& hLib) { + if (this->baseLibrary == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + if (settingId.empty() || isFaultySetting(settingId)) { return E_INVALIDARG; }; + + HRESULT res { ERROR_SUCCESS }; + wstring settingDLL { L"" }; + BOOL loaded { false }; + + res = getSettingDLL(settingId, settingDLL); + + if (res == ERROR_SUCCESS) { + if (!isFaultyLib(settingDLL)) { + BOOL isBaseLib { settingDLL == constants::BaseLibPath() }; + loaded = isLibraryLoaded(settingDLL); + + if (loaded || isBaseLib) { + if (isBaseLib) { + hLib = this->baseLibrary; + } else { + HMODULE lib { NULL }; + res = getLoadedLibrary(settingDLL, lib); + + if (res == ERROR_SUCCESS) { + hLib = lib; + } + } + } else { + res = SettingAPI::loadLibrary(settingDLL, hLib); + + if (res == ERROR_SUCCESS) { + for (const auto& lib : constants::CoupledLibs()) { + if (lib.first == settingDLL) { + for (const auto& coupledLib : lib.second) { + HMODULE _tmp {}; + res = SettingAPI::loadLibrary(coupledLib, _tmp); + + if (res != ERROR_SUCCESS) { + // TODO: Add meaningful error message + break; + } + } + } + } + } + } + } else { + // TODO: Change for more meaningful error message + res = E_INVALIDARG; + } + } + + return res; +} + +HRESULT SettingAPI::loadLibrary(const wstring& libPath, HMODULE& rHLib) { + HRESULT errCode { ERROR_SUCCESS }; + + rHLib = LoadLibrary(libPath.c_str()); + + if (rHLib == NULL) { + errCode = GetLastError(); + } else { + this->loadLibraries.push_back({ libPath, rHLib }); + } + + return errCode; +} + +// --------------------------- Public --------------------------------------- + +HRESULT SettingAPI::loadBaseSetting(const std::wstring& settingId, SettingItem& settingItem) { + if (this->baseLibrary == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + if (settingId.empty() || isFaultySetting(settingId)) { return E_INVALIDARG; } + + HRESULT res { ERROR_SUCCESS }; + HMODULE lib { NULL }; + ISettingItem* setting { NULL }; + + res = loadSettingLibrary(settingId, lib); + + if (res != ERROR_SUCCESS) { + return res; + } + + try { + + // TODO: Recheck the condition of failing loaded DLL. + DWORD lastError = ERROR_SUCCESS; + DWORD preGetLastError = GetLastError(); + GetSettingFunc getSetting = (GetSettingFunc)GetProcAddress(lib, "GetSetting"); + DWORD postGetLastError = GetLastError(); + + if (preGetLastError != postGetLastError) { + lastError = postGetLastError; + } + + HSTRING hSettingId = NULL; + res = WindowsCreateString(settingId.c_str(), static_cast(settingId.size()), &hSettingId); + + if (res == ERROR_SUCCESS && lastError == ERROR_SUCCESS && getSetting != NULL) { + res = getSetting(hSettingId, &setting, 0); + + BOOL isUpdating { true }; + // ColorFilter setting doesn't update this field when ready. + BOOL isEnabled { true }; + // IsApplicable' may cause segfault in certain settings. + BOOL isApplicable { true }; + + UINT counter = 0; + while (isUpdating) {// || (isEnabled == false )) { // && isApplicable == false)) { + // Timer + // ========== + if (counter > 10) { + break; + } else { + System::Threading::Thread::Sleep(10); + counter += 1; + } + + setting->get_IsUpdating(&isUpdating); + // setting->get_IsApplicable(&isApplicable); + // setting->get_IsEnabled(&isEnabled); + } + + if (res == ERROR_SUCCESS && isApplicable == TRUE && isEnabled == TRUE && isUpdating == FALSE) { + ATL::CComPtr comSetting { NULL }; + comSetting.Attach(setting); + settingItem = SettingItem { settingId, comSetting }; + } else { + setting->Release(); + res = E_INVALIDARG; + } + } else { + res = lastError; + } + + WindowsDeleteString(hSettingId); + + } catch(...) { + if (setting != NULL) { + setting->Release(); + } + res = E_NOTIMPL; + } + + return res; +} + +HRESULT SettingAPI::getCollectionSettings(const vector& ids, SettingItem& collSetting, vector& rSettings) { + if (this->baseLibrary == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + if (ids.empty() || checkEmptyIds(ids)) { return E_INVALIDARG; } + + SettingType type { SettingType::Empty }; + collSetting.GetSettingType(&type); + if (type != SettingType::SettingCollection) { + // TODO: Change with more meaningful error + return E_INVALIDARG; + } + + HRESULT errCode { ERROR_SUCCESS }; + UINT32 vectorSize { 0 }; + vector _ids { ids }; + + ATL::CComPtr collection = NULL; + errCode = collSetting.GetValue(L"Value", collection); + + if (errCode == ERROR_SUCCESS) { + ATL::CComPtr> pSettingVector { NULL }; + pSettingVector.Attach( + static_cast*>( + static_cast( + collection.Detach() + ) + ) + ); + + UINT32 vectorSize { 0 }; + vector settingItems {}; + + errCode = pSettingVector->get_Size(&vectorSize); + + if (errCode == ERROR_SUCCESS) { + for (UINT32 i = 0; i < vectorSize; i++) { + ATL::CComPtr pCurSetting = NULL; + errCode = pSettingVector->GetAt(i, reinterpret_cast(&pCurSetting)); + + if (errCode == ERROR_SUCCESS) { + for (auto& id = _ids.begin(); id != _ids.end();) { + SettingItem setting { collSetting.settingId, *id, pCurSetting }; + std::wstring curSettingDesc {}; + std::wstring curSettingId {}; + + // TODO: We may want to check the return of this operations. + setting.GetDescription(curSettingDesc); + setting.GetId(curSettingId); + + if (*id == curSettingId || *id == curSettingDesc) { + settingItems.push_back(setting); + + // Remove the already found id + id = _ids.erase(id); + } else { + ++id; + } + } + } else { + break; + } + } + } + + if (settingItems.empty()) { + errCode = E_INVALIDARG; + } else { + rSettings = settingItems; + } + } + + return errCode; +} + +HRESULT SettingAPI::getCollectionSettings(SettingItem& collSetting, vector& rSettings) { + if (this->baseLibrary == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + + HRESULT errCode { ERROR_SUCCESS }; + UINT32 vectorSize { 0 }; + + SettingType type { SettingType::Empty }; + errCode = collSetting.GetSettingType(&type); + if (type != SettingType::SettingCollection) { + // TODO: Change with more meaningful error + return E_INVALIDARG; + } + + ATL::CComPtr collection = NULL; + errCode = collSetting.GetValue(L"Value", collection); + + if (errCode == ERROR_SUCCESS) { + ATL::CComPtr> pSettingVector { NULL }; + pSettingVector.Attach( + static_cast*>( + static_cast( + collection.Detach() + ) + ) + ); + + UINT32 vectorSize { 0 }; + vector settingItems {}; + + errCode = pSettingVector->get_Size(&vectorSize); + + if (errCode == ERROR_SUCCESS) { + for (UINT32 i = 0; i < vectorSize; i++) { + ATL::CComPtr pCurSetting = NULL; + errCode = pSettingVector->GetAt(i, reinterpret_cast(&pCurSetting)); + + if (errCode == ERROR_SUCCESS) { + HSTRING hCurSettingId { NULL }; + errCode = pCurSetting->get_Id(&hCurSettingId); + + if (errCode) { + UINT32 length { 0 }; + LPCWSTR pStrBuffer { WindowsGetStringRawBuffer(hCurSettingId, &length) }; + std::wstring id { pStrBuffer }; + SettingItem setting { collSetting.settingId, id, pCurSetting }; + + settingItems.push_back(setting); + } + } else { + break; + } + } + } + + if (settingItems.empty()) { + errCode = E_INVALIDARG; + } else { + rSettings = settingItems; + } + } + + return errCode; +} + +HRESULT SettingAPI::getCollectionSetting(const wstring& id, SettingItem& settingCollection, SettingItem& rSetting) { + if (this->baseLibrary == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + if (id.empty()) { return E_INVALIDARG; } + + SettingType type { SettingType::Empty }; + settingCollection.GetSettingType(&type); + if (type != SettingType::SettingCollection) { + // TODO: Change with more meaningful error + return E_INVALIDARG; + } + + HRESULT errCode { ERROR_SUCCESS }; + vector settings {}; + + errCode = getCollectionSettings({ id }, settingCollection, settings); + + if (errCode == ERROR_SUCCESS && !settings.empty()) { + rSetting = settings.front(); + } else { + errCode = E_INVALIDARG; + } + + return errCode; +} diff --git a/settingsHelper/SettingsHelperLib/SettingUtils.h b/settingsHelper/SettingsHelperLib/SettingUtils.h new file mode 100644 index 000000000..1819559f8 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/SettingUtils.h @@ -0,0 +1,150 @@ +#pragma once + +#ifndef F__SettingUtils_H +#define F__SettingUtils_H + +#include "SettingItem.h" + +#include + +using namespace ABI::Windows::Foundation; + +/// +/// Returns a vector holding the subkeys of the specified registry key. +/// +HRESULT getRegSubKeys(const HKEY& hKey, vector& rKeys); +/// +/// Changes a setting value using the supplied string. +/// +HRESULT setSettingValue(const VARIANT& value, SettingItem& settingItem); +/// +/// Returns the string representation of the value contained in the IPropertyValue. +/// +HRESULT toString(const ATL::CComPtr& propValue, wstring& rValueStr); +/// +/// +/// +vector split(const wstring& str, const wstring& delim, const wstring& esc_sec); +/// +/// Receives the path to a setting and splits it into the secuence of SettingsId +/// that needs to be queried to get to the desired setting. +/// +/// The complete path to the setting. +/// The vector with the secuence of settings to be obtained. +HRESULT splitSettingPath(const wstring& settingPath, vector& idsPath); +/// +/// Gets the corresponding DLL to the supplied setting identifier. +/// +/// A string containing the setting id for which the dll needs to be searched. +/// A reference to a string to be filled with the setting dll location. +/// +/// An HRESULT error if the operation failed or ERROR_SUCCESS. Possible errors: +/// -ERROR_OPEN_FAILED: If the registry key containing the Id can't be openned. +/// +HRESULT getSettingDLL(const std::wstring& settingId, std::wstring& settingDLL); + +class SettingAPI { +private: + /// + /// The base library that need to be loaded before any other lib. + /// + HMODULE baseLibrary { NULL }; + /// + /// A list of the already loaded libraries. + /// + vector> loadLibraries {}; + + /// + /// Checks if a library is already loaded. + /// + /// + /// + BOOL isLibraryLoaded(wstring libraryPath); + /// + /// Gets an already loaded library. + /// + /// The loaded library path in the filesystem. + /// A reference to be filled with the found loaded library. + /// + /// An error code specifying ERROR_SUCCESS if the operation was successful or + /// one of the following error codes: + /// - ERROR_NOT_FOUND: If the library isn't already loaded. + /// + HRESULT getLoadedLibrary(const wstring& libPath, HMODULE& rLib); + /// + /// Loads the specified library and register it in the 'loadedLibraries' map. + /// + /// The path to the dll binary to be loaded. + /// + /// An error code specifying ERROR_SUCCESS if the operation was successful or + /// one of the following error codes: + /// + HRESULT loadLibrary(const wstring& libPath, HMODULE& rHLib); + /// + /// Loads the library associated with a particular setting Id. + /// + HRESULT loadSettingLibrary(const wstring& settingId, HMODULE& lib); + +public: + /// + /// Empty constructor. + /// + SettingAPI(); + /// + /// Desctructor, it deinitialize loaded libraries in the proper order. + /// + /// ~SettingAPI(); + /// + /// Copy constructor operator. + /// + /// + /// + SettingAPI& operator=(SettingAPI& other) = delete; + /// + /// Loads the base setting exposed through the DLL. + /// + /// The setting id to be loaded. + /// A reference to the SettingItem to be filled. + /// + /// An HRESULT error if the operation failed or ERROR_SUCCESS. Possible errors: + /// - ERROR_OPEN_FAILED: If the inner GetSettingDLL operation fails. + /// - E_OUTOFMEMORY: If the system runs out of memory and WindowsCreateString fails. + /// - ERROR_MOD_NOT_FOUND: If the LoadLibrary function fails. + /// + HRESULT loadBaseSetting(const std::wstring& settingId, SettingItem& settingItem); + /// + /// Gets the settings inside a collection. + /// + /// + /// + /// + HRESULT getCollectionSettings(SettingItem& collSetting, vector& rSettings); + /// + /// Load the settingsi + /// + /// + /// + /// + /// + HRESULT getCollectionSettings(const vector& ids, SettingItem& collSetting, vector& rSettings); + /// + /// + /// + /// + /// + /// + /// + HRESULT getCollectionSetting(const wstring& id, SettingItem& settingCollection, SettingItem& rSetting); + + /// + /// Initializes the SettingAPI. + /// + friend SettingAPI& LoadSettingAPI(HRESULT& rErrCode); + /// + /// Deinitializes the SettingAPI. + /// + /// + friend HRESULT UnloadSettingsAPI(SettingAPI& rSAPI); +}; + +#endif // F__SettingUtils_H \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/SettingsHelperLib.vcxproj b/settingsHelper/SettingsHelperLib/SettingsHelperLib.vcxproj new file mode 100644 index 000000000..7f94ae120 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/SettingsHelperLib.vcxproj @@ -0,0 +1,203 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 15.0 + {5950CFD8-254D-41B9-A743-ECA34C2AD788} + Win32Proj + SettingsHelperLib + 10.0.10240.0 + true + + + + StaticLibrary + true + v140 + Unicode + + + StaticLibrary + false + v140 + true + Unicode + + + StaticLibrary + true + v140 + Unicode + true + + + StaticLibrary + false + v140 + false + Unicode + true + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + false + true + + + Windows + DebugFull + + + true + + + Windows + + + + + Use + Level3 + Disabled + true + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + true + + + Windows + true + + + + + Use + Level3 + MaxSpeed + true + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + true + + + Windows + true + true + true + + + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;LIB;%(PreprocessorDefinitions) + false + true + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + Create + Create + Create + + + + + + + \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/SettingsHelperLib.vcxproj.filters b/settingsHelper/SettingsHelperLib/SettingsHelperLib.vcxproj.filters new file mode 100644 index 000000000..d19eb0e49 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/SettingsHelperLib.vcxproj.filters @@ -0,0 +1,108 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/SettingsIIDs.h b/settingsHelper/SettingsHelperLib/SettingsIIDs.h new file mode 100644 index 000000000..516e27aa3 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/SettingsIIDs.h @@ -0,0 +1,119 @@ +#pragma once + +#ifndef I_SETTINGSIIDS_H +#define I_SETTINGSIIDS_H + +#include + +/// +/// +/// +IID IID_ISettingItem { + 0x40c037cc, + 0xd8bf, + 0x489e, + { + 0x86, + 0x97, + 0xd6, + 0x6b, + 0xaa, + 0x32, + 0x21, + 0xbf + } +}; +/// +/// +/// +IID IID_ISettingItem2 { + 0x32572999, + 0x399d, + 0x40ff, + { + 0xa4, + 0x23, + 0x35, + 0x95, + 0xb4, + 0xdc, + 0x99, + 0x3b + } +}; + +// +6: SystemSettings::DataModel::CSettingBase::OnSettingsAppSuspending +// +7: SystemSettings::DataModel::CSettingBase::OnSettingsAppResuming +IID IID_ISettingsAppNotification { + 0xdff43ddc, + 0x7d1d, + 0x4899, + { + 0x94, + 0xc5, + 0xa2, + 0xa0, + 0x6e, + 0x17, + 0x77, + 0x12 + } +}; + +// +6: SystemSettings::DataModel::CSettingBase::GetPageSessionId(struct* _GUID) +// +7: SystemSettings::DataModel::CSettingBase::SetPageSessionId(struct _GUID) +IID IID_ISettingTelemetry { + 0x7cf1e617, + 0xfe1e, + 0x48ef, + { + 0x89, + 0x62, + 0x40, + 0xa4, + 0x25, + 0xd2, + 0x1d, + 0x7e + } +}; + +/// +/// +/// +IID IID_IWeakReferenceSource { + 0x00000038, + 0x0000, + 0x0000, + { + 0xc0, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x46 + } +}; + +/// +/// +/// +IID IID_ISettingObservableVector { + 0xc5267a7c, + 0x640e, + 0x5cfc, + { + 0xa7, + 0xcc, + 0x40, + 0x1c, + 0xcf, + 0x42, + 0x6e, + 0x36 + } +}; + +#endif diff --git a/settingsHelper/SettingsHelperLib/StringConversion.cpp b/settingsHelper/SettingsHelperLib/StringConversion.cpp new file mode 100644 index 000000000..79ea7bb47 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/StringConversion.cpp @@ -0,0 +1,18 @@ +#include "stdafx.h" + +#include "StringConversion.h" + +#include + +HRESULT getInnerString(System::String^ sysStr, wstring& rWString) { + HRESULT res { ERROR_SUCCESS }; + + try { + wstring tempStr = msclr::interop::marshal_as(sysStr); + rWString = tempStr; + } catch(...) { + res = E_INVALIDARG; + } + + return res; +} \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/StringConversion.h b/settingsHelper/SettingsHelperLib/StringConversion.h new file mode 100644 index 000000000..5fe018f5d --- /dev/null +++ b/settingsHelper/SettingsHelperLib/StringConversion.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +#include + +using std::wstring; + +/// +/// Converts a managed System::String into an unmanaged std::wstring. +/// +/// +/// The System::String to be converted into an unmanaged string. +/// +/// +/// A reference to an wstring to be filled with the contents of the managed string. +/// +/// +/// ERROR_SUCCESS in case of success or E_INVALIDARG if something failed. +/// +HRESULT getInnerString(System::String^ sysStr, wstring& rWString); \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/stdafx.cpp b/settingsHelper/SettingsHelperLib/stdafx.cpp new file mode 100644 index 000000000..fd4f341c7 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/stdafx.cpp @@ -0,0 +1 @@ +#include "stdafx.h" diff --git a/settingsHelper/SettingsHelperLib/stdafx.h b/settingsHelper/SettingsHelperLib/stdafx.h new file mode 100644 index 000000000..821fbecc9 --- /dev/null +++ b/settingsHelper/SettingsHelperLib/stdafx.h @@ -0,0 +1,13 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, but +// are changed infrequently +// + +#pragma once + +#include "targetver.h" + +// Exclude rarely-used stuff from Windows headers +#define WIN32_LEAN_AND_MEAN + +// reference additional headers your program requires here diff --git a/settingsHelper/SettingsHelperLib/targetver.h b/settingsHelper/SettingsHelperLib/targetver.h new file mode 100644 index 000000000..87c0086de --- /dev/null +++ b/settingsHelper/SettingsHelperLib/targetver.h @@ -0,0 +1,8 @@ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/settingsHelper/SettingsHelperTests/GlobalEnvironment.h b/settingsHelper/SettingsHelperTests/GlobalEnvironment.h new file mode 100644 index 000000000..523278c8b --- /dev/null +++ b/settingsHelper/SettingsHelperTests/GlobalEnvironment.h @@ -0,0 +1,21 @@ +#pragma once + +#ifndef __GLOBAL_ENVIRONMNET_TEARDOWN_GTEST_HH +#define __GLOBAL_ENVIRONMNET_TEARDOWN_GTEST_HH + +#include +#include + +class ComDLLsLibraryTearDown : public ::testing::Environment +{ +public: + virtual ~ComDLLsLibraryTearDown () = default; + + // Override this to define how to set up the environment. + virtual void SetUp(); + + // Override this to define how to tear down the environment. + virtual void TearDown(); +}; + +#endif // !__GLOBAL_ENVIRONMNET_GTEST_HH diff --git a/settingsHelper/SettingsHelperTests/ParsingTests.cpp b/settingsHelper/SettingsHelperTests/ParsingTests.cpp new file mode 100644 index 000000000..eef9a1d85 --- /dev/null +++ b/settingsHelper/SettingsHelperTests/ParsingTests.cpp @@ -0,0 +1,100 @@ +#include "pch.h" +#include +#include + +#include +#include + +#pragma comment (lib, "WindowsApp.lib") + +using namespace ABI::Windows::Foundation; + +TEST(ParseJSONPayload, parseSampleJSON) { + ABI::Windows::Data::Json::IJsonObjectStatics* jsonArrayFactory = NULL; + HSTRING rJSONClass = NULL; + WindowsCreateString( + RuntimeClass_Windows_Data_Json_JsonObject, + static_cast(wcslen(RuntimeClass_Windows_Data_Json_JsonObject)), + &rJSONClass + ); + + GetActivationFactory(rJSONClass, &jsonArrayFactory); + HSTRING hJSON; + PCWSTR pJSON = L"{\"main\":\"Hello World\"}"; + WindowsCreateString(pJSON, static_cast(wcslen(pJSON)), &hJSON); + + ABI::Windows::Data::Json::IJsonObject* jObject = NULL; + jsonArrayFactory->Parse(hJSON, &jObject); + + HSTRING hMainContent = NULL; + HSTRING hMainName; + PCWSTR pMainName = L"main"; + WindowsCreateString(pMainName, static_cast(wcslen(pMainName)), &hMainName); + + jObject->GetNamedString(hMainName, &hMainContent); + + UINT32 length = 0; + PCWSTR pMainContent = WindowsGetStringRawBuffer(hMainContent, &length); + + std::wstring sMainContent{ pMainContent }; + + EXPECT_EQ(std::wstring(L"Hello World"), sMainContent); +} + +TEST(ParseJSONPayload, parseExamplePayload) { + std::wstring payload { L"[ { \"main\" : \"Hello World\" } ]" }; + std::vector> actions {}; + + HRESULT res = parsePayload(payload, actions); + EXPECT_EQ(E_INVALIDARG, res); + EXPECT_EQ(WEB_E_JSON_VALUE_NOT_FOUND, actions.front().second); +} + +const wstring getPayload = LR"foo( +[ + { + "settingID": "SystemSettings_Accessibility_Magnifier_IsEnabled", + "method" : "GetValue" + } +] +)foo"; + +TEST(ParseJSONPayload, getPayloadMagnifier) { + std::vector> actions {}; + + HRESULT res = parsePayload(getPayload, actions); + EXPECT_EQ(ERROR_SUCCESS, res); + ASSERT_EQ(1, actions.size()); + + EXPECT_EQ(actions.front().first.settingID, std::wstring { L"SystemSettings_Accessibility_Magnifier_IsEnabled" }); + EXPECT_EQ(actions.front().first.method, std::wstring{ L"GetValue" }); +} + +const wstring setPayload = LR"foo( +[ + { + "settingID": "SystemSettings_Accessibility_Magnifier_IsEnabled", + "method": "SetValue", + "parameters": [ true ] + } +] +)foo"; + +TEST(ParseJSONPayload, setPayloadMagnifier) { + std::vector> actions {}; + + HRESULT res = parsePayload(setPayload, actions); + + EXPECT_EQ(ERROR_SUCCESS, res); + ASSERT_EQ(1, actions.size()); + + EXPECT_EQ(actions.front().first.settingID, std::wstring { L"SystemSettings_Accessibility_Magnifier_IsEnabled" }); + EXPECT_EQ(actions.front().first.method, std::wstring{ L"SetValue" }); + + ASSERT_EQ(1, actions.front().first.params.size()); + boolean paramValue = false; + res = actions.front().first.params.front().iPropVal->GetBoolean(¶mValue); + EXPECT_EQ(ERROR_SUCCESS, res); + + EXPECT_EQ(paramValue, boolean {true}); +} diff --git a/settingsHelper/SettingsHelperTests/SettingItemTests.cpp b/settingsHelper/SettingsHelperTests/SettingItemTests.cpp new file mode 100644 index 000000000..2cbcb3492 --- /dev/null +++ b/settingsHelper/SettingsHelperTests/SettingItemTests.cpp @@ -0,0 +1,578 @@ +#include "pch.h" +#include +#include +#include +#include +#include +#include + +#include "GlobalEnvironment.h" + +#include +#include + +#include +#include + +#include +#include +#include +#include + +using std::vector; +using std::pair; +using std::wstring; + +#pragma comment (lib, "WindowsApp.lib") +#include +#pragma comment(lib, "mscoree.lib") + +using namespace ABI::Windows::Foundation; + +HRESULT apiErrCode { ERROR_SUCCESS }; +SettingAPI& sAPI { LoadSettingAPI(apiErrCode) }; + +void ComDLLsLibraryTearDown::SetUp() { + ASSERT_EQ(apiErrCode, ERROR_SUCCESS); +} + +void ComDLLsLibraryTearDown::TearDown() { + UnloadSettingsAPI(sAPI); +} + +TEST(GetSettingDLL, UsingSettingId) { + std::wstring settingDLL { L"" }; + std::wstring settingId { L"SystemSettings_Notifications_DoNotDisturb_Toggle" }; + + auto getDLLRes = getSettingDLL(settingId, settingDLL); + + EXPECT_EQ(getDLLRes, ERROR_SUCCESS); + EXPECT_EQ(settingDLL, std::wstring { L"C:\\Windows\\System32\\SettingsHandlers_Notifications.dll" }); +} + +TEST(LoadBaseSetting, UsingSettingId) { + std::wstring settingId { L"SystemSettings_Notifications_DoNotDisturb_Toggle" }; + HRESULT res { ERROR_SUCCESS }; + + { + SettingItem doNotDisturbSetting {}; + res = sAPI.loadBaseSetting(settingId, doNotDisturbSetting); + EXPECT_EQ(res, ERROR_SUCCESS); + } +} + +TEST(ParseSettingPath, SplitFunction) { + wstring settingId { + L".SystemSettings_Notifications_AppList.Microsoft\\.Windows\\.Cortana_cw5n1h2txyewy9." + }; + + vector idsPath = split(settingId, wstring { L"." }, wstring { L'\\' }); + vector idsResult { + L"", + L"SystemSettings_Notifications_AppList", + L"Microsoft\\.Windows\\.Cortana_cw5n1h2txyewy9", + L"" + }; + + ASSERT_EQ(idsPath, idsResult); +} + +TEST(ParseSettingPath, SplitSimplePath) { + wstring settingId { + L"SystemSettings_Notifications_AppList" + }; + + vector idsPath = split(settingId, wstring { L"." }, wstring { L'\\' }); + vector idsResult { + L"SystemSettings_Notifications_AppList" + }; + + ASSERT_EQ(idsPath, idsResult); +} + +TEST(ParseSettingPath, SplitSettingPath) { + wstring settingId { + L"SystemSettings_Notifications_AppList.Microsoft\\.Windows\\.Cortana_cw5n1h2txyewy9" + }; + vector idsPath {}; + + HRESULT res = splitSettingPath(settingId, idsPath); + ASSERT_EQ(res, ERROR_SUCCESS); + + vector idsResult { + L"SystemSettings_Notifications_AppList", + L"Microsoft\\.Windows\\.Cortana_cw5n1h2txyewy9" + }; + + ASSERT_EQ(idsPath, idsResult); +} + +TEST(GetSettingItem, GetMagnifierEnable) { + // This setting is well-known, and as many other settings, it's initialization + // takes some time. If the thread doesn't properly wait for its value to be + // changed, the value retrieved wont be the current one. + wstring settingId { L"SystemSettings_Accessibility_Magnifier_IsEnabled"}; + HRESULT res { ERROR_SUCCESS }; + + { + SettingItem doNotDisturbSetting {}; + res = sAPI.loadBaseSetting(settingId, doNotDisturbSetting); + EXPECT_EQ(res, ERROR_SUCCESS); + + if (res == ERROR_SUCCESS) { + ATL::CComPtr blueLightSetting = NULL; + res = doNotDisturbSetting.GetValue(L"Value", blueLightSetting); + + if (res == ERROR_SUCCESS) { + ATL::CComPtr blueLightProp = + static_cast(blueLightSetting.Detach()); + EXPECT_FALSE(blueLightProp == NULL); + + boolean curVal { false }; + blueLightProp->GetBoolean(&curVal); + EXPECT_EQ(curVal, boolean(false)); + } + } + } +} + +TEST(GetSettingItem, GetTaskbarLocation) { + wstring settingId { L"SystemSettings_Taskbar_Location" }; + + HRESULT res { ERROR_SUCCESS }; + + { + SettingItem taskbarLocation {}; + res = sAPI.loadBaseSetting(settingId, taskbarLocation); + EXPECT_EQ(res, ERROR_SUCCESS); + + if (res == ERROR_SUCCESS) { + ATL::CComPtr taskbarValue = NULL; + res = taskbarLocation.GetValue(L"Value", taskbarValue); + EXPECT_EQ(res, ERROR_SUCCESS); + } + } + +} + +TEST(GetSettingItem, GetInvalidSetting) { + CoInitializeEx(NULL, COINIT_MULTITHREADED); + + wstring settingId { L"SystemSettings_Accessibility_Display_DisplayBrightness"}; + HRESULT res { ERROR_SUCCESS }; + + { + SettingItem displayBrightness {}; + res = sAPI.loadBaseSetting(settingId, displayBrightness); + EXPECT_EQ(res, ERROR_SUCCESS); + // displayBrightness.setting.Detach()->Release(); + } + + System::GC::Collect(); + System::GC::WaitForPendingFinalizers(); + CoUninitialize(); + +} + +/// +/// This setting is well-known, and as many other settings, it's initialization +/// takes some time. If the thread doesn't properly wait for its value to be +/// changed, the value retrieved wont be the current one. +/// +/// This tests ensure that settings like this are being properly retrieved. +/// +TEST(GetSettingItem, GetKnownDelayedSetting) { + wstring settingId { L"SystemSettings_Display_BlueLight_ManualToggleQuickAction"}; + HRESULT res { ERROR_SUCCESS }; + + { + SettingItem doNotDisturbSetting{}; + res = sAPI.loadBaseSetting(settingId, doNotDisturbSetting); + EXPECT_EQ(res, ERROR_SUCCESS); + + if (res == ERROR_SUCCESS) { + ATL::CComPtr blueLightSetting = NULL; + res = doNotDisturbSetting.GetValue(L"Value", blueLightSetting); + + if (res == ERROR_SUCCESS) { + ATL::CComPtr blueLightProp = + static_cast(blueLightSetting.Detach()); + EXPECT_FALSE(blueLightProp == NULL); + + boolean curVal = false; + blueLightProp->GetBoolean(&curVal); + EXPECT_EQ(curVal, false); + } + } + } +} + +/// +/// Setting property "IsApplicable" provoques a segmentation fault in case +/// of being queried during load of 'MouseCursorCustomColor', it's the +/// only known setting to exhibit this behavior. But due to this, the +/// setting is no longer queried during setting loading operation. +/// +TEST(GetSettingItem, SegfaultErrorDueToIsApplicable) { + HRESULT errCode { ERROR_SUCCESS }; + + wstring settingId { L"SystemSettings_Accessibility_MouseCursorCustomColor" }; + wstring settingDLL {}; + HMODULE hLib { NULL }; + + errCode = getSettingDLL(settingId, settingDLL); + + if (errCode == ERROR_SUCCESS) { + SettingItem setting {}; + errCode = sAPI.loadBaseSetting(settingId, setting); + + EXPECT_EQ(errCode, ERROR_SUCCESS); + ATL::CComPtr iValue { NULL }; + errCode = setting.GetValue(L"Value", iValue); + + if (errCode == ERROR_SUCCESS) { + ATL::CComPtr pValue { + static_cast( + static_cast(iValue.Detach()) + ) + }; + } + } +} + +TEST(GetSettingItem, SegfaultDueToUnproperDLLUnloading) { + HRESULT errCode { ERROR_SUCCESS }; + + wstring settingId { L"SystemSettings_Gaming_XboxNetworkingAttemptFix" }; + wstring settingDLL {}; + HMODULE hLib { NULL }; + + errCode = getSettingDLL(settingId, settingDLL); + + if (errCode == ERROR_SUCCESS) { + SettingItem setting {}; + errCode = sAPI.loadBaseSetting(settingId, setting); + + EXPECT_EQ(errCode, ERROR_SUCCESS); + ATL::CComPtr iValue { NULL }; + errCode = setting.GetValue(L"Value", iValue); + + if (errCode == ERROR_SUCCESS) { + ATL::CComPtr pValue { + static_cast( + static_cast(iValue.Detach()) + ) + }; + } + } +} + +TEST(GetSettingValue, GetKnownSettingValue) { + std::wstring settingId { L"SystemSettings_Notifications_DoNotDisturb_Toggle" }; + SettingItem doNotDisturbSetting {}; + HRESULT res { ERROR_SUCCESS }; + + { + SettingItem doNotDisturbSetting{}; + res = sAPI.loadBaseSetting(settingId, doNotDisturbSetting); + + EXPECT_EQ(res, ERROR_SUCCESS); + + ATL::CComPtr toogleValue = NULL; + doNotDisturbSetting.GetValue(L"Value", toogleValue); + EXPECT_FALSE(toogleValue == NULL); + + wstring vValueStr { L"Alarms only" }; + VARIANT vValue {}; + vValue.vt = VARENUM::VT_BSTR; + vValue.bstrVal = const_cast(vValueStr.c_str()); + + ATL::CComPtr propValue { NULL }; + res = createPropertyValue(vValue, propValue); + EXPECT_EQ(res, ERROR_SUCCESS); + + res = doNotDisturbSetting.SetValue(L"Value", propValue); + EXPECT_EQ(res, ERROR_SUCCESS); + } +} + +/// +/// If base library isn't properly loaded, trying to retrieve some settings causes +/// segfaulting error. +/// +/// NOTE: This setting is only available in devices with a battery, so trying to load the setting +/// fails with result E_INVALIDARG. +/// +TEST(GetSettingValue, SegfaultDueToInvalidInitialization) { + HRESULT errCode { ERROR_SUCCESS }; + + wstring settingId { L"SystemSettings_BatterySaver_UsagePage_AppsBreakdown" }; + wstring settingDLL {}; + HMODULE hLib { NULL }; + + errCode = getSettingDLL(settingId, settingDLL); + + if (errCode == ERROR_SUCCESS) { + SettingItem setting {}; + errCode = sAPI.loadBaseSetting(settingId, setting); + EXPECT_EQ(errCode, E_INVALIDARG); + } +} + +/// +/// If multiple base settings are loaded without any delay between the loads, +/// a queue will be exhausted and an exception will be arised from inside the DLL. +/// +TEST(GetSettingValue, AppFailsToUnload) { + HRESULT errCode { ERROR_SUCCESS }; + + wstring settingId { L"SystemSettings_HDR_Battery_InfoText" }; + wstring settingDLL {}; + HMODULE hLib { NULL }; + + errCode = getSettingDLL(settingId, settingDLL); + + if (errCode == ERROR_SUCCESS) { + SettingItem setting {}; + errCode = sAPI.loadBaseSetting(settingId, setting); + + EXPECT_EQ(errCode, ERROR_SUCCESS); + ATL::CComPtr iValue { NULL }; + errCode = setting.GetValue(L"Value", iValue); + + if (errCode == ERROR_SUCCESS) { + ATL::CComPtr pValue { + static_cast( + static_cast(iValue.Detach()) + ) + }; + } + } +} + +TEST(GetSettingValue, GetColorFilterSettingValue) { + HRESULT errCode { ERROR_SUCCESS }; + + wstring settingId { L"SystemSettings_Accessibility_ColorFiltering_FilterType" }; + wstring settingDLL {}; + + errCode = getSettingDLL(settingId, settingDLL); + EXPECT_EQ(errCode, ERROR_SUCCESS); + + if (errCode == ERROR_SUCCESS) { + SettingItem setting {}; + errCode = sAPI.loadBaseSetting(settingId, setting); + EXPECT_EQ(errCode, ERROR_SUCCESS); + } +} + +// TODO: Refactor doing something meaninful +TEST(GetSettingValue, GetCollectionValues) { + std::wstring settingId { L"SystemSettings_Notifications_AppList" }; + HRESULT res { ERROR_SUCCESS }; + + { + SettingItem setting {}; + res = sAPI.loadBaseSetting(settingId, setting); + EXPECT_EQ(res, ERROR_SUCCESS); + + vector>> collectionValues {}; + // res = getCollectionValues(setting, collectionValues); + EXPECT_EQ(res, ERROR_SUCCESS); + // EXPECT_TRUE(collectionValues.size() > 0); + } +} + +TEST(GetSettingValue, GetNestedSetting) { + std::wstring settingId { + L"SystemSettings_QuietMoments_On_Scheduled_Mode.SystemSettings_QuietMoments_Scheduled_Mode_StartTime" + }; + HRESULT res { ERROR_SUCCESS }; + + vector settingIds {}; + res = splitSettingPath(settingId, settingIds); + EXPECT_EQ(res, ERROR_SUCCESS); + + { + SettingItem baseSetting {}; + res = sAPI.loadBaseSetting(settingIds[0], baseSetting); + EXPECT_EQ(res, ERROR_SUCCESS); + + ATL::CComPtr value; + res = baseSetting.GetValue(settingIds[1], value); + EXPECT_EQ(res, ERROR_SUCCESS); + EXPECT_TRUE(value != NULL); + } +} + +TEST(GetSettingValue, SetCollectionNestedSetting) { + std::wstring settingId { + L"SystemSettings_Notifications_AppList.Settings.SystemSettings_Notifications_AppNotificationSoundToggle" + }; + HRESULT res { ERROR_SUCCESS }; + + vector settingIds {}; + res = splitSettingPath(settingId, settingIds); + EXPECT_EQ(res, ERROR_SUCCESS); + + { + SettingItem baseSetting {}; + res = sAPI.loadBaseSetting(settingIds[0], baseSetting); + EXPECT_EQ(res, ERROR_SUCCESS); + + { + SettingItem colSetting {}; + res = sAPI.getCollectionSetting(settingIds[1], baseSetting, colSetting); + EXPECT_EQ(res, ERROR_SUCCESS); + + { + ATL::CComPtr innerValue { NULL }; + res = colSetting.GetValue(settingIds[2], innerValue); + EXPECT_EQ(res, ERROR_SUCCESS); + + VARIANT vValue {}; + vValue.vt = VARENUM::VT_BOOL; + vValue.boolVal = true; + + ATL::CComPtr propValue { NULL }; + res = createPropertyValue(vValue, propValue); + EXPECT_EQ(res, ERROR_SUCCESS); + + res = colSetting.SetValue(settingIds[2], propValue); + EXPECT_EQ(res, ERROR_SUCCESS); + } + } + } +} + +TEST(GetSettingValue, GetSettingsNestedSetting) { + std::wstring settingId { + L"SystemSettings_Notifications_AppList.Settings.SystemSettings_Notifications_AppNotifications" + }; + HRESULT res { ERROR_SUCCESS }; + vector settingIds {}; + + res = splitSettingPath(settingId, settingIds); + EXPECT_EQ(res, ERROR_SUCCESS); + + { + SettingItem baseSetting {}; + res = sAPI.loadBaseSetting(settingIds[0], baseSetting); + EXPECT_EQ(res, ERROR_SUCCESS); + + { + SettingItem colSetting {}; + res = sAPI.getCollectionSetting(settingIds[1], baseSetting, colSetting); + EXPECT_EQ(res, ERROR_SUCCESS); + + { + ATL::CComPtr innerValue { NULL }; + res = colSetting.GetValue(settingIds[2], innerValue); + EXPECT_EQ(res, ERROR_SUCCESS); + + VARIANT vValue {}; + vValue.vt = VARENUM::VT_BOOL; + vValue.boolVal = true; + + ATL::CComPtr propValue { NULL }; + res = createPropertyValue(vValue, propValue); + EXPECT_EQ(res, ERROR_SUCCESS); + + res = colSetting.SetValue(settingIds[2], propValue); + EXPECT_EQ(res, ERROR_SUCCESS); + } + } + } +} + +TEST(GetSettingValue, GetCollectionNestedSetting) { + std::wstring settingId { + L"SystemSettings_Notifications_AppList.Settings.SystemSettings_Notifications_AppNotificationSoundToggle" + }; + HRESULT res { ERROR_SUCCESS }; + + vector settingIds {}; + res = splitSettingPath(settingId, settingIds); + EXPECT_EQ(res, ERROR_SUCCESS); + + { + SettingItem baseSetting {}; + res = sAPI.loadBaseSetting(settingIds.front(), baseSetting); + EXPECT_EQ(res, ERROR_SUCCESS); + + SettingItem colSetting {}; + res = sAPI.getCollectionSetting(settingIds[1], baseSetting, colSetting); + EXPECT_EQ(res, ERROR_SUCCESS); + + ATL::CComPtr value; + res = colSetting.GetValue(settingIds[2], value); + EXPECT_EQ(res, ERROR_SUCCESS); + EXPECT_TRUE(value != NULL); + } +} + +/* +/// +/// NOTE: This test should remain commented, as it's only used for development purposes. +/// +/// This test is used for trying to load all the possible settings present in the system, +/// in order to find which are faulty, or which libraries should not be used. +/// +TEST(GetAllSettingsValues, GetAllPossibleSettings) { + CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); + + HKEY hTestKey; + HRESULT res = RegOpenKeyEx( + HKEY_LOCAL_MACHINE, + TEXT("SOFTWARE\\Microsoft\\SystemSettings\\SettingId"), + 0, + KEY_READ, + &hTestKey + ); + + if (res == ERROR_SUCCESS) { + vector keys {}; + + res = getRegSubKeys(hTestKey, keys); + EXPECT_EQ(res, ERROR_SUCCESS); + + bool foundMark { false }; + + for (auto& setting : keys) { + if (setting == L"SystemSettings_Display_BlueLight_StatusInfo") { + foundMark = true; + } + + if (foundMark) { + wstring settingDLL {}; + res = getSettingDLL(setting, settingDLL); + + if (res == ERROR_SUCCESS) { + std::wcout << setting << L"\r\n"; + + try { + SettingItem settingItem; + res = sAPI.loadBaseSetting(setting, settingItem); + + if (res != E_NOTIMPL && res != E_INVALIDARG) { + EXPECT_EQ(res, ERROR_SUCCESS); + EXPECT_TRUE(settingItem.setting != NULL); + } + } catch(...) {} + // wait(); + } + + System::Threading::Thread::Sleep(300); + } + + if (setting == L"SystemSettings_Holographic_Environment_Reset") { + break; + } + } + + RegCloseKey(hTestKey); + } + + CoUninitialize(); +} +*/ \ No newline at end of file diff --git a/settingsHelper/SettingsHelperTests/SettingUtilsTests.cpp b/settingsHelper/SettingsHelperTests/SettingUtilsTests.cpp new file mode 100644 index 000000000..5b79b8391 --- /dev/null +++ b/settingsHelper/SettingsHelperTests/SettingUtilsTests.cpp @@ -0,0 +1,185 @@ +#include "pch.h" +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +using std::vector; +using std::pair; +using std::wstring; + +#pragma comment (lib, "WindowsApp.lib") +#include +#pragma comment(lib, "mscoree.lib") + +using namespace ABI::Windows::Foundation; + +TEST(CreatePropertyValue, CreateDateFromDouble) { + HRESULT res { ERROR_SUCCESS }; + + VARIANT propVarValue {}; + ATL::CComPtr iPropValue = NULL; + PropertyType iPropValueType = PropertyType::PropertyType_Empty; + + // Create the proper VARIANT + propVarValue.vt = VARENUM::VT_R8; + propVarValue.dblVal = static_cast(8.0); + + // Create the corresponding IPropertyValue from the VARIANT contents + res = createPropertyValue(propVarValue, iPropValue); + EXPECT_EQ(res, ERROR_SUCCESS); + + // Check the returned PropertyType + res = iPropValue->get_Type(&iPropValueType); + EXPECT_EQ(res, ERROR_SUCCESS); + EXPECT_EQ(iPropValueType, PropertyType::PropertyType_Double); + + DOUBLE retDouble { 0 }; + res = iPropValue->GetDouble(&retDouble); + + EXPECT_EQ(res, ERROR_SUCCESS); + EXPECT_EQ(retDouble, 8); +} + +TEST(CreatePropertyValue, CreateDateFromINT64) { + HRESULT res { ERROR_SUCCESS }; + + VARIANT propVarValue {}; + ATL::CComPtr iPropValue = NULL; + PropertyType iPropValueType = PropertyType::PropertyType_Empty; + + // Create the proper VARIANT + propVarValue.vt = VARENUM::VT_I8; + propVarValue.llVal = static_cast(8); + + // Create the corresponding IPropertyValue from the VARIANT contents + res = createPropertyValue(propVarValue, iPropValue); + EXPECT_EQ(res, ERROR_SUCCESS); + + // Check the returned PropertyType + res = iPropValue->get_Type(&iPropValueType); + EXPECT_EQ(res, ERROR_SUCCESS); + EXPECT_EQ(iPropValueType, PropertyType::PropertyType_Int64); + + INT64 retDouble { 0 }; + res = iPropValue->GetInt64(&retDouble); + + EXPECT_EQ(res, ERROR_SUCCESS); + EXPECT_EQ(retDouble, 8); +} + +TEST(CreatePropertyValue, CreateFromString) { + HRESULT res { ERROR_SUCCESS }; + + std::wstring settingDLL { L"TestValue" }; + VARIANT propVarValue {}; + ATL::CComPtr iPropValue = NULL; + + // Create the proper VARIANT + propVarValue.vt = VARENUM::VT_BSTR; + propVarValue.bstrVal = const_cast(settingDLL.c_str()); + + // Create the corresponding IPropertyValue from the VARIANT contents + res = createPropertyValue(propVarValue, iPropValue); + EXPECT_EQ(res, ERROR_SUCCESS); + + HSTRING iPropValueStr = NULL; + res = iPropValue->GetString(&iPropValueStr); + EXPECT_EQ(res, ERROR_SUCCESS); + + HSTRING hStrToCompare = NULL; + LPWSTR pStrToCompare = L"TestValue"; + res = WindowsCreateString(pStrToCompare, static_cast(wcslen(pStrToCompare)), &hStrToCompare); + EXPECT_EQ(res, ERROR_SUCCESS); + + INT32 cmpRes = 0; + res = WindowsCompareStringOrdinal(iPropValueStr, hStrToCompare, &cmpRes); + EXPECT_EQ(res, ERROR_SUCCESS); + EXPECT_EQ(cmpRes, 0); +} + +TEST(CreatePropertyValue, CreateTimeSpanFromString) { + HRESULT res { ERROR_SUCCESS }; + + std::wstring settingDLL { L"11:00:01" }; + VARIANT propVarValue {}; + ATL::CComPtr iPropValue = NULL; + PropertyType iPropValueType = PropertyType::PropertyType_Empty; + + // Create the proper VARIANT + propVarValue.vt = VARENUM::VT_BSTR; + propVarValue.bstrVal = const_cast(settingDLL.c_str()); + + // Create the corresponding IPropertyValue from the VARIANT contents + res = createPropertyValue(propVarValue, iPropValue); + EXPECT_EQ(res, ERROR_SUCCESS); + + // Check the returned PropertyType + HSTRING iPropTimeSpan = NULL; + res = iPropValue->get_Type(&iPropValueType); + EXPECT_EQ(res, ERROR_SUCCESS); + EXPECT_EQ(iPropValueType, PropertyType::PropertyType_TimeSpan); + + TimeSpan abiRetTypeSpan { 0 }; + res = iPropValue->GetTimeSpan(&abiRetTypeSpan); + EXPECT_EQ(res, ERROR_SUCCESS); + + System::TimeSpan^ rtTimeSpan = gcnew System::TimeSpan(abiRetTypeSpan.Duration); + System::Globalization::CultureInfo^ culture = gcnew System::Globalization::CultureInfo{ L"en-US" }; + System::String^ rtStrTimeSpan = rtTimeSpan->ToString("c", culture); + System::String^ rtStrToCompare = gcnew System::String( L"11:00:01" ); + + System::StringComparer^ strComparer = System::StringComparer::Create(culture, true); + INT32 strCmpRes = strComparer->Compare(rtStrTimeSpan, rtStrToCompare); + EXPECT_EQ(res, ERROR_SUCCESS); + EXPECT_EQ(strCmpRes, 0); +} + +TEST(CreatePropertyValue, CreateDateFromString) { + HRESULT res { ERROR_SUCCESS }; + + std::wstring settingDLL { L"5/1/2008 6:00:00 AM +00:00" }; + VARIANT propVarValue {}; + ATL::CComPtr iPropValue = NULL; + PropertyType iPropValueType = PropertyType::PropertyType_Empty; + + // Create the proper VARIANT + propVarValue.vt = VARENUM::VT_BSTR; + propVarValue.bstrVal = const_cast(settingDLL.c_str()); + + // Create the corresponding IPropertyValue from the VARIANT contents + res = createPropertyValue(propVarValue, iPropValue); + EXPECT_EQ(res, ERROR_SUCCESS); + + // Check the returned PropertyType + HSTRING iPropTimeSpan = NULL; + res = iPropValue->get_Type(&iPropValueType); + EXPECT_EQ(res, ERROR_SUCCESS); + EXPECT_EQ(iPropValueType, PropertyType::PropertyType_DateTime); + + DateTime abiRetTypeSpan { 0 }; + res = iPropValue->GetDateTime(&abiRetTypeSpan); + EXPECT_EQ(res, ERROR_SUCCESS); + + System::DateTime^ rtDateTime = gcnew System::DateTime(abiRetTypeSpan.UniversalTime, System::DateTimeKind::Utc); + System::Globalization::CultureInfo^ culture = gcnew System::Globalization::CultureInfo { L"en-US" }; + System::String^ rtStrTimeSpan = rtDateTime->ToString("", culture); + + System::String^ rtStrToCompare = gcnew System::String(L"5/1/2008 6:00:00 AM"); + System::DateTime^ rtDateTimeToCompare = System::DateTime::Parse(rtStrToCompare, culture, System::Globalization::DateTimeStyles::AssumeUniversal); + + INT32 strCmpRes = System::DateTime::Compare(*rtDateTime, *rtDateTimeToCompare); + + EXPECT_EQ(res, ERROR_SUCCESS); + EXPECT_EQ(strCmpRes, 0); +} diff --git a/settingsHelper/SettingsHelperTests/SettingsHelperTests.vcxproj b/settingsHelper/SettingsHelperTests/SettingsHelperTests.vcxproj new file mode 100644 index 000000000..aaf63c8c8 --- /dev/null +++ b/settingsHelper/SettingsHelperTests/SettingsHelperTests.vcxproj @@ -0,0 +1,155 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {d938b6bb-3085-4a39-af75-8f4e339cc23d} + Win32Proj + 10.0.10240.0 + Application + v140 + Unicode + true + + + + true + + + true + + + + + + + + true + + + + + + + + + + Create + Create + Create + Create + + + + + + + {5950cfd8-254d-41b9-a743-eca34c2ad788} + + + + + + + + + + + + + Use + pch.h + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + Level3 + true + + + true + Console + + + + + Use + pch.h + Disabled + X64;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + Default + MultiThreadedDebugDLL + Level3 + $(SolutionDir)SettingsHelperLib;%(AdditionalIncludeDirectories) + ProgramDatabase + false + true + + + true + Console + + + true + + + + + Use + pch.h + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + true + + + true + Console + true + true + + + + + Use + pch.h + X64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + $(SolutionDir)SettingsHelperLib;%(AdditionalIncludeDirectories) + true + + + true + Console + true + true + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/settingsHelper/SettingsHelperTests/TestsMain.cpp b/settingsHelper/SettingsHelperTests/TestsMain.cpp new file mode 100644 index 000000000..8eebf66fe --- /dev/null +++ b/settingsHelper/SettingsHelperTests/TestsMain.cpp @@ -0,0 +1,12 @@ +#include "pch.h" + +#include +#include "GlobalEnvironment.h" + +int main(int argc, char **argv) { + ComDLLsLibraryTearDown* env = new ComDLLsLibraryTearDown(); + ::testing::AddGlobalTestEnvironment(env); + + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/settingsHelper/SettingsHelperTests/packages.config b/settingsHelper/SettingsHelperTests/packages.config new file mode 100644 index 000000000..0acd30a41 --- /dev/null +++ b/settingsHelper/SettingsHelperTests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/settingsHelper/SettingsHelperTests/pch.cpp b/settingsHelper/SettingsHelperTests/pch.cpp new file mode 100644 index 000000000..97b544ec1 --- /dev/null +++ b/settingsHelper/SettingsHelperTests/pch.cpp @@ -0,0 +1,6 @@ +// +// pch.cpp +// Include the standard header and generate the precompiled header. +// + +#include "pch.h" diff --git a/settingsHelper/SettingsHelperTests/pch.h b/settingsHelper/SettingsHelperTests/pch.h new file mode 100644 index 000000000..29c81fffa --- /dev/null +++ b/settingsHelper/SettingsHelperTests/pch.h @@ -0,0 +1,8 @@ +// +// pch.h +// Header for standard system include files. +// + +#pragma once + +#include "gtest/gtest.h" diff --git a/settingsHelper/examples/README.md b/settingsHelper/examples/README.md deleted file mode 100644 index 14c77fb86..000000000 --- a/settingsHelper/examples/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Examples - -Pipe these files into `WindowsSettings.exe`. - -* [magnifier.json](magnifier.json): Gets the current value of the magnifier (on or off), then turns it on. -* [night-light.json](night-light.json): Gets the current value of the blue-light setting, enables it, then returns the - new value. (Requires a real monitor). -* [taskbar.json](taskbar.json): Puts the taskbar on the top of the screen. diff --git a/settingsHelper/examples/magnifier.json b/settingsHelper/examples/magnifier.json deleted file mode 100644 index 38902c959..000000000 --- a/settingsHelper/examples/magnifier.json +++ /dev/null @@ -1,11 +0,0 @@ -[ - { - "settingID": "SystemSettings_Accessibility_Magnifier_IsEnabled", - "method": "GetValue" - }, - { - "settingID": "SystemSettings_Accessibility_Magnifier_IsEnabled", - "method": "SetValue", - "parameters": [ true ] - } -] diff --git a/settingsHelper/examples/night-light.json b/settingsHelper/examples/night-light.json deleted file mode 100644 index f19acead1..000000000 --- a/settingsHelper/examples/night-light.json +++ /dev/null @@ -1,6 +0,0 @@ -[ - { - "settingID": "SystemSettings_Display_BlueLight_ManualToggleQuickAction", - "method": "GetValue" - } -] diff --git a/settingsHelper/examples/taskbar.json b/settingsHelper/examples/taskbar.json deleted file mode 100644 index dc474db62..000000000 --- a/settingsHelper/examples/taskbar.json +++ /dev/null @@ -1,11 +0,0 @@ -[ - { - "settingID": "SystemSettings_Taskbar_Location", - "method": "GetValue" - }, - { - "settingID": "SystemSettings_Taskbar_Location", - "method": "SetValue", - "parameters": [ 1 ] - } -] From 66dd0adc34d397d47a29804f0c2bacc1e18d4fef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 15 Nov 2019 10:07:08 +0100 Subject: [PATCH 054/123] GPII-4152: Added new development dependencies necessary for building the native settingsHelper --- provisioning/Chocolatey.ps1 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/provisioning/Chocolatey.ps1 b/provisioning/Chocolatey.ps1 index b01ac70c7..6c943b7b0 100644 --- a/provisioning/Chocolatey.ps1 +++ b/provisioning/Chocolatey.ps1 @@ -30,4 +30,14 @@ Invoke-Command $chocolatey "install python2 -y" Add-Path $python2Path $true refreshenv +Invoke-Command $chocolatey "install nuget.commandline" +refreshenv + +# Install the required ATL Library & WindowsSDK for SystemSettingsHandler +Invoke-Command $chocolatey 'install --force -y vcbuildtools -ia "/InstallSelectableItems VisualCppBuildTools_ATLMFC_SDK;VisualCppBuildTools_NETFX_SDK"' +refreshenv + +Invoke-Command $chocolatey "install windows-sdk-10.0 -y" +refreshenv + exit 0 From 54df327f130cfbe8ea38fa16f5bfa83993454360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 15 Nov 2019 10:08:36 +0100 Subject: [PATCH 055/123] GPII-4152: Added new building steps for the native settingsHelper --- provisioning/NpmInstall.ps1 | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/provisioning/NpmInstall.ps1 b/provisioning/NpmInstall.ps1 index b739a235a..909f43b20 100755 --- a/provisioning/NpmInstall.ps1 +++ b/provisioning/NpmInstall.ps1 @@ -12,10 +12,11 @@ Import-Module (Join-Path $scriptDir 'Provisioning.psm1') -Force -Verbose $msbuild = Get-MSBuild "4.0" -# Build the settings helper +# Build the settingsHelper solution +nuget restore .\settingsHelper\SettingsHelper.sln -MSBuildVersion 4.0 $settingsHelperDir = Join-Path $rootDir "settingsHelper" -Invoke-Command $msbuild "SettingsHelper.sln /p:Configuration=Release /p:Platform=`"Any CPU`" /p:FrameworkPathOverride=`"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.1`"" $settingsHelperDir - +Invoke-Command $msbuild "SettingsHelper.sln /p:FrameworkPathOverride=`"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.1`" /p:Configuration=Release /p:Platform=`"x64`"" $settingsHelperDir +# Build the volumeControl solution $volumeControlDir = Join-Path $rootDir "gpii\node_modules\nativeSettingsHandler\nativeSolutions\VolumeControl" Invoke-Command $msbuild "VolumeControl.sln /p:Configuration=Release /p:Platform=`"x86`" /p:FrameworkPathOverride=`"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.1`"" $volumeControlDir From ed4c99e5f00c186a7fded8c3173f6f98550a4545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 15 Nov 2019 10:12:14 +0100 Subject: [PATCH 056/123] GPII-4152: Added project config to place app binary in the root bin folder --- settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj | 1 + 1 file changed, 1 insertion(+) diff --git a/settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj b/settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj index 5cbbd159b..b64b208f2 100644 --- a/settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj +++ b/settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj @@ -85,6 +85,7 @@ false + $(SolutionDir)..\bin From 55ca8356de6a7c0d1884a52cae7a87429512bd7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 15 Nov 2019 10:16:10 +0100 Subject: [PATCH 057/123] GPII-4152: Changed executable name to match replaced solution --- settingsHelper/SettingsHelper.sln | 5 +++-- settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/settingsHelper/SettingsHelper.sln b/settingsHelper/SettingsHelper.sln index b06db8ea8..78d9d3529 100644 --- a/settingsHelper/SettingsHelper.sln +++ b/settingsHelper/SettingsHelper.sln @@ -46,7 +46,8 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {7E8828CD-C502-44DC-934A-803E13AA5090} - SolutionGuid = {234D8C77-BC56-4A33-9214-B9391D1606FF} + SolutionGuid = {234D8C77-BC56-4A33-9214-B9391D1606FF} + SolutionGuid = {7E8828CD-C502-44DC-934A-803E13AA5090} + SolutionGuid = {18672BF6-20DA-4184-B947-45EC9ACDD423} EndGlobalSection EndGlobal diff --git a/settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj b/settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj index b64b208f2..9cdf41a34 100644 --- a/settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj +++ b/settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj @@ -76,6 +76,7 @@ true $(SolutionDir)..\bin + SettingsHelper true @@ -86,6 +87,7 @@ false $(SolutionDir)..\bin + SettingsHelper From 879136d391ea6723ade8c993771ac1de3e302b2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 15 Nov 2019 19:14:58 +0100 Subject: [PATCH 058/123] GPII-4152: Modified the installed sdk version and changed the way msbuild is called for 'SettingsHelper' --- provisioning/Chocolatey.ps1 | 2 +- provisioning/NpmInstall.ps1 | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/provisioning/Chocolatey.ps1 b/provisioning/Chocolatey.ps1 index 6c943b7b0..cab8a9b19 100644 --- a/provisioning/Chocolatey.ps1 +++ b/provisioning/Chocolatey.ps1 @@ -37,7 +37,7 @@ refreshenv Invoke-Command $chocolatey 'install --force -y vcbuildtools -ia "/InstallSelectableItems VisualCppBuildTools_ATLMFC_SDK;VisualCppBuildTools_NETFX_SDK"' refreshenv -Invoke-Command $chocolatey "install windows-sdk-10.0 -y" +Invoke-Command $chocolatey "install windows-sdk-10-version-1809-all --version=10.0.17763.1 -y" refreshenv exit 0 diff --git a/provisioning/NpmInstall.ps1 b/provisioning/NpmInstall.ps1 index 909f43b20..d038e252e 100755 --- a/provisioning/NpmInstall.ps1 +++ b/provisioning/NpmInstall.ps1 @@ -13,9 +13,11 @@ Import-Module (Join-Path $scriptDir 'Provisioning.psm1') -Force -Verbose $msbuild = Get-MSBuild "4.0" # Build the settingsHelper solution -nuget restore .\settingsHelper\SettingsHelper.sln -MSBuildVersion 4.0 +nuget restore .\settingsHelper\SettingsHelper.sln + $settingsHelperDir = Join-Path $rootDir "settingsHelper" -Invoke-Command $msbuild "SettingsHelper.sln /p:FrameworkPathOverride=`"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.1`" /p:Configuration=Release /p:Platform=`"x64`"" $settingsHelperDir +Invoke-Command $msbuild "SettingsHelper.sln /p:Configuration=Release /p:Platform=`"x64`"" $settingsHelperDir + # Build the volumeControl solution $volumeControlDir = Join-Path $rootDir "gpii\node_modules\nativeSettingsHandler\nativeSolutions\VolumeControl" Invoke-Command $msbuild "VolumeControl.sln /p:Configuration=Release /p:Platform=`"x86`" /p:FrameworkPathOverride=`"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5.1`"" $volumeControlDir From 9237c6fce5af15c658b02a178e82d50907a6a7b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 15 Nov 2019 19:18:30 +0100 Subject: [PATCH 059/123] GPII-4152: Modified project files for being able to build the project using visual studio 2015 --- settingsHelper/SettingsHelper.sln | 7 ++++--- .../SettingsHelperApp/SettingsHelperApp.vcxproj | 9 ++++++--- .../SettingsHelperLib/SettingsHelperLib.vcxproj | 5 ++++- .../SettingsHelperTests/SettingsHelperTests.vcxproj | 5 ++--- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/settingsHelper/SettingsHelper.sln b/settingsHelper/SettingsHelper.sln index 78d9d3529..5cc4e16c1 100644 --- a/settingsHelper/SettingsHelper.sln +++ b/settingsHelper/SettingsHelper.sln @@ -46,8 +46,9 @@ Global HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {234D8C77-BC56-4A33-9214-B9391D1606FF} - SolutionGuid = {7E8828CD-C502-44DC-934A-803E13AA5090} - SolutionGuid = {18672BF6-20DA-4184-B947-45EC9ACDD423} + SolutionGuid = {18672BF6-20DA-4184-B947-45EC9ACDD423} + SolutionGuid = {7E8828CD-C502-44DC-934A-803E13AA5090} + SolutionGuid = {234D8C77-BC56-4A33-9214-B9391D1606FF} + SolutionGuid = {98E79CF4-B371-4A98-9C84-1EEEA1B41C17} EndGlobalSection EndGlobal diff --git a/settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj b/settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj index 9cdf41a34..0a39a35fe 100644 --- a/settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj +++ b/settingsHelper/SettingsHelperApp/SettingsHelperApp.vcxproj @@ -23,8 +23,9 @@ {23C9A7E4-FEC8-4948-A232-809044EB871B} Win32Proj SettingsHelperApp - 10.0.10240.0 + 10.0.17763.0 true + false @@ -75,7 +76,7 @@ true - $(SolutionDir)..\bin + $(SolutionDir)..\bin\ SettingsHelper @@ -86,7 +87,7 @@ false - $(SolutionDir)..\bin + $(SolutionDir)..\bin\ SettingsHelper @@ -100,6 +101,7 @@ pch.h $(SolutionDir)SettingsHelperLib;%(AdditionalIncludeDirectories) true + $(FrameworkPathOverride) Console @@ -155,6 +157,7 @@ pch.h $(SolutionDir)SettingsHelperLib;%(AdditionalIncludeDirectories) true + $(FrameworkPathOverride) Console diff --git a/settingsHelper/SettingsHelperLib/SettingsHelperLib.vcxproj b/settingsHelper/SettingsHelperLib/SettingsHelperLib.vcxproj index 7f94ae120..a7bd41f07 100644 --- a/settingsHelper/SettingsHelperLib/SettingsHelperLib.vcxproj +++ b/settingsHelper/SettingsHelperLib/SettingsHelperLib.vcxproj @@ -23,8 +23,9 @@ {5950CFD8-254D-41B9-A743-ECA34C2AD788} Win32Proj SettingsHelperLib - 10.0.10240.0 + 10.0.17763.0 true + false @@ -115,6 +116,7 @@ WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) true true + $(FrameworkPathOverride) Windows @@ -151,6 +153,7 @@ NDEBUG;LIB;%(PreprocessorDefinitions) false true + $(FrameworkPathOverride) Windows diff --git a/settingsHelper/SettingsHelperTests/SettingsHelperTests.vcxproj b/settingsHelper/SettingsHelperTests/SettingsHelperTests.vcxproj index aaf63c8c8..6c3adad08 100644 --- a/settingsHelper/SettingsHelperTests/SettingsHelperTests.vcxproj +++ b/settingsHelper/SettingsHelperTests/SettingsHelperTests.vcxproj @@ -21,10 +21,11 @@ {d938b6bb-3085-4a39-af75-8f4e339cc23d} Win32Proj - 10.0.10240.0 + 10.0.17763.0 Application v140 Unicode + 4.5.1 true @@ -119,7 +120,6 @@ MultiThreadedDLL Level3 ProgramDatabase - true true @@ -137,7 +137,6 @@ Level3 ProgramDatabase $(SolutionDir)SettingsHelperLib;%(AdditionalIncludeDirectories) - true true From 54805668acd9fad43facfed79fbdb53ead992e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 15 Nov 2019 19:21:27 +0100 Subject: [PATCH 060/123] GPII-4152: Removed unused variable --- settingsHelper/SettingsHelperLib/BaseSettingItem.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/settingsHelper/SettingsHelperLib/BaseSettingItem.cpp b/settingsHelper/SettingsHelperLib/BaseSettingItem.cpp index 492bcb1f4..6e35198d2 100644 --- a/settingsHelper/SettingsHelperLib/BaseSettingItem.cpp +++ b/settingsHelper/SettingsHelperLib/BaseSettingItem.cpp @@ -231,7 +231,6 @@ UINT BaseSettingItem::Invoke() { ICoreWindow* core = NULL; ICoreWindowStatic* spCoreWindowStatic; ICoreWindow* spCoreWindow; - ICoreWindowInterop* spCoreWindowInterop; HSTRING strIWindowClassId; WindowsCreateString( From 0e22cff9f6f8ae453281a3fe573460b0360ce406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 15 Nov 2019 19:26:32 +0100 Subject: [PATCH 061/123] GPII-4152: Fixed warning with explicit conversion from 'bool' to 'boolean' --- settingsHelper/SettingsHelperTests/SettingItemTests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/settingsHelper/SettingsHelperTests/SettingItemTests.cpp b/settingsHelper/SettingsHelperTests/SettingItemTests.cpp index 2cbcb3492..b2e024bb9 100644 --- a/settingsHelper/SettingsHelperTests/SettingItemTests.cpp +++ b/settingsHelper/SettingsHelperTests/SettingItemTests.cpp @@ -199,9 +199,9 @@ TEST(GetSettingItem, GetKnownDelayedSetting) { static_cast(blueLightSetting.Detach()); EXPECT_FALSE(blueLightProp == NULL); - boolean curVal = false; + boolean curVal { false }; blueLightProp->GetBoolean(&curVal); - EXPECT_EQ(curVal, false); + EXPECT_EQ(curVal, boolean { false }); } } } From dde3b6d889439c38402436b0af0df8417cd25adf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 15 Nov 2019 20:15:04 +0100 Subject: [PATCH 062/123] GPII-4152: Fixed non-returning payload readed from standard input --- settingsHelper/SettingsHelperLib/PayloadProc.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/settingsHelper/SettingsHelperLib/PayloadProc.cpp b/settingsHelper/SettingsHelperLib/PayloadProc.cpp index 9e781e2a3..a9c9a931a 100644 --- a/settingsHelper/SettingsHelperLib/PayloadProc.cpp +++ b/settingsHelper/SettingsHelperLib/PayloadProc.cpp @@ -305,6 +305,10 @@ HRESULT getInputPayload(pair* pInput, wstring& rPayloadStr) { ); WaitForSingleObject(hInputReader, 1000); + + if (!inputStream.empty()) { + rPayloadStr = inputStream; + } } return errCode; From 1ee46a4678612b94d11a5053c71908152257e7c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Sat, 16 Nov 2019 11:58:48 +0100 Subject: [PATCH 063/123] GPII-4152: Uninstalled box windows-sdk-8.1 and reinstalled after windows-sdk-10 preventing sdk-8.1 corruption --- provisioning/Chocolatey.ps1 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/provisioning/Chocolatey.ps1 b/provisioning/Chocolatey.ps1 index cab8a9b19..b8a08f711 100644 --- a/provisioning/Chocolatey.ps1 +++ b/provisioning/Chocolatey.ps1 @@ -33,6 +33,9 @@ refreshenv Invoke-Command $chocolatey "install nuget.commandline" refreshenv +Invoke-Command $chocolatey "uninstall windows-sdk-8.1 -y" +refreshenv + # Install the required ATL Library & WindowsSDK for SystemSettingsHandler Invoke-Command $chocolatey 'install --force -y vcbuildtools -ia "/InstallSelectableItems VisualCppBuildTools_ATLMFC_SDK;VisualCppBuildTools_NETFX_SDK"' refreshenv @@ -40,4 +43,7 @@ refreshenv Invoke-Command $chocolatey "install windows-sdk-10-version-1809-all --version=10.0.17763.1 -y" refreshenv +Invoke-Command $chocolatey "install windows-sdk-8.1 -y" +refreshenv + exit 0 From d8a0e0fd42091fb7bd058f3d0ae6631162c5f156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Sat, 16 Nov 2019 11:59:45 +0100 Subject: [PATCH 064/123] GPII-4152: Added env variable selecting vstudio version for 'SettingsHelper' building --- provisioning/NpmInstall.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/provisioning/NpmInstall.ps1 b/provisioning/NpmInstall.ps1 index d038e252e..30bfd9b4b 100755 --- a/provisioning/NpmInstall.ps1 +++ b/provisioning/NpmInstall.ps1 @@ -15,6 +15,7 @@ $msbuild = Get-MSBuild "4.0" # Build the settingsHelper solution nuget restore .\settingsHelper\SettingsHelper.sln +$env:VisualStudioVersion = '15.0' $settingsHelperDir = Join-Path $rootDir "settingsHelper" Invoke-Command $msbuild "SettingsHelper.sln /p:Configuration=Release /p:Platform=`"x64`"" $settingsHelperDir From e9508fe5726d408072df989123c2cb12f4d436da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Sat, 16 Nov 2019 14:25:52 +0100 Subject: [PATCH 065/123] GPII-4152: Temporarly added more resources to VM to try to make CI pass on time --- Vagrantfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index 60508370e..c4cf68359 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -5,8 +5,8 @@ Vagrant.require_version ">= 1.8.0" # By default this VM will use 2 processor cores and 2GB of RAM. The 'VM_CPUS' and # "VM_RAM" environment variables can be used to change that behaviour. -cpus = ENV["VM_CPUS"] || 2 -ram = ENV["VM_RAM"] || 2048 +cpus = ENV["VM_CPUS"] || 4 +ram = ENV["VM_RAM"] || 4096 Vagrant.configure(2) do |config| From 7cd288b56ea0348af3e706e11f31fea639f87a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Mon, 18 Nov 2019 12:23:15 +0100 Subject: [PATCH 066/123] GPII-4152: Reversed the VM resources to regular ones --- Vagrantfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index c4cf68359..60508370e 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -5,8 +5,8 @@ Vagrant.require_version ">= 1.8.0" # By default this VM will use 2 processor cores and 2GB of RAM. The 'VM_CPUS' and # "VM_RAM" environment variables can be used to change that behaviour. -cpus = ENV["VM_CPUS"] || 4 -ram = ENV["VM_RAM"] || 4096 +cpus = ENV["VM_CPUS"] || 2 +ram = ENV["VM_RAM"] || 2048 Vagrant.configure(2) do |config| From e572ae7470465693f126a57a614f8ebb4555080b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Mon, 18 Nov 2019 12:25:00 +0100 Subject: [PATCH 067/123] GPII-4152: Refactored set method using helper function encapsulating waiting logic --- .../SettingsHelperLib/SettingItem.cpp | 204 +++++++++--------- .../SettingsHelperLib/SettingItem.h | 24 ++- 2 files changed, 123 insertions(+), 105 deletions(-) diff --git a/settingsHelper/SettingsHelperLib/SettingItem.cpp b/settingsHelper/SettingsHelperLib/SettingItem.cpp index 8f0935211..dad63aa70 100644 --- a/settingsHelper/SettingsHelperLib/SettingItem.cpp +++ b/settingsHelper/SettingsHelperLib/SettingItem.cpp @@ -56,91 +56,116 @@ UINT SettingItem::GetValue(wstring id, ATL::CComPtr& item) { if (this->setting == NULL) { return ERROR_INVALID_HANDLE_STATE; }; if (id.empty()) { return E_INVALIDARG; }; - HRESULT res = ERROR_SUCCESS; - BOOL isUpdating = false; + HRESULT errCode { ERROR_SUCCESS }; + BOOL isUpdating { false }; + HSTRING hId { NULL }; + ATL::CComPtr _item { NULL }; - HSTRING hId = NULL; - res = WindowsCreateString(id.c_str(), static_cast(id.size()), &hId); + errCode = WindowsCreateString(id.c_str(), static_cast(id.size()), &hId); + if (errCode != ERROR_SUCCESS) { goto cleanup; } - IInspectable* curValue = NULL; - if (res == ERROR_SUCCESS) { - if (id == L"Value" || id == L"DynamicSettingsDatabaseValue") { - res = BaseSettingItem::GetValue(id, item); + if (id == L"Value" || id == L"DynamicSettingsDatabaseValue") { + errCode = BaseSettingItem::GetValue(id, _item); + + if (errCode == ERROR_SUCCESS) { + item = _item; + } + } else { + // Access one of the inner Settings inside the + // DynamicSettingDatabase hold inside the setting. + wstring settingId {}; + if (isSupportedDb(this->parentId)) { + settingId = this->parentId; } else { - // Access one of the inner Settings inside the DynamicSettingDatabase - // holded inside the setting. - wstring settingId {}; - if (isSupportedDb(this->parentId)) { - settingId = this->parentId; - } else { - settingId = this->settingId; - } + settingId = this->settingId; + } - if (isSupportedDb(settingId)) { - if (this->dbSettings.empty()) { - DynamicSettingDatabase dynSettingDb {}; - res = loadSettingDatabase(settingId, *this, dynSettingDb); + if (isSupportedDb(settingId)) { + if (this->dbSettings.empty()) { + DynamicSettingDatabase dynSettingDb {}; + errCode = loadSettingDatabase(settingId, *this, dynSettingDb); - if (res == ERROR_SUCCESS) { - res = dynSettingDb.GetDatabaseSettings(this->dbSettings); - } + if (errCode == ERROR_SUCCESS) { + errCode = dynSettingDb.GetDatabaseSettings(this->dbSettings); } + } - if (res == ERROR_SUCCESS) { - for (auto& setting : this->dbSettings) { - if (id == setting.settingId) { - res = setting.GetValue(L"Value", item); - break; - } + if (errCode == ERROR_SUCCESS) { + for (auto& setting : this->dbSettings) { + if (id == setting.settingId) { + errCode = setting.GetValue(L"Value", _item); + break; } } - } else { - // TODO: Improve error code - res = E_INVALIDARG; } - } + // The required DynamicDatabaseSetting hasn't been found + if (_item == NULL) { + errCode = E_INVALIDARG; + } + } else { + // TODO: Improve error code + errCode = E_INVALIDARG; + } } - return res; +cleanup: + if (hId != NULL) { WindowsDeleteString(hId); } + + return errCode; } -HRESULT SettingItem::SetValue(const wstring& id, ATL::CComPtr& item) { - if (this->setting == NULL) { return ERROR_INVALID_HANDLE_STATE; }; +HRESULT SettingItem::_SetValue(const wstring& id, const DbSettingItem& dbSetting, ATL::CComPtr& item) { + HRESULT errCode { ERROR_SUCCESS }; + BOOL completed { false }; + BOOL isUpdating { true }; + BOOL innerUpdating { false }; + BOOL setInnerSetting { dbSetting.setting != NULL }; - HRESULT res { ERROR_SUCCESS }; + ATL::CComPtr> handler = + new ITypedEventHandler(&completed); + EventRegistrationToken token { 0 }; - if (id == L"Value") { - BOOL completed = false; - BOOL isUpdating { true }; - UINT maxIt { 10 }; - - ATL::CComPtr> handler = - new ITypedEventHandler(&completed); - EventRegistrationToken token { 0 }; - - res = this->setting->add_SettingChanged(handler, &token); - - if (res == ERROR_SUCCESS) { - res = BaseSettingItem::SetValue(id, item); - - if (res == ERROR_SUCCESS) { - UINT it = 0; - while (completed != TRUE && isUpdating == TRUE) { - if (it < maxIt) { - this->setting->get_IsUpdating(&isUpdating); - System::Threading::Thread::Sleep(100); - - it++; - } else { - completed = true; - res = ERROR_TIMEOUT; + if (setInnerSetting) { + innerUpdating = true; + } + + errCode = this->setting->add_SettingChanged(handler, &token); + + if (errCode == ERROR_SUCCESS) { + errCode = BaseSettingItem::SetValue(id, item); + + if (errCode == ERROR_SUCCESS) { + UINT it = 0; + while (completed != TRUE && isUpdating || innerUpdating) { + if (it < this->maxIt) { + this->setting->get_IsUpdating(&isUpdating); + if (setInnerSetting) { + dbSetting.GetIsUpdating(&innerUpdating); } + + System::Threading::Thread::Sleep(100); + it++; + } else { + completed = true; + errCode = ERROR_TIMEOUT; } } } + } + + this->setting->remove_SettingChanged(token); + + return errCode; +} + +HRESULT SettingItem::SetValue(const wstring& id, ATL::CComPtr& item) { + if (this->setting == NULL) { return ERROR_INVALID_HANDLE_STATE; }; + + HRESULT errCode { ERROR_SUCCESS }; - this->setting->remove_SettingChanged(token); + if (id == L"Value") { + errCode = SettingItem::_SetValue(id, DbSettingItem {}, item); } else { // Access one of the inner Settings inside the DynamicSettingDatabase // holded inside the setting. @@ -152,59 +177,36 @@ HRESULT SettingItem::SetValue(const wstring& id, ATL::CComPtr& i } if (isSupportedDb(settingId)) { + BOOL applied { false }; + if (this->dbSettings.empty()) { DynamicSettingDatabase dynSettingDb {}; - res = loadSettingDatabase(settingId, *this, dynSettingDb); + errCode = loadSettingDatabase(settingId, *this, dynSettingDb); - if (res == ERROR_SUCCESS) { - res = dynSettingDb.GetDatabaseSettings(this->dbSettings); + if (errCode == ERROR_SUCCESS) { + errCode = dynSettingDb.GetDatabaseSettings(this->dbSettings); } } - if (res == ERROR_SUCCESS) { + if (errCode == ERROR_SUCCESS) { for (auto& setting : this->dbSettings) { if (id == setting.settingId) { - BOOL isUpdating { true }; - BOOL completed { false }; - BOOL innerUpdating { true }; - UINT maxIt { 10 }; - - ATL::CComPtr> handler = - new ITypedEventHandler(&completed); - EventRegistrationToken token { 0 }; - - res = this->setting->add_SettingChanged(handler, &token); - - res = setting.SetValue(L"Value", item); - - if (res == ERROR_SUCCESS) { - UINT it = 0; - while (completed != TRUE || isUpdating == TRUE || innerUpdating == TRUE) { - if (it < maxIt) { - this->setting->get_IsUpdating(&isUpdating); - setting.GetIsUpdating(&innerUpdating); - - System::Threading::Thread::Sleep(100); - - it++; - } else { - completed = true; - res = ERROR_TIMEOUT; - } - } - } - - res = this->setting->remove_SettingChanged(token); - + errCode = SettingItem::_SetValue(id, setting, item); + applied = TRUE; break; } } } + + // The required DynamicDatabaseSetting hasn't been found + if (applied == FALSE) { + errCode = E_INVALIDARG; + } } else { // TODO: Improve error code - res = E_INVALIDARG; + errCode = E_INVALIDARG; } } - return res; + return errCode; } diff --git a/settingsHelper/SettingsHelperLib/SettingItem.h b/settingsHelper/SettingsHelperLib/SettingItem.h index 9008cff1a..28bcf4240 100644 --- a/settingsHelper/SettingsHelperLib/SettingItem.h +++ b/settingsHelper/SettingsHelperLib/SettingItem.h @@ -23,14 +23,30 @@ using namespace ABI::Windows::Foundation::Collections; struct SettingItem : BaseSettingItem { private: + /// + /// Maximum number of times the delay for Getting/Setting + /// a setting value is waited. + /// + UINT maxIt { 10 }; + /// + /// Lazy vector with DBSettingItems supported by the + /// setting. This vector will be filled the first time this + /// settings are requested. + /// vector dbSettings {}; /// - /// This id specifies the parent setting of the current setting. This id is specially - /// useful in situations where the setting is a setting from a Collection, in this case, - /// we want to specify this Id as a way to know if we support accesing the inner db settings - /// of this particular setting. + /// This id specifies the parent setting of the current setting. + /// This id is specially useful in situations where the setting + /// is a setting from a Collection, in this case, we want + /// to specify this Id as a way to know if we support accesing + /// the inner db settings of this particular setting. /// wstring parentId {}; + /// + /// Private helper method encapsulating the waiting logic + /// necessary for properly set a new setting a new value. + /// + HRESULT _SetValue(const wstring& id, const DbSettingItem& dbSetting, ATL::CComPtr& item); public: using BaseSettingItem::BaseSettingItem; From a5d1ac8982cb6f3e757f358b66d4c7727be6bc43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Mon, 18 Nov 2019 12:26:01 +0100 Subject: [PATCH 068/123] GPII-4152: Removed unnecessary legacy operations in 'handlePayload' function --- settingsHelper/SettingsHelperLib/PayloadProc.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/settingsHelper/SettingsHelperLib/PayloadProc.cpp b/settingsHelper/SettingsHelperLib/PayloadProc.cpp index a9c9a931a..ad86f4247 100644 --- a/settingsHelper/SettingsHelperLib/PayloadProc.cpp +++ b/settingsHelper/SettingsHelperLib/PayloadProc.cpp @@ -330,18 +330,11 @@ HRESULT handlePayload(pair* pInput) { if (res == ERROR_SUCCESS) { for (const auto& action : operations) { if (action.second == ERROR_SUCCESS) { - SettingItem settingItem {}; - vector settingIds {}; + Result actionResult {}; + res = handleAction(sAPI, action.first, actionResult); - res = splitSettingPath(action.first.settingID, settingIds); - - if (res == ERROR_SUCCESS) { - Result actionResult {}; - res = handleAction(sAPI, action.first, actionResult); - - // Result should contain the error in case of failure - results.push_back(actionResult); - } + // Result should contain the error in case of failure + results.push_back(actionResult); } else { wstring errMsg { invalidPayloadMsg(action.second) }; From 2543fcc50b803343ed1644cd21e7033a06e309f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Mon, 18 Nov 2019 12:32:11 +0100 Subject: [PATCH 069/123] GPII-4231: Changed universal reference --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cf31b5880..ab6327b57 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dependencies": { "edge-js": "10.3.1", "ffi-napi": "2.4.3", - "gpii-universal": "JavierJF/universal#GPII-3810", + "gpii-universal": "JavierJF/universal#GPII-4231", "@pokusew/pcsclite": "0.4.18", "ref": "1.3.4", "ref-struct": "1.1.0", From 02c78d03e3891d36544dbfc7489fce3ce4d1cf0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Tue, 19 Nov 2019 13:51:12 +0100 Subject: [PATCH 070/123] GPII-4152: Added new checking get/set of required setting --- .../SettingsHelperTests/SettingItemTests.cpp | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/settingsHelper/SettingsHelperTests/SettingItemTests.cpp b/settingsHelper/SettingsHelperTests/SettingItemTests.cpp index b2e024bb9..b02ba66a2 100644 --- a/settingsHelper/SettingsHelperTests/SettingItemTests.cpp +++ b/settingsHelper/SettingsHelperTests/SettingItemTests.cpp @@ -366,6 +366,43 @@ TEST(GetSettingValue, GetColorFilterSettingValue) { } } +TEST(GetSettingValue, GetInputTouchSensitivitySetting) { + HRESULT errCode { ERROR_SUCCESS }; + + wstring settingId { L"SystemSettings_Input_Touch_SetActivationTimeout" }; + wstring settingDLL {}; + + errCode = getSettingDLL(settingId, settingDLL); + EXPECT_EQ(errCode, ERROR_SUCCESS); + + if (errCode == ERROR_SUCCESS) { + SettingItem setting {}; + errCode = sAPI.loadBaseSetting(settingId, setting); + EXPECT_EQ(errCode, ERROR_SUCCESS); + + ATL::CComPtr iValue { NULL }; + errCode = setting.GetValue(L"Value", iValue); + + if (errCode == ERROR_SUCCESS) { + ATL::CComPtr pValue { + static_cast( + static_cast(iValue.Detach()) + ) + }; + + VARIANT newVal {}; + newVal.vt = VARENUM::VT_BSTR; + newVal.bstrVal = L"Medium sensitivity"; + + ATL::CComPtr newPValue { NULL }; + createPropertyValue(newVal, newPValue); + + setting.SetValue(L"Value", newPValue); + EXPECT_EQ(errCode, ERROR_SUCCESS); + } + } +} + // TODO: Refactor doing something meaninful TEST(GetSettingValue, GetCollectionValues) { std::wstring settingId { L"SystemSettings_Notifications_AppList" }; From ada0636c95b3d81143e4edf5eed6a39f4eb543f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Tue, 19 Nov 2019 13:53:00 +0100 Subject: [PATCH 071/123] GPII-4152: Added support for serializing and comparing string value payloads --- .../SettingsHelperLib/IPropertyValueUtils.cpp | 21 ++++++++++++++++++- settingsHelper/SettingsHelperLib/Payload.cpp | 21 ++++++++++++++++++- .../SettingsHelperLib/SettingUtils.cpp | 11 ++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/settingsHelper/SettingsHelperLib/IPropertyValueUtils.cpp b/settingsHelper/SettingsHelperLib/IPropertyValueUtils.cpp index e6f4cfc39..50b1ca959 100644 --- a/settingsHelper/SettingsHelperLib/IPropertyValueUtils.cpp +++ b/settingsHelper/SettingsHelperLib/IPropertyValueUtils.cpp @@ -293,8 +293,27 @@ HRESULT equals(ATL::CComPtr fstProp, ATL::CComPtrGetString(&fstInnerString); + if (errCode == ERROR_SUCCESS) { + UINT32 innerStringSz { 0 }; + LPCWSTR rawStr = WindowsGetStringRawBuffer(fstInnerString, &innerStringSz); + wstring fstStringValue { rawStr, innerStringSz }; + + errCode = sndProp->GetString(&sndInnerString); + + if (errCode == ERROR_SUCCESS) { + LPCWSTR rawStr = WindowsGetStringRawBuffer(sndInnerString, &innerStringSz); + wstring sndStringValue { rawStr, innerStringSz }; + + res = fstStringValue == sndStringValue; + } + } } else { - // TODO: Improve error code + // TODO: Improve error message errCode = E_NOTIMPL; } } diff --git a/settingsHelper/SettingsHelperLib/Payload.cpp b/settingsHelper/SettingsHelperLib/Payload.cpp index 335a93bd0..6c12fc6c3 100644 --- a/settingsHelper/SettingsHelperLib/Payload.cpp +++ b/settingsHelper/SettingsHelperLib/Payload.cpp @@ -473,6 +473,16 @@ HRESULT parsePayload(const wstring & payload, vector>& act return res; } +BOOL isNumber(wstring resVal) { + wchar_t* end { NULL }; + wcstol(resVal.c_str(), &end, 10); + return *end == 0; +} + +BOOL isBoolean(wstring resVal) { + return resVal == L"true" || resVal == L"false"; +} + // ------------------------ Serialization ---------------------------------- HRESULT serializeResult(const Result& result, std::wstring& str) { @@ -515,7 +525,16 @@ HRESULT serializeResult(const Result& result, std::wstring& str) { if (result.returnValue.empty()) { resultStr.append(L"null"); } else { - resultStr.append(result.returnValue); + BOOL isNotString { + isNumber(result.returnValue) || + isBoolean(result.returnValue) + }; + + if (isNotString) { + resultStr.append(result.returnValue); + } else { + resultStr.append(L"\"" + result.returnValue + L"\""); + } } // JSON object end diff --git a/settingsHelper/SettingsHelperLib/SettingUtils.cpp b/settingsHelper/SettingsHelperLib/SettingUtils.cpp index e11f92f30..75d780b5f 100644 --- a/settingsHelper/SettingsHelperLib/SettingUtils.cpp +++ b/settingsHelper/SettingsHelperLib/SettingUtils.cpp @@ -195,7 +195,18 @@ HRESULT toString(const ATL::CComPtr& propValue, wstring& rValueS res = E_INVALIDARG; } } + } else if (valueType == PropertyType::PropertyType_String) { + HSTRING innerString { NULL }; + + res = propValue->GetString(&innerString); + if (res == ERROR_SUCCESS) { + UINT32 innerStringSz { 0 }; + LPCWSTR rawStr = WindowsGetStringRawBuffer(innerString, &innerStringSz); + + rValueStr = wstring { rawStr, innerStringSz }; + } } else { + // TODO: Improve error message res = E_INVALIDARG; } } From f9ec64aafb5484f67cd49400c81d9273c45b3137 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Tue, 19 Nov 2019 13:54:21 +0100 Subject: [PATCH 072/123] GPII-4152: Added new tests for required setting and making tests restore initial settings --- .../test/testSystemSettingsHandler.js | 57 ++++++++++++++++++- 1 file changed, 55 insertions(+), 2 deletions(-) diff --git a/gpii/node_modules/systemSettingsHandler/test/testSystemSettingsHandler.js b/gpii/node_modules/systemSettingsHandler/test/testSystemSettingsHandler.js index 35e016da0..e04487a87 100644 --- a/gpii/node_modules/systemSettingsHandler/test/testSystemSettingsHandler.js +++ b/gpii/node_modules/systemSettingsHandler/test/testSystemSettingsHandler.js @@ -219,7 +219,8 @@ gpii.tests.windows.systemSettingsHandler.apiTests = fluid.freezeRecursive([ "test.windows.systemSettingsHandler": [{ settings: { "SystemSettings_Accessibility_Keyboard_WarningEnabled": {}, - "SystemSettings_Taskbar_Location": {} + "SystemSettings_Taskbar_Location": {}, + "SystemSettings_Input_Touch_SetActivationTimeout": {} }, options: { Async: true @@ -234,6 +235,9 @@ gpii.tests.windows.systemSettingsHandler.apiTests = fluid.freezeRecursive([ }, "SystemSettings_Taskbar_Location": { "value": 3 + }, + "SystemSettings_Input_Touch_SetActivationTimeout": { + "value": "Medium sensitivity" } } }] @@ -249,6 +253,9 @@ gpii.tests.windows.systemSettingsHandler.apiTests = fluid.freezeRecursive([ }, "SystemSettings_Taskbar_Location": { "value": 3 + }, + "SystemSettings_Input_Touch_SetActivationTimeout": { + "value": "Low sensitivity" } }, options: { @@ -274,6 +281,51 @@ gpii.tests.windows.systemSettingsHandler.apiTests = fluid.freezeRecursive([ "newValue": { "value": 3 } + }, + "SystemSettings_Input_Touch_SetActivationTimeout": { + "oldValue": { + "value": "Medium sensitivity" + }, + "newValue": { + "value": "Low sensitivity" + } + } + } + }] + } + }, + { + func: "set", + input: { + "test.windows.systemSettingsHandler": [{ + settings: { + "SystemSettings_Accessibility_Keyboard_WarningEnabled": { + "value": true + }, + "SystemSettings_Input_Touch_SetActivationTimeout": { + "value": "Medium sensitivity" + } + } + }] + }, + expect: { + "test.windows.systemSettingsHandler": [{ + settings: { + "SystemSettings_Accessibility_Keyboard_WarningEnabled": { + "oldValue": { + "value": false + }, + "newValue": { + "value": true + } + }, + "SystemSettings_Input_Touch_SetActivationTimeout": { + "oldValue": { + "value": "Low sensitivity" + }, + "newValue": { + "value": "Medium sensitivity" + } } } }] @@ -338,6 +390,7 @@ gpii.tests.windows.systemSettingsHandler.deepMatch = function (subject, expected return match; }; + jqUnit.asyncTest("Testing executeHelper", function () { var tests = gpii.tests.windows.systemSettingsHandler.executeHelperTests; @@ -411,7 +464,7 @@ jqUnit.asyncTest("Testing system settings handler API", function () { gpii.windows.systemSettingsHandler[test.func](test.input).then(function (value) { jqUnit.assertDeepEq("resolve", test.expect, value); runTest(testIndex + 1); - }); + }, fluid.fail); }; runTest(0); From a27c8ccb77c9aa2e6ff99694a7e77f25b4d09b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Wed, 20 Nov 2019 15:50:06 +0100 Subject: [PATCH 073/123] GPII-4152: Removed redundant checks --- .../SettingsHelperLib/BaseSettingItem.cpp | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/settingsHelper/SettingsHelperLib/BaseSettingItem.cpp b/settingsHelper/SettingsHelperLib/BaseSettingItem.cpp index 6e35198d2..4283001be 100644 --- a/settingsHelper/SettingsHelperLib/BaseSettingItem.cpp +++ b/settingsHelper/SettingsHelperLib/BaseSettingItem.cpp @@ -134,33 +134,28 @@ UINT BaseSettingItem::GetValue(wstring id, ATL::CComPtr& item) { if (res == ERROR_SUCCESS) { res = this->setting->get_IsUpdating(&isUpdating); - if (res == ERROR_SUCCESS) { - UINT it = 0; - UINT wait = timeout / retries; - - while (isUpdating == TRUE && res == ERROR_SUCCESS) { - System::Threading::Thread::Sleep(10); - res = this->setting->get_IsUpdating(&isUpdating); + while (isUpdating == TRUE && res == ERROR_SUCCESS) { + System::Threading::Thread::Sleep(10); + res = this->setting->get_IsUpdating(&isUpdating); - if (res != ERROR_SUCCESS) { - break; - } + if (res != ERROR_SUCCESS) { + break; } + } - HSTRING otherStr = NULL; - res = WindowsCreateString(id.c_str(), static_cast(id.size()), &otherStr); + HSTRING otherStr = NULL; + res = WindowsCreateString(id.c_str(), static_cast(id.size()), &otherStr); - curValue->Release(); + curValue->Release(); - // TODO: This second get is necessary for some settings, like - // "Collection" settings. For this ones the second get guarantees - // that the real value is the one received. - res = this->setting->GetValue(otherStr, &curValue); - if (res == ERROR_SUCCESS) { - item.Attach(curValue); - } else { - curValue->Release(); - } + // TODO: This second get is necessary for some settings, like + // "Collection" settings. For this ones the second get guarantees + // that the real value is the one received. + res = this->setting->GetValue(otherStr, &curValue); + if (res == ERROR_SUCCESS) { + item.Attach(curValue); + } else { + curValue->Release(); } } } From 57e6893bbe9499e0e9ad0334541cbb921e85de7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Wed, 20 Nov 2019 15:51:19 +0100 Subject: [PATCH 074/123] GPII-4152: Fixed the access to internal dbSettigns and the member helper function _SetValue --- settingsHelper/SettingsHelperLib/SettingItem.cpp | 14 ++++++++++---- settingsHelper/SettingsHelperLib/SettingItem.h | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/settingsHelper/SettingsHelperLib/SettingItem.cpp b/settingsHelper/SettingsHelperLib/SettingItem.cpp index dad63aa70..24799a2e0 100644 --- a/settingsHelper/SettingsHelperLib/SettingItem.cpp +++ b/settingsHelper/SettingsHelperLib/SettingItem.cpp @@ -102,6 +102,8 @@ UINT SettingItem::GetValue(wstring id, ATL::CComPtr& item) { // The required DynamicDatabaseSetting hasn't been found if (_item == NULL) { errCode = E_INVALIDARG; + } else { + item = _item; } } else { // TODO: Improve error code @@ -115,7 +117,7 @@ UINT SettingItem::GetValue(wstring id, ATL::CComPtr& item) { return errCode; } -HRESULT SettingItem::_SetValue(const wstring& id, const DbSettingItem& dbSetting, ATL::CComPtr& item) { +HRESULT SettingItem::_SetValue(DbSettingItem& dbSetting, ATL::CComPtr& item) { HRESULT errCode { ERROR_SUCCESS }; BOOL completed { false }; BOOL isUpdating { true }; @@ -133,7 +135,11 @@ HRESULT SettingItem::_SetValue(const wstring& id, const DbSettingItem& dbSetting errCode = this->setting->add_SettingChanged(handler, &token); if (errCode == ERROR_SUCCESS) { - errCode = BaseSettingItem::SetValue(id, item); + if (setInnerSetting) { + errCode = dbSetting.SetValue(L"Value", item); + } else { + errCode = BaseSettingItem::SetValue(L"Value", item); + } if (errCode == ERROR_SUCCESS) { UINT it = 0; @@ -165,7 +171,7 @@ HRESULT SettingItem::SetValue(const wstring& id, ATL::CComPtr& i HRESULT errCode { ERROR_SUCCESS }; if (id == L"Value") { - errCode = SettingItem::_SetValue(id, DbSettingItem {}, item); + errCode = SettingItem::_SetValue(DbSettingItem {}, item); } else { // Access one of the inner Settings inside the DynamicSettingDatabase // holded inside the setting. @@ -191,7 +197,7 @@ HRESULT SettingItem::SetValue(const wstring& id, ATL::CComPtr& i if (errCode == ERROR_SUCCESS) { for (auto& setting : this->dbSettings) { if (id == setting.settingId) { - errCode = SettingItem::_SetValue(id, setting, item); + errCode = SettingItem::_SetValue(setting, item); applied = TRUE; break; } diff --git a/settingsHelper/SettingsHelperLib/SettingItem.h b/settingsHelper/SettingsHelperLib/SettingItem.h index 28bcf4240..3f75874eb 100644 --- a/settingsHelper/SettingsHelperLib/SettingItem.h +++ b/settingsHelper/SettingsHelperLib/SettingItem.h @@ -46,7 +46,7 @@ struct SettingItem : BaseSettingItem { /// Private helper method encapsulating the waiting logic /// necessary for properly set a new setting a new value. /// - HRESULT _SetValue(const wstring& id, const DbSettingItem& dbSetting, ATL::CComPtr& item); + HRESULT _SetValue(DbSettingItem& dbSetting, ATL::CComPtr& item); public: using BaseSettingItem::BaseSettingItem; From 3cbae25e50f6830672f7c3bd3db6cef1d6ca84df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Wed, 20 Nov 2019 15:52:18 +0100 Subject: [PATCH 075/123] GPII-4152: Changed the comment style from special test in favour of macro --- settingsHelper/SettingsHelperTests/SettingItemTests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/settingsHelper/SettingsHelperTests/SettingItemTests.cpp b/settingsHelper/SettingsHelperTests/SettingItemTests.cpp index b02ba66a2..e1cc6ee53 100644 --- a/settingsHelper/SettingsHelperTests/SettingItemTests.cpp +++ b/settingsHelper/SettingsHelperTests/SettingItemTests.cpp @@ -548,7 +548,7 @@ TEST(GetSettingValue, GetCollectionNestedSetting) { } } -/* +#if 0 /// /// NOTE: This test should remain commented, as it's only used for development purposes. /// @@ -612,4 +612,4 @@ TEST(GetAllSettingsValues, GetAllPossibleSettings) { CoUninitialize(); } -*/ \ No newline at end of file +#endif \ No newline at end of file From 95fd5fd68c5d2d74645e6dce331ed1c2c3ca2273 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Wed, 20 Nov 2019 15:53:30 +0100 Subject: [PATCH 076/123] GPII-4152: Added multiprocessor compilation to other project configurations for settingsHelperTests --- settingsHelper/SettingsHelperTests/SettingsHelperTests.vcxproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/settingsHelper/SettingsHelperTests/SettingsHelperTests.vcxproj b/settingsHelper/SettingsHelperTests/SettingsHelperTests.vcxproj index 6c3adad08..1dff64e86 100644 --- a/settingsHelper/SettingsHelperTests/SettingsHelperTests.vcxproj +++ b/settingsHelper/SettingsHelperTests/SettingsHelperTests.vcxproj @@ -120,6 +120,7 @@ MultiThreadedDLL Level3 ProgramDatabase + true true @@ -137,6 +138,7 @@ Level3 ProgramDatabase $(SolutionDir)SettingsHelperLib;%(AdditionalIncludeDirectories) + true true From e38914d88bc81a5f071a997af7b88088f9cbe6b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Wed, 20 Nov 2019 15:58:43 +0100 Subject: [PATCH 077/123] GPII-4152: Added example payloads that can be used to test the settingsHelper executable --- .../payloads/access_keyboard_warning.json | 7 +++++++ .../payloads/focus_quiet_hours.json | 13 ++++++++++++ .../payloads/input_touch_sensitivity.json | 7 +++++++ .../payloads/invalid_empty_payload.json | 4 ++++ .../payloads/magnifier.json | 11 ++++++++++ .../payloads/night_light.json | 6 ++++++ .../payloads/notification_sounds.json | 21 +++++++++++++++++++ .../SettingsHelperTests/payloads/taskbar.json | 11 ++++++++++ 8 files changed, 80 insertions(+) create mode 100644 settingsHelper/SettingsHelperTests/payloads/access_keyboard_warning.json create mode 100644 settingsHelper/SettingsHelperTests/payloads/focus_quiet_hours.json create mode 100644 settingsHelper/SettingsHelperTests/payloads/input_touch_sensitivity.json create mode 100644 settingsHelper/SettingsHelperTests/payloads/invalid_empty_payload.json create mode 100644 settingsHelper/SettingsHelperTests/payloads/magnifier.json create mode 100644 settingsHelper/SettingsHelperTests/payloads/night_light.json create mode 100644 settingsHelper/SettingsHelperTests/payloads/notification_sounds.json create mode 100644 settingsHelper/SettingsHelperTests/payloads/taskbar.json diff --git a/settingsHelper/SettingsHelperTests/payloads/access_keyboard_warning.json b/settingsHelper/SettingsHelperTests/payloads/access_keyboard_warning.json new file mode 100644 index 000000000..ff6cfdd04 --- /dev/null +++ b/settingsHelper/SettingsHelperTests/payloads/access_keyboard_warning.json @@ -0,0 +1,7 @@ +[ + { + "settingID": "SystemSettings_Accessibility_Keyboard_WarningEnabled", + "method": "GetValue", + "async": true + } +] diff --git a/settingsHelper/SettingsHelperTests/payloads/focus_quiet_hours.json b/settingsHelper/SettingsHelperTests/payloads/focus_quiet_hours.json new file mode 100644 index 000000000..6fdc366a2 --- /dev/null +++ b/settingsHelper/SettingsHelperTests/payloads/focus_quiet_hours.json @@ -0,0 +1,13 @@ +[ + { + "settingID": "SystemSettings_QuietMoments_On_Scheduled_Mode.SystemSettings_QuietMoments_Scheduled_Mode_StartTime", + "method": "SetValue", + "parameters": [ + "8:00:00" + ] + }, + { + "settingID": "SystemSettings_QuietMoments_On_Scheduled_Mode.SystemSettings_QuietMoments_Scheduled_Mode_StartTime", + "method": "GetValue" + } +] diff --git a/settingsHelper/SettingsHelperTests/payloads/input_touch_sensitivity.json b/settingsHelper/SettingsHelperTests/payloads/input_touch_sensitivity.json new file mode 100644 index 000000000..ef7df71d1 --- /dev/null +++ b/settingsHelper/SettingsHelperTests/payloads/input_touch_sensitivity.json @@ -0,0 +1,7 @@ +[ + { + "settingID": "SystemSettings_Input_Touch_SetActivationTimeout", + "method": "GetValue", + "async": true + } +] \ No newline at end of file diff --git a/settingsHelper/SettingsHelperTests/payloads/invalid_empty_payload.json b/settingsHelper/SettingsHelperTests/payloads/invalid_empty_payload.json new file mode 100644 index 000000000..4d12b754f --- /dev/null +++ b/settingsHelper/SettingsHelperTests/payloads/invalid_empty_payload.json @@ -0,0 +1,4 @@ +[ + {}, + {} +] diff --git a/settingsHelper/SettingsHelperTests/payloads/magnifier.json b/settingsHelper/SettingsHelperTests/payloads/magnifier.json new file mode 100644 index 000000000..38902c959 --- /dev/null +++ b/settingsHelper/SettingsHelperTests/payloads/magnifier.json @@ -0,0 +1,11 @@ +[ + { + "settingID": "SystemSettings_Accessibility_Magnifier_IsEnabled", + "method": "GetValue" + }, + { + "settingID": "SystemSettings_Accessibility_Magnifier_IsEnabled", + "method": "SetValue", + "parameters": [ true ] + } +] diff --git a/settingsHelper/SettingsHelperTests/payloads/night_light.json b/settingsHelper/SettingsHelperTests/payloads/night_light.json new file mode 100644 index 000000000..f19acead1 --- /dev/null +++ b/settingsHelper/SettingsHelperTests/payloads/night_light.json @@ -0,0 +1,6 @@ +[ + { + "settingID": "SystemSettings_Display_BlueLight_ManualToggleQuickAction", + "method": "GetValue" + } +] diff --git a/settingsHelper/SettingsHelperTests/payloads/notification_sounds.json b/settingsHelper/SettingsHelperTests/payloads/notification_sounds.json new file mode 100644 index 000000000..4aa4f3297 --- /dev/null +++ b/settingsHelper/SettingsHelperTests/payloads/notification_sounds.json @@ -0,0 +1,21 @@ +[ + { + "settingID": "SystemSettings_Notifications_AppList.SystemSettings_Notifications_AppNotifications", + "method": "SetValue", + "parameters": [ + { + "elemId": "Settings", + "elemVal": false + } + ] + }, + { + "settingID": "SystemSettings_Notifications_AppList.SystemSettings_Notifications_AppNotifications", + "method": "GetValue", + "parameters": [ + { + "elemId": "Settings" + } + ] + } +] diff --git a/settingsHelper/SettingsHelperTests/payloads/taskbar.json b/settingsHelper/SettingsHelperTests/payloads/taskbar.json new file mode 100644 index 000000000..dc474db62 --- /dev/null +++ b/settingsHelper/SettingsHelperTests/payloads/taskbar.json @@ -0,0 +1,11 @@ +[ + { + "settingID": "SystemSettings_Taskbar_Location", + "method": "GetValue" + }, + { + "settingID": "SystemSettings_Taskbar_Location", + "method": "SetValue", + "parameters": [ 1 ] + } +] From 769e8a3787edfb8ed34991f36d80439151ab8b3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Wed, 20 Nov 2019 16:29:08 +0100 Subject: [PATCH 078/123] GPII-4152: Added copyright headers in project files with brief description --- .../SettingsHelperApp/SettingsHelperApp.cpp | 12 ++++++++++++ settingsHelper/SettingsHelperApp/pch.cpp | 13 +++++++++++++ settingsHelper/SettingsHelperApp/pch.h | 18 +++++++++++------- .../SettingsHelperLib/BaseSettingItem.cpp | 12 ++++++++++++ .../SettingsHelperLib/BaseSettingItem.h | 12 ++++++++++++ settingsHelper/SettingsHelperLib/Constants.cpp | 12 ++++++++++++ settingsHelper/SettingsHelperLib/Constants.h | 12 ++++++++++++ .../SettingsHelperLib/DbSettingItem.cpp | 13 +++++++++++++ .../SettingsHelperLib/DbSettingItem.h | 13 +++++++++++++ .../DynamicSettingDatabase.cpp | 12 ++++++++++++ .../DynamicSettingsDatabase.h | 12 ++++++++++++ .../IDynamicSettingsDatabase.h | 12 ++++++++++++ .../SettingsHelperLib/IPropertyValueUtils.cpp | 12 ++++++++++++ .../SettingsHelperLib/IPropertyValueUtils.h | 12 ++++++++++++ .../SettingsHelperLib/ISettingItem.h | 12 ++++++++++++ .../SettingsHelperLib/ISettingsCollection.h | 12 ++++++++++++ settingsHelper/SettingsHelperLib/Payload.cpp | 12 ++++++++++++ settingsHelper/SettingsHelperLib/Payload.h | 12 ++++++++++++ .../SettingsHelperLib/PayloadProc.cpp | 12 ++++++++++++ settingsHelper/SettingsHelperLib/PayloadProc.h | 12 ++++++++++++ .../SettingsHelperLib/SettingItem.cpp | 12 ++++++++++++ settingsHelper/SettingsHelperLib/SettingItem.h | 12 ++++++++++++ .../SettingItemEventHandler.cpp | 12 ++++++++++++ .../SettingItemEventHandler.h | 12 ++++++++++++ .../SettingsHelperLib/SettingUtils.cpp | 12 ++++++++++++ .../SettingsHelperLib/SettingUtils.h | 12 ++++++++++++ .../SettingsHelperLib/SettingsIIDs.h | 12 ++++++++++++ .../SettingsHelperLib/StringConversion.cpp | 12 ++++++++++++ .../SettingsHelperLib/StringConversion.h | 12 ++++++++++++ settingsHelper/SettingsHelperLib/stdafx.cpp | 12 ++++++++++++ settingsHelper/SettingsHelperLib/stdafx.h | 15 +++++++++++---- settingsHelper/SettingsHelperLib/targetver.h | 12 ++++++++++++ .../SettingsHelperTests/ParsingTests.cpp | 12 ++++++++++++ .../SettingsHelperTests/SettingItemTests.cpp | 12 ++++++++++++ .../SettingsHelperTests/SettingUtilsTests.cpp | 12 ++++++++++++ settingsHelper/SettingsHelperTests/pch.cpp | 15 ++++++++++++++- settingsHelper/SettingsHelperTests/pch.h | 16 ++++++++++++---- 37 files changed, 447 insertions(+), 16 deletions(-) diff --git a/settingsHelper/SettingsHelperApp/SettingsHelperApp.cpp b/settingsHelper/SettingsHelperApp/SettingsHelperApp.cpp index d86195a08..b67610bc8 100644 --- a/settingsHelper/SettingsHelperApp/SettingsHelperApp.cpp +++ b/settingsHelper/SettingsHelperApp/SettingsHelperApp.cpp @@ -1,3 +1,15 @@ +/** + * Application main definition. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #include "pch.h" #include diff --git a/settingsHelper/SettingsHelperApp/pch.cpp b/settingsHelper/SettingsHelperApp/pch.cpp index 3a3d12b5a..2bfd092b8 100644 --- a/settingsHelper/SettingsHelperApp/pch.cpp +++ b/settingsHelper/SettingsHelperApp/pch.cpp @@ -1,3 +1,16 @@ +/** + * Source file corresponding to pre-compiled header; + * necessary for compilation to succeed + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + // pch.cpp: source file corresponding to pre-compiled header; necessary for compilation to succeed #include "pch.h" diff --git a/settingsHelper/SettingsHelperApp/pch.h b/settingsHelper/SettingsHelperApp/pch.h index 0ddf6ec24..d26b4d5b6 100644 --- a/settingsHelper/SettingsHelperApp/pch.h +++ b/settingsHelper/SettingsHelperApp/pch.h @@ -1,10 +1,14 @@ -// Tips for Getting Started: -// 1. Use the Solution Explorer window to add/manage files -// 2. Use the Team Explorer window to connect to source control -// 3. Use the Output window to see build output and other messages -// 4. Use the Error List window to view errors -// 5. Go to Project > Add New Item to create new code files, or Project > Add Existing Item to add existing code files to the project -// 6. In the future, to open this project again, go to File > Open > Project and select the .sln file +/** + * Header for standard system include files. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ #ifndef PCH_H #define PCH_H diff --git a/settingsHelper/SettingsHelperLib/BaseSettingItem.cpp b/settingsHelper/SettingsHelperLib/BaseSettingItem.cpp index 4283001be..d7c30e35c 100644 --- a/settingsHelper/SettingsHelperLib/BaseSettingItem.cpp +++ b/settingsHelper/SettingsHelperLib/BaseSettingItem.cpp @@ -1,3 +1,15 @@ +/** + * BaseSettingItem - a wrapper for ISettingItem. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #include "stdafx.h" #include "BaseSettingItem.h" #include "ISettingsCollection.h" diff --git a/settingsHelper/SettingsHelperLib/BaseSettingItem.h b/settingsHelper/SettingsHelperLib/BaseSettingItem.h index 88d1bff19..7b1adeb44 100644 --- a/settingsHelper/SettingsHelperLib/BaseSettingItem.h +++ b/settingsHelper/SettingsHelperLib/BaseSettingItem.h @@ -1,3 +1,15 @@ +/** + * BaseSettingItem - a wrapper for ISettingItem. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #pragma once #ifndef S__BASESETTINGITEM_H diff --git a/settingsHelper/SettingsHelperLib/Constants.cpp b/settingsHelper/SettingsHelperLib/Constants.cpp index abe785f7a..9c3cf59be 100644 --- a/settingsHelper/SettingsHelperLib/Constants.cpp +++ b/settingsHelper/SettingsHelperLib/Constants.cpp @@ -1,3 +1,15 @@ +/** + * Constants + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #include "stdafx.h" #include "Constants.h" diff --git a/settingsHelper/SettingsHelperLib/Constants.h b/settingsHelper/SettingsHelperLib/Constants.h index 83c04cc25..da3a776c9 100644 --- a/settingsHelper/SettingsHelperLib/Constants.h +++ b/settingsHelper/SettingsHelperLib/Constants.h @@ -1,3 +1,15 @@ +/** + * Constants + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #pragma once #ifndef _SAPI_CONSTANTS_HL�AKJ diff --git a/settingsHelper/SettingsHelperLib/DbSettingItem.cpp b/settingsHelper/SettingsHelperLib/DbSettingItem.cpp index dc09f9be4..6f35149e0 100644 --- a/settingsHelper/SettingsHelperLib/DbSettingItem.cpp +++ b/settingsHelper/SettingsHelperLib/DbSettingItem.cpp @@ -1,3 +1,16 @@ +/** + * DBSettingItem - a wrapper for ISettingItem representing settings + * inside a DynamicSettingsDatabase. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #include "stdafx.h" #include "DbSettingItem.h" diff --git a/settingsHelper/SettingsHelperLib/DbSettingItem.h b/settingsHelper/SettingsHelperLib/DbSettingItem.h index 9d78c732c..3cce33b46 100644 --- a/settingsHelper/SettingsHelperLib/DbSettingItem.h +++ b/settingsHelper/SettingsHelperLib/DbSettingItem.h @@ -1,3 +1,16 @@ +/** + * DBSettingItem - a wrapper for ISettingItem representing settings + * inside a DynamicSettingsDatabase. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #pragma once #ifndef S__IDBSETTINGITEM_H diff --git a/settingsHelper/SettingsHelperLib/DynamicSettingDatabase.cpp b/settingsHelper/SettingsHelperLib/DynamicSettingDatabase.cpp index cb496a70e..cea78d606 100644 --- a/settingsHelper/SettingsHelperLib/DynamicSettingDatabase.cpp +++ b/settingsHelper/SettingsHelperLib/DynamicSettingDatabase.cpp @@ -1,3 +1,15 @@ +/** + * DynamicSettingDatabase - a wrapper for IDynamicSettingsDatabase. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #include "stdafx.h" #include "DynamicSettingsDatabase.h" diff --git a/settingsHelper/SettingsHelperLib/DynamicSettingsDatabase.h b/settingsHelper/SettingsHelperLib/DynamicSettingsDatabase.h index 7520b9c1b..2b766843b 100644 --- a/settingsHelper/SettingsHelperLib/DynamicSettingsDatabase.h +++ b/settingsHelper/SettingsHelperLib/DynamicSettingsDatabase.h @@ -1,3 +1,15 @@ +/** + * DynamicSettingDatabase - a wrapper for IDynamicSettingsDatabase. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #pragma once #ifndef S__DYNAMICSETTINGSDATABASE_H diff --git a/settingsHelper/SettingsHelperLib/IDynamicSettingsDatabase.h b/settingsHelper/SettingsHelperLib/IDynamicSettingsDatabase.h index eba1722b5..bd855223a 100644 --- a/settingsHelper/SettingsHelperLib/IDynamicSettingsDatabase.h +++ b/settingsHelper/SettingsHelperLib/IDynamicSettingsDatabase.h @@ -1,3 +1,15 @@ +/** + * IDynamicSettingsDatabase definition. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #pragma once #ifndef I__IDynamicSettingsDatabase diff --git a/settingsHelper/SettingsHelperLib/IPropertyValueUtils.cpp b/settingsHelper/SettingsHelperLib/IPropertyValueUtils.cpp index 50b1ca959..68c6df039 100644 --- a/settingsHelper/SettingsHelperLib/IPropertyValueUtils.cpp +++ b/settingsHelper/SettingsHelperLib/IPropertyValueUtils.cpp @@ -1,3 +1,15 @@ +/** + * Utilities for handling IPropertyValue types. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #include "stdafx.h" #include "IPropertyValueUtils.h" diff --git a/settingsHelper/SettingsHelperLib/IPropertyValueUtils.h b/settingsHelper/SettingsHelperLib/IPropertyValueUtils.h index bf0a6385e..fb921a02b 100644 --- a/settingsHelper/SettingsHelperLib/IPropertyValueUtils.h +++ b/settingsHelper/SettingsHelperLib/IPropertyValueUtils.h @@ -1,3 +1,15 @@ +/** + * Utilities for handling IPropertyValue types. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #pragma once #ifndef F__IPropertyValueUtils_H diff --git a/settingsHelper/SettingsHelperLib/ISettingItem.h b/settingsHelper/SettingsHelperLib/ISettingItem.h index 97ff4b509..1a0679e60 100644 --- a/settingsHelper/SettingsHelperLib/ISettingItem.h +++ b/settingsHelper/SettingsHelperLib/ISettingItem.h @@ -1,3 +1,15 @@ +/** + * Definition of ISettingItem interface. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #pragma once #ifndef I__ISettingItem_H diff --git a/settingsHelper/SettingsHelperLib/ISettingsCollection.h b/settingsHelper/SettingsHelperLib/ISettingsCollection.h index 91b1476f0..f5bcb466a 100644 --- a/settingsHelper/SettingsHelperLib/ISettingsCollection.h +++ b/settingsHelper/SettingsHelperLib/ISettingsCollection.h @@ -1,3 +1,15 @@ +/** + * Definition of ISettingsCollection interface. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #pragma once #include "ISettingItem.h" diff --git a/settingsHelper/SettingsHelperLib/Payload.cpp b/settingsHelper/SettingsHelperLib/Payload.cpp index 6c12fc6c3..5dcaf34b1 100644 --- a/settingsHelper/SettingsHelperLib/Payload.cpp +++ b/settingsHelper/SettingsHelperLib/Payload.cpp @@ -1,3 +1,15 @@ +/** + * Datatypes representing payload and functions. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #include "stdafx.h" #include "Payload.h" #include "IPropertyValueUtils.h" diff --git a/settingsHelper/SettingsHelperLib/Payload.h b/settingsHelper/SettingsHelperLib/Payload.h index 2cc58ac4a..7743aafce 100644 --- a/settingsHelper/SettingsHelperLib/Payload.h +++ b/settingsHelper/SettingsHelperLib/Payload.h @@ -1,3 +1,15 @@ +/** + * Datatypes representing payload and functions. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #pragma once #include "stdafx.h" diff --git a/settingsHelper/SettingsHelperLib/PayloadProc.cpp b/settingsHelper/SettingsHelperLib/PayloadProc.cpp index ad86f4247..425442e1e 100644 --- a/settingsHelper/SettingsHelperLib/PayloadProc.cpp +++ b/settingsHelper/SettingsHelperLib/PayloadProc.cpp @@ -1,3 +1,15 @@ +/** + * Payload processing functions. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #include "stdafx.h" #include "PayloadProc.h" diff --git a/settingsHelper/SettingsHelperLib/PayloadProc.h b/settingsHelper/SettingsHelperLib/PayloadProc.h index c8d0c0596..f8c49efd0 100644 --- a/settingsHelper/SettingsHelperLib/PayloadProc.h +++ b/settingsHelper/SettingsHelperLib/PayloadProc.h @@ -1,3 +1,15 @@ +/** + * Payload processing functions. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #pragma once #ifndef _SPAYLOAD_PROCESSING_H diff --git a/settingsHelper/SettingsHelperLib/SettingItem.cpp b/settingsHelper/SettingsHelperLib/SettingItem.cpp index 24799a2e0..43652958c 100644 --- a/settingsHelper/SettingsHelperLib/SettingItem.cpp +++ b/settingsHelper/SettingsHelperLib/SettingItem.cpp @@ -1,3 +1,15 @@ +/** + * Represents a Setting - ISettingItem wrapper. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #include "stdafx.h" #include "SettingItem.h" #include "ISettingsCollection.h" diff --git a/settingsHelper/SettingsHelperLib/SettingItem.h b/settingsHelper/SettingsHelperLib/SettingItem.h index 3f75874eb..e751f1dfb 100644 --- a/settingsHelper/SettingsHelperLib/SettingItem.h +++ b/settingsHelper/SettingsHelperLib/SettingItem.h @@ -1,3 +1,15 @@ +/** + * Represents a Setting - ISettingItem wrapper. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #pragma once #ifndef S__SettingItem_H diff --git a/settingsHelper/SettingsHelperLib/SettingItemEventHandler.cpp b/settingsHelper/SettingsHelperLib/SettingItemEventHandler.cpp index d4c0b898a..ccc7177aa 100644 --- a/settingsHelper/SettingsHelperLib/SettingItemEventHandler.cpp +++ b/settingsHelper/SettingsHelperLib/SettingItemEventHandler.cpp @@ -1,3 +1,15 @@ +/** + * Event handlers to react for settings changes. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #include "stdafx.h" #include "SettingItemEventHandler.h" diff --git a/settingsHelper/SettingsHelperLib/SettingItemEventHandler.h b/settingsHelper/SettingsHelperLib/SettingItemEventHandler.h index 3c4bf1f9f..4afd59c7b 100644 --- a/settingsHelper/SettingsHelperLib/SettingItemEventHandler.h +++ b/settingsHelper/SettingsHelperLib/SettingItemEventHandler.h @@ -1,3 +1,15 @@ +/** + * Event handlers to react for settings changes. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #pragma once #include diff --git a/settingsHelper/SettingsHelperLib/SettingUtils.cpp b/settingsHelper/SettingsHelperLib/SettingUtils.cpp index 75d780b5f..308f1ebd6 100644 --- a/settingsHelper/SettingsHelperLib/SettingUtils.cpp +++ b/settingsHelper/SettingsHelperLib/SettingUtils.cpp @@ -1,3 +1,15 @@ +/** + * Utility functions to handle access settings values. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #include "stdafx.h" #include "Constants.h" diff --git a/settingsHelper/SettingsHelperLib/SettingUtils.h b/settingsHelper/SettingsHelperLib/SettingUtils.h index 1819559f8..486063f85 100644 --- a/settingsHelper/SettingsHelperLib/SettingUtils.h +++ b/settingsHelper/SettingsHelperLib/SettingUtils.h @@ -1,3 +1,15 @@ +/** + * Utility functions to handle access settings values. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #pragma once #ifndef F__SettingUtils_H diff --git a/settingsHelper/SettingsHelperLib/SettingsIIDs.h b/settingsHelper/SettingsHelperLib/SettingsIIDs.h index 516e27aa3..dd7a2f2e4 100644 --- a/settingsHelper/SettingsHelperLib/SettingsIIDs.h +++ b/settingsHelper/SettingsHelperLib/SettingsIIDs.h @@ -1,3 +1,15 @@ +/** + * Additional type IIDs found during research. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #pragma once #ifndef I_SETTINGSIIDS_H diff --git a/settingsHelper/SettingsHelperLib/StringConversion.cpp b/settingsHelper/SettingsHelperLib/StringConversion.cpp index 79ea7bb47..81585cc72 100644 --- a/settingsHelper/SettingsHelperLib/StringConversion.cpp +++ b/settingsHelper/SettingsHelperLib/StringConversion.cpp @@ -1,3 +1,15 @@ +/** + * Converts a managed string into a unmanaged one. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #include "stdafx.h" #include "StringConversion.h" diff --git a/settingsHelper/SettingsHelperLib/StringConversion.h b/settingsHelper/SettingsHelperLib/StringConversion.h index 5fe018f5d..966f5ee5e 100644 --- a/settingsHelper/SettingsHelperLib/StringConversion.h +++ b/settingsHelper/SettingsHelperLib/StringConversion.h @@ -1,3 +1,15 @@ +/** + * Converts a managed string into a unmanaged one. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #pragma once #include diff --git a/settingsHelper/SettingsHelperLib/stdafx.cpp b/settingsHelper/SettingsHelperLib/stdafx.cpp index fd4f341c7..201a5d88b 100644 --- a/settingsHelper/SettingsHelperLib/stdafx.cpp +++ b/settingsHelper/SettingsHelperLib/stdafx.cpp @@ -1 +1,13 @@ +/** + * Include file for standard system include files. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #include "stdafx.h" diff --git a/settingsHelper/SettingsHelperLib/stdafx.h b/settingsHelper/SettingsHelperLib/stdafx.h index 821fbecc9..2a2606057 100644 --- a/settingsHelper/SettingsHelperLib/stdafx.h +++ b/settingsHelper/SettingsHelperLib/stdafx.h @@ -1,7 +1,14 @@ -// stdafx.h : include file for standard system include files, -// or project specific include files that are used frequently, but -// are changed infrequently -// +/** + * Include file for standard system include files. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ #pragma once diff --git a/settingsHelper/SettingsHelperLib/targetver.h b/settingsHelper/SettingsHelperLib/targetver.h index 87c0086de..52d775a46 100644 --- a/settingsHelper/SettingsHelperLib/targetver.h +++ b/settingsHelper/SettingsHelperLib/targetver.h @@ -1,3 +1,15 @@ +/** + * TargetVersion header. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #pragma once // Including SDKDDKVer.h defines the highest available Windows platform. diff --git a/settingsHelper/SettingsHelperTests/ParsingTests.cpp b/settingsHelper/SettingsHelperTests/ParsingTests.cpp index eef9a1d85..62b552dff 100644 --- a/settingsHelper/SettingsHelperTests/ParsingTests.cpp +++ b/settingsHelper/SettingsHelperTests/ParsingTests.cpp @@ -1,3 +1,15 @@ +/** + * Tests for the parsing functions. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #include "pch.h" #include #include diff --git a/settingsHelper/SettingsHelperTests/SettingItemTests.cpp b/settingsHelper/SettingsHelperTests/SettingItemTests.cpp index e1cc6ee53..c746affeb 100644 --- a/settingsHelper/SettingsHelperTests/SettingItemTests.cpp +++ b/settingsHelper/SettingsHelperTests/SettingItemTests.cpp @@ -1,3 +1,15 @@ +/** + * Tests setting value accessing. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #include "pch.h" #include #include diff --git a/settingsHelper/SettingsHelperTests/SettingUtilsTests.cpp b/settingsHelper/SettingsHelperTests/SettingUtilsTests.cpp index 5b79b8391..dc70d6a99 100644 --- a/settingsHelper/SettingsHelperTests/SettingUtilsTests.cpp +++ b/settingsHelper/SettingsHelperTests/SettingUtilsTests.cpp @@ -1,3 +1,15 @@ +/** + * Tests function utilities. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + #include "pch.h" #include #include diff --git a/settingsHelper/SettingsHelperTests/pch.cpp b/settingsHelper/SettingsHelperTests/pch.cpp index 97b544ec1..8f0ac5bec 100644 --- a/settingsHelper/SettingsHelperTests/pch.cpp +++ b/settingsHelper/SettingsHelperTests/pch.cpp @@ -1,6 +1,19 @@ +/** + * Source file corresponding to pre-compiled header; + * necessary for compilation to succeed + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + + // // pch.cpp // Include the standard header and generate the precompiled header. // - #include "pch.h" diff --git a/settingsHelper/SettingsHelperTests/pch.h b/settingsHelper/SettingsHelperTests/pch.h index 29c81fffa..0f3e177bc 100644 --- a/settingsHelper/SettingsHelperTests/pch.h +++ b/settingsHelper/SettingsHelperTests/pch.h @@ -1,7 +1,15 @@ -// -// pch.h -// Header for standard system include files. -// +/** + * Precompiled header for tests project. + * Header for standard system include files. + * + * Copyright 2019 Raising the Floor - US + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ #pragma once From 5d6913beee81bce28712797fd33f45ac1b999c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Wed, 20 Nov 2019 17:24:57 +0100 Subject: [PATCH 079/123] GPII-4152: Added forgotten empty-line at the end of file --- .../SettingsHelperTests/payloads/input_touch_sensitivity.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settingsHelper/SettingsHelperTests/payloads/input_touch_sensitivity.json b/settingsHelper/SettingsHelperTests/payloads/input_touch_sensitivity.json index ef7df71d1..acffe5b35 100644 --- a/settingsHelper/SettingsHelperTests/payloads/input_touch_sensitivity.json +++ b/settingsHelper/SettingsHelperTests/payloads/input_touch_sensitivity.json @@ -4,4 +4,4 @@ "method": "GetValue", "async": true } -] \ No newline at end of file +] From 85aa6cd552265a5dc90950d73afc9a10f953141f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Wed, 20 Nov 2019 17:40:18 +0100 Subject: [PATCH 080/123] GPII-4152: Removed included guards for header files in favour of uniform 'pragma once' usage --- settingsHelper/SettingsHelperLib/BaseSettingItem.h | 6 ------ settingsHelper/SettingsHelperLib/Constants.h | 5 ----- settingsHelper/SettingsHelperLib/DbSettingItem.h | 5 ----- settingsHelper/SettingsHelperLib/DynamicSettingsDatabase.h | 5 ----- settingsHelper/SettingsHelperLib/IDynamicSettingsDatabase.h | 5 ----- settingsHelper/SettingsHelperLib/IPropertyValueUtils.h | 5 ----- settingsHelper/SettingsHelperLib/ISettingItem.h | 5 ----- settingsHelper/SettingsHelperLib/PayloadProc.h | 5 ----- settingsHelper/SettingsHelperLib/SettingItem.h | 6 ------ settingsHelper/SettingsHelperLib/SettingUtils.h | 5 ----- settingsHelper/SettingsHelperLib/SettingsIIDs.h | 5 ----- settingsHelper/SettingsHelperTests/GlobalEnvironment.h | 5 ----- 12 files changed, 62 deletions(-) diff --git a/settingsHelper/SettingsHelperLib/BaseSettingItem.h b/settingsHelper/SettingsHelperLib/BaseSettingItem.h index 7b1adeb44..0fc7821ab 100644 --- a/settingsHelper/SettingsHelperLib/BaseSettingItem.h +++ b/settingsHelper/SettingsHelperLib/BaseSettingItem.h @@ -12,9 +12,6 @@ #pragma once -#ifndef S__BASESETTINGITEM_H -#define S__BASESETTINGITEM_H - #include "ISettingItem.h" #include @@ -180,6 +177,3 @@ struct BaseSettingItem { /// An HRESULT error if the operation failed or ERROR_SUCCESS. UINT Invoke(); }; - - -#endif // S__BASESETTINGITEM_H \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/Constants.h b/settingsHelper/SettingsHelperLib/Constants.h index da3a776c9..018852f9b 100644 --- a/settingsHelper/SettingsHelperLib/Constants.h +++ b/settingsHelper/SettingsHelperLib/Constants.h @@ -12,9 +12,6 @@ #pragma once -#ifndef _SAPI_CONSTANTS_HL�AKJ -#define _SAPI_CONSTANTS_HL�AKJ - #include #include #include @@ -57,5 +54,3 @@ namespace constants { /// const map>& CoupledLibs(); } - -#endif // _SAPI_CONSTANTS \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/DbSettingItem.h b/settingsHelper/SettingsHelperLib/DbSettingItem.h index 3cce33b46..d80cb23b4 100644 --- a/settingsHelper/SettingsHelperLib/DbSettingItem.h +++ b/settingsHelper/SettingsHelperLib/DbSettingItem.h @@ -13,9 +13,6 @@ #pragma once -#ifndef S__IDBSETTINGITEM_H -#define S__IDBSETTINGITEM_H - #include "BaseSettingItem.h" struct DbSettingItem : public BaseSettingItem { @@ -23,5 +20,3 @@ struct DbSettingItem : public BaseSettingItem { DbSettingItem(); }; - -#endif // S__IDBSETTINGITEM_H \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/DynamicSettingsDatabase.h b/settingsHelper/SettingsHelperLib/DynamicSettingsDatabase.h index 2b766843b..6cb580bb5 100644 --- a/settingsHelper/SettingsHelperLib/DynamicSettingsDatabase.h +++ b/settingsHelper/SettingsHelperLib/DynamicSettingsDatabase.h @@ -12,9 +12,6 @@ #pragma once -#ifndef S__DYNAMICSETTINGSDATABASE_H -#define S__DYNAMICSETTINGSDATABASE_H - #include "IDynamicSettingsDatabase.h" #include "SettingItem.h" @@ -41,5 +38,3 @@ struct DynamicSettingDatabase { BOOL isSupportedDb(const wstring& settingId); HRESULT loadSettingDatabase(const wstring& settingId, SettingItem& settingItem, DynamicSettingDatabase& dynSettingDatabase); HRESULT getSupportedDbSettings(const DynamicSettingDatabase& database, vector& settingIds); - -#endif // S__DYNAMICSETTINGSDATABASE_H diff --git a/settingsHelper/SettingsHelperLib/IDynamicSettingsDatabase.h b/settingsHelper/SettingsHelperLib/IDynamicSettingsDatabase.h index bd855223a..1888e27c0 100644 --- a/settingsHelper/SettingsHelperLib/IDynamicSettingsDatabase.h +++ b/settingsHelper/SettingsHelperLib/IDynamicSettingsDatabase.h @@ -12,9 +12,6 @@ #pragma once -#ifndef I__IDynamicSettingsDatabase -#define I__IDynamicSettingsDatabase - #include "ISettingItem.h" #include @@ -26,5 +23,3 @@ __interface __declspec(uuid("2bea7562-66a9-47a1-aebd-aa188e3a1b57")) IDynamicSettingsDatabase : public IInspectable { HRESULT GetSetting(HSTRING id, ISettingItem** setting); }; - -#endif // !I__IDynamicSettingsDatabase diff --git a/settingsHelper/SettingsHelperLib/IPropertyValueUtils.h b/settingsHelper/SettingsHelperLib/IPropertyValueUtils.h index fb921a02b..6ea504be4 100644 --- a/settingsHelper/SettingsHelperLib/IPropertyValueUtils.h +++ b/settingsHelper/SettingsHelperLib/IPropertyValueUtils.h @@ -12,9 +12,6 @@ #pragma once -#ifndef F__IPropertyValueUtils_H -#define F__IPropertyValueUtils_H - #include #include @@ -64,5 +61,3 @@ HRESULT createValueVariant(const wstring& value, PropertyType type, VARIANT& rVa /// - E_NOTIMPL if the comparison if requested for non-supported IPropertyValue contents. /// HRESULT equals(ATL::CComPtr fstProp, ATL::CComPtr sndProp, BOOL& result); - -#endif // S__IPropertyValueUtils_H \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/ISettingItem.h b/settingsHelper/SettingsHelperLib/ISettingItem.h index 1a0679e60..53c1a343c 100644 --- a/settingsHelper/SettingsHelperLib/ISettingItem.h +++ b/settingsHelper/SettingsHelperLib/ISettingItem.h @@ -12,9 +12,6 @@ #pragma once -#ifndef I__ISettingItem_H -#define I__ISettingItem_H - #include #include #include @@ -85,5 +82,3 @@ ISettingItem : public IInspectable { int add_SettingChanged(ABI::Windows::Foundation::ITypedEventHandler* eventHnd, EventRegistrationToken* token); int remove_SettingChanged(EventRegistrationToken token); }; - -#endif // !I__ISettingItem_H diff --git a/settingsHelper/SettingsHelperLib/PayloadProc.h b/settingsHelper/SettingsHelperLib/PayloadProc.h index f8c49efd0..a99d027b2 100644 --- a/settingsHelper/SettingsHelperLib/PayloadProc.h +++ b/settingsHelper/SettingsHelperLib/PayloadProc.h @@ -12,9 +12,6 @@ #pragma once -#ifndef _SPAYLOAD_PROCESSING_H -#define _SPAYLOAD_PROCESSING_H - #include #include @@ -190,5 +187,3 @@ HRESULT getInputPayload(pair* pInput, wstring& rPayloadStr); /// ERROR_SUCCESS in case of success or one of the following error codes: /// HRESULT handlePayload(pair* pInput); - -#endif // !_SPAYLOAD_PROCESSING_H diff --git a/settingsHelper/SettingsHelperLib/SettingItem.h b/settingsHelper/SettingsHelperLib/SettingItem.h index e751f1dfb..34b4931c5 100644 --- a/settingsHelper/SettingsHelperLib/SettingItem.h +++ b/settingsHelper/SettingsHelperLib/SettingItem.h @@ -12,9 +12,6 @@ #pragma once -#ifndef S__SettingItem_H -#define S__SettingItem_H - #include "BaseSettingItem.h" #include "DbSettingItem.h" @@ -123,6 +120,3 @@ struct SettingItem : BaseSettingItem { /// HRESULT SetValue(const wstring& id, ATL::CComPtr& value); }; - - -#endif // S__SettingItem_H \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/SettingUtils.h b/settingsHelper/SettingsHelperLib/SettingUtils.h index 486063f85..7c97cdd6d 100644 --- a/settingsHelper/SettingsHelperLib/SettingUtils.h +++ b/settingsHelper/SettingsHelperLib/SettingUtils.h @@ -12,9 +12,6 @@ #pragma once -#ifndef F__SettingUtils_H -#define F__SettingUtils_H - #include "SettingItem.h" #include @@ -158,5 +155,3 @@ class SettingAPI { /// friend HRESULT UnloadSettingsAPI(SettingAPI& rSAPI); }; - -#endif // F__SettingUtils_H \ No newline at end of file diff --git a/settingsHelper/SettingsHelperLib/SettingsIIDs.h b/settingsHelper/SettingsHelperLib/SettingsIIDs.h index dd7a2f2e4..29589660c 100644 --- a/settingsHelper/SettingsHelperLib/SettingsIIDs.h +++ b/settingsHelper/SettingsHelperLib/SettingsIIDs.h @@ -12,9 +12,6 @@ #pragma once -#ifndef I_SETTINGSIIDS_H -#define I_SETTINGSIIDS_H - #include /// @@ -127,5 +124,3 @@ IID IID_ISettingObservableVector { 0x36 } }; - -#endif diff --git a/settingsHelper/SettingsHelperTests/GlobalEnvironment.h b/settingsHelper/SettingsHelperTests/GlobalEnvironment.h index 523278c8b..328060374 100644 --- a/settingsHelper/SettingsHelperTests/GlobalEnvironment.h +++ b/settingsHelper/SettingsHelperTests/GlobalEnvironment.h @@ -1,8 +1,5 @@ #pragma once -#ifndef __GLOBAL_ENVIRONMNET_TEARDOWN_GTEST_HH -#define __GLOBAL_ENVIRONMNET_TEARDOWN_GTEST_HH - #include #include @@ -17,5 +14,3 @@ class ComDLLsLibraryTearDown : public ::testing::Environment // Override this to define how to tear down the environment. virtual void TearDown(); }; - -#endif // !__GLOBAL_ENVIRONMNET_GTEST_HH From b1a7b80ebd86d4275301f73ac8b7904fe970f2a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Wed, 20 Nov 2019 17:41:28 +0100 Subject: [PATCH 081/123] GPII-4152: Changed comment style to reflect the fact that is research info --- .../SettingsHelperLib/IDynamicSettingsDatabase.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/settingsHelper/SettingsHelperLib/IDynamicSettingsDatabase.h b/settingsHelper/SettingsHelperLib/IDynamicSettingsDatabase.h index 1888e27c0..1f7dc0233 100644 --- a/settingsHelper/SettingsHelperLib/IDynamicSettingsDatabase.h +++ b/settingsHelper/SettingsHelperLib/IDynamicSettingsDatabase.h @@ -17,7 +17,12 @@ #include #include -// SystemSettings::NotificationsDataModel::AppSettingsDynamicDatabase::GetSetting(struct HSTRING__ *,struct SystemSettings::DataModel::ISettingItem * *) +/** + * RESEARCH DATA: This is the RAW pointer table found for the interface IDynamicSettingsDatabase + * ============================================================================ + * + * 6 -> SystemSettings::NotificationsDataModel::AppSettingsDynamicDatabase::GetSetting(struct HSTRING__ *,struct SystemSettings::DataModel::ISettingItem * *) + */ __interface __declspec(uuid("2bea7562-66a9-47a1-aebd-aa188e3a1b57")) IDynamicSettingsDatabase : public IInspectable { From c5312fd53ddff1d0f1f609e5cbc3a1f2eb7f307c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Hern=C3=A1ndez?= Date: Thu, 21 Nov 2019 20:14:15 +0100 Subject: [PATCH 082/123] GPII-4214: Updated universal reference --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 306dcef1f..bab887462 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "dependencies": { "edge-js": "10.3.1", "ffi-napi": "2.4.3", - "gpii-universal": "javihernandez/universal#85e8de4e70087188170712e296083e9437b1b120", + "gpii-universal": "javihernandez/universal#98913212f87e07f432a9d36e4e6ff63b334a6d1d", "@pokusew/pcsclite": "0.4.18", "ref": "1.3.4", "ref-struct": "1.1.0", From 2168fcad858d5a85b79e8e62cb5e802f158ea175 Mon Sep 17 00:00:00 2001 From: ste Date: Mon, 25 Nov 2019 23:14:17 +0000 Subject: [PATCH 083/123] GPII-4244: expandEnvironmentStrings works recursively for objects. --- gpii-service/src/service.js | 2 +- gpii-service/src/windows.js | 33 +++++++++++++++--- gpii-service/tests/windows-tests.js | 52 +++++++++++++++++++++++++---- 3 files changed, 75 insertions(+), 12 deletions(-) diff --git a/gpii-service/src/service.js b/gpii-service/src/service.js index dea3f15d2..1303dd804 100644 --- a/gpii-service/src/service.js +++ b/gpii-service/src/service.js @@ -103,7 +103,7 @@ service.loadConfig = function (dir, file) { }; /** - * Gets the the secrets, which is the data stored in the secrets file. + * Gets the secrets, which is the data stored in the secrets file. * * The secret is installed in a separate installer, which could occur after Morphic was installed. Also, the secret * may be later updated. Because of this, the secret is read each time it is used. diff --git a/gpii-service/src/windows.js b/gpii-service/src/windows.js index cc39a00e9..693d5ef24 100644 --- a/gpii-service/src/windows.js +++ b/gpii-service/src/windows.js @@ -429,15 +429,21 @@ windows.setPipePermissions = function (pipeName) { }; /** - * Expands the environment variables in a string, which are surrounded by '%'. + * Expands the environment variables in a string (or strings deep within an object), which are surrounded by '%'. * For example, the input string of "%SystemRoot%\System32" returns "C:\Windows\System32". * - * @param {String} input The input string. - * @return {String} The input string with the environment variables expanded. + * @param {String|Object} input The input string, or an object. + * @return {String|Object} The input string, or an object, with the environment variables expanded. */ windows.expandEnvironmentStrings = function (input) { var result; - if (input && input.length > 0) { + + if (windows.isPlainObject(input)) { + result = Array.isArray(input) ? [] : {}; + for (var key in input) { + result[key] = windows.expandEnvironmentStrings(input[key]); + } + } else if (input && input.length > 0) { var inputBuffer = winapi.stringToWideChar(input); // Initial buffer of MAX_PATH should be big enough for most cases (assuming this function is called for paths). var len = Math.max(winapi.constants.MAX_PATH + 1, input.length + 20); @@ -455,10 +461,27 @@ windows.expandEnvironmentStrings = function (input) { result = winapi.stringFromWideChar(outputBuffer); } else { - result = ""; + result = input; } return result; }; +/** Taken from fluid.isPlainObject + * Determines whether the supplied object is a plain JSON-forming container - that is, it is either a plain Object + * or a plain Array. Note that this differs from jQuery's isPlainObject which does not pass Arrays. + * @param {Any} totest - The object to be tested + * @param {Boolean} [strict] - (optional) If `true`, plain Arrays will fail the test rather than passing. + * @return {Boolean} - `true` if `totest` is a plain object, `false` otherwise. + */ +windows.isPlainObject = function (totest, strict) { + var string = Object.prototype.toString.call(totest); + if (string === "[object Array]") { + return !strict; + } else if (string !== "[object Object]") { + return false; + } // FLUID-5226: This inventive strategy taken from jQuery detects whether the object's prototype is directly Object.prototype by virtue of having an "isPrototypeOf" direct member + return !totest.constructor || !totest.constructor.prototype || Object.prototype.hasOwnProperty.call(totest.constructor.prototype, "isPrototypeOf"); +}; + module.exports = windows; diff --git a/gpii-service/tests/windows-tests.js b/gpii-service/tests/windows-tests.js index e4cb4899b..2d08df96f 100644 --- a/gpii-service/tests/windows-tests.js +++ b/gpii-service/tests/windows-tests.js @@ -548,7 +548,6 @@ jqUnit.test("expandEnvironmentStrings tests", function () { var result = windows.expandEnvironmentStrings(input); // the value should be expanded jqUnit.assertEquals("expandEnvironmentStrings should return expected result", "startVALUEend", result); - delete process.env._env_test1; // Test an unset value var input2 = "start%_env_unset%end"; @@ -565,9 +564,50 @@ jqUnit.test("expandEnvironmentStrings tests", function () { jqUnit.assertEquals("expandEnvironmentStrings (long value) should return the expected result", "start" + process.env._env_test2 + "end", result3); - // Call with empty string or null should return an empty string. - ["", null].forEach(function (input) { - var result = windows.expandEnvironmentStrings(input); - jqUnit.assertEquals("expandEnvironmentStrings (empty/null) should return empty string", "", result); - }); + + // Call with an object (also tests passing other value types) + var input4 = { + set: "start%_env_test1%end", + unset: "start%_env_unset%end", + plain: "nothing to expand", + number: 123, + bool: true, + nul: null, + undef: undefined, + array: [ "A1 start%_env_test1%end", "start%_env_unset%end", "", 5, [], [ "A2 start%_env_test1%end", 6 ] ], + empty: {}, + regex: /a.*/g, + deep: { + set: "1 start%_env_test1%end", + unset: "1 start%_env_unset%end", + plain: "1 nothing to expand", + deeper: { + set: "2 start%_env_test1%end" + } + } + }; + var expect4 = { + set: "startVALUEend", + unset: "start%_env_unset%end", + plain: "nothing to expand", + number: 123, + bool: true, + nul: null, + undef: undefined, + array: [ "A1 startVALUEend", "start%_env_unset%end", "", 5, [], [ "A2 startVALUEend", 6 ] ], + empty: {}, + regex: /a.*/g, + deep: { + set: "1 startVALUEend", + unset: "1 start%_env_unset%end", + plain: "1 nothing to expand", + deeper: { + set: "2 startVALUEend" + } + } + }; + var result4 = windows.expandEnvironmentStrings(input4); + jqUnit.assertDeepEq("expandEnvironmentStrings (object) should return the expected object", expect4, result4); + + delete process.env._env_test1; }); From 073f9992451f88f794f148e0b4f1ac3d494225be Mon Sep 17 00:00:00 2001 From: ste Date: Mon, 25 Nov 2019 23:18:41 +0000 Subject: [PATCH 084/123] GPII-4244: Expanding environment variables in the whole config object. --- gpii-service/src/service.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gpii-service/src/service.js b/gpii-service/src/service.js index 1303dd804..25d71fab4 100644 --- a/gpii-service/src/service.js +++ b/gpii-service/src/service.js @@ -94,7 +94,9 @@ service.loadConfig = function (dir, file) { } service.log("Loading config file", configFile); - service.config = JSON5.parse(fs.readFileSync(configFile)); + var config = JSON5.parse(fs.readFileSync(configFile)); + // Expand all environment %variables% within the config. + service.config = windows.expandEnvironmentStrings(config); // Change to the configured log level (if it's not passed via command line) if (!service.args.loglevel && service.config.logging && service.config.logging.level) { @@ -114,8 +116,7 @@ service.getSecrets = function () { var secret = null; try { - var file = service.config.secretFile - && path.resolve(windows.expandEnvironmentStrings(service.config.secretFile)); + var file = service.config.secretFile && path.resolve(service.config.secretFile); if (file) { service.log("Reading secrets from " + file); secret = JSON5.parse(fs.readFileSync(file)); From cbddbe9c8d1d01e6482b3e7c4764a9c39eebd1ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Tue, 26 Nov 2019 11:13:41 +0100 Subject: [PATCH 085/123] GPII-4208: Initial refactor of metrics using RAW API --- .../WindowsUtilities/WindowsUtilities.js | 99 +++++++++- .../windowsMetrics/src/windowsMetrics.js | 174 +++++++++++++----- 2 files changed, 227 insertions(+), 46 deletions(-) diff --git a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js index 40a689979..2b73fb00d 100644 --- a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js +++ b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js @@ -406,6 +406,17 @@ windows.user32 = ffi.Library("user32", { // https://docs.microsoft.com/windows/win32/api/winuser/nf-winuser-oemkeyscan "OemKeyScan": [ t.DWORD, [ t.WORD ] + ], + // https://msdn.microsoft.com/library/ms633497 + "RegisterRawInputDevices": [ + t.BOOL, [t.PVOID, t.UINT, t.UINT] + ], + "GetRawInputData": [ + t.INT, [t.PVOID, t.UINT, t.PVOID, t.PVOID, t.UINT] + ], + // https://docs.microsoft.com/es-es/windows/win32/api/winuser/nf-winuser-getmessagetime + "GetMessageTime": [ + t.LONG, [] ] }); @@ -788,9 +799,15 @@ windows.API_constants = { SW_SHOWNORMAL: 1, SW_SHOWMINIMIZED: 2, SW_SHOWMAXIMIZED: 3, - SW_SHOWNOACTIVATE: 4 - - + SW_SHOWNOACTIVATE: 4, + // https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawmouse + RI_MOUSE_LEFT_BUTTON_UP: 0x0002, + RI_MOUSE_MIDDLE_BUTTON_UP: 0x0020, + RI_MOUSE_RIGHT_BUTTON_UP: 0x0008, + RI_MOUSE_WHEEL: 0x0400, + + // https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawmouse + WM_INPUT: 0x00FF }; fluid.each(windows.API_constants.returnCodesLookup, function (value, key) { @@ -1051,6 +1068,59 @@ windows.KEY_INPUT = new Struct([ [t.ULONG_PTR, "dwExtraInfo"] ]); +windows.RAWINPUTHEADER = new Struct([ + [t.DWORD, "dwType"], + [t.DWORD, "dwSize"], + [t.HANDLE, "hDevice"], + [t.PUINT, "wParam"] +]); + +windows.RAWMOUSE = new Struct([ + [t.WORD, "usFlags"], + [t.WORD, "usButtonData"], + [t.WORD, "usButtonFlags"], + [t.ULONG, "ulRawButtons"], + [t.LONG, "lLastX"], + [t.LONG, "lLastY"], + [t.ULONG, "ulExtraInformation"] +]); + +windows.RAWKEYBOARD = new Struct([ + [t.WORD, "MakeCode"], + [t.WORD, "Flags"], + [t.WORD, "Reserved"], + [t.WORD, "VKey"], + [t.UINT, "Message"], + [t.ULONG, "ExtraInformation"] +]); + +windows.RAWHID = new Struct([ + [t.DWORD, "dwSizeHid"], + [t.DWORD, "dwCount"], + [arrayType("char", 1), "bRawData"] +]); + +windows.RAWINPUTMOUSE = new Struct([ + [windows.RAWINPUTHEADER, "header"], + [windows.RAWMOUSE, "mouse"] +]); + +windows.RAWINPUTKEYBOARD = new Struct([ + [windows.RAWINPUTHEADER, "header"], + [windows.RAWKEYBOARD, "keyboard"] +]); + +windows.RAWINPUTHID = new Struct([ + [windows.RAWINPUTHEADER, "header"], + [windows.RAWHID, "mouse"] +]); + +windows.RAWINPUTDEVICE = new Struct([ + [t.WORD, "usUsagePage"], + [t.WORD, "usUsage"], + [t.DWORD, "dwFlags"], + [t.ULONG, "hwndTarget"] +]); /** * Contains flags used in the "dwFlags" field of various structures @@ -1117,7 +1187,28 @@ windows.flagConstants = { // LANGUAGE Identifiers flags "LANG_NEUTRAL": 0x00, - "SUBLANG_DEFAULT": 0x01 + "SUBLANG_DEFAULT": 0x01, + + // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-rawinputdevice + "RIDEV_APPKEYS": 0x00000400, + "RIDEV_CAPTUREMOUSE": 0x00000200, + "RIDEV_DEVNOTIFY": 0x00002000, + "RIDEV_EXCLUDE": 0x00000010, + "RIDEV_EXINPUTSINK": 0x00001000, + "RIDEV_INPUTSINK": 0x00000100, + "RIDEV_NOHOTKEYS": 0x00000200, + "RIDEV_NOLEGACY": 0x00000030, + "RIDEV_PAGEONLY": 0x00000020, + "RIDEV_REMOVE": 0x00000001, + + // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getrawinputdata + "RID_HEADER": 0x10000005, + "RID_INPUT": 0x10000003, + + // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-rid_device_info + "RIM_TYPEHID": 2, + "RIM_TYPEKEYBOARD": 1, + "RIM_TYPEMOUSE": 0 // TODO Define additional flags used across various structures here. }; diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index 6f2606a90..9b6787afc 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -19,7 +19,9 @@ "use strict"; -var ffi = require("ffi-napi"); +// var ffi = require("ffi-napi"); + +var ref = require("ref"); var fluid = require("gpii-universal"), path = require("path"); @@ -145,7 +147,9 @@ fluid.defaults("gpii.windowsMetrics", { // Mouse position lastPos: null, distance: 0, - lastInputTime: 0 + lastInputTime: 0, + // Marks the end of wheel movement + wheelMovement: 0 } }, keyboardHookHandle: null, @@ -305,6 +309,43 @@ windows.metrics.logAppActivate = function (that, windowInfo) { that.logMetric("app-active", data); }; +windows.metrics.getRawInputData = function (lParam) { + var promise = fluid.promise(); + + var dataSz = ref.alloc(windows.types.UINT, 0); + var res = windows.user32.GetRawInputData( + lParam, + windows.flagConstants.RID_INPUT, + ref.NULL, + dataSz, + windows.RAWINPUTHEADER.size + ); + + if (res >= 0) { + var rawInputBuf = Buffer.alloc(dataSz.deref()); + res = windows.user32.GetRawInputData( + lParam, + windows.flagConstants.RID_INPUT, + rawInputBuf, + dataSz, + windows.RAWINPUTHEADER.size + ); + var rawInput = ref.get(rawInputBuf, 0, windows.RAWINPUTKEYBOARD); + + if (rawInput.header.dwType === windows.flagConstants.RIM_TYPEMOUSE) { + var rawMouse = ref.get(rawInputBuf, 0, windows.RAWINPUTMOUSE); + promise.resolve(rawMouse); + } else { + promise.resolve(rawInput); + } + } else { + var errCode = windows.kernel32.GetLastError(); + promise.reject({ code: errCode, message: "Failed to get GetRawInputData."}); + } + + return promise; +}; + /** * Called when an event has been received by the message window. * @@ -317,6 +358,68 @@ windows.metrics.logAppActivate = function (that, windowInfo) { windows.metrics.windowMessage = function (that, hwnd, msg, wParam, lParam) { var lParamNumber = (lParam && lParam.address) ? lParam.address() : lParam || 0; switch (msg) { + case 0xFF: + windows.metrics.userInput(that); + + var pRawInput = windows.metrics.getRawInputData(lParam); + + pRawInput.then(function (rawInput) { + if (rawInput.header.dwType === windows.flagConstants.RIM_TYPEKEYBOARD) { + // Ignore injected keys + if (rawInput.header.hDevice !== 0 && rawInput.keyboard.Message === windows.API_constants.WM_KEYUP) { + var keyValue = windows.user32.MapVirtualKeyW(rawInput.keyboard.VKey, windows.API_constants.MAPVK_VK_TO_CHAR); + var specialKey = windows.metrics.specialKeys[rawInput.keyboard.VKey]; + + if (specialKey || keyValue) { + var timestamp = windows.user32.GetMessageTime(); + windows.metrics.recordKeyTiming(that, timestamp, specialKey, String.fromCharCode(keyValue)); + } + } + } else if (rawInput.header.dwType === windows.flagConstants.RIM_TYPEMOUSE) { + var relevantMouseEvent = + rawInput.mouse.usButtonFlags === windows.API_constants.RI_MOUSE_WHEEL || + rawInput.mouse.usButtonFlags === windows.API_constants.RI_MOUSE_LEFT_BUTTON_UP || + rawInput.mouse.usButtonFlags === windows.API_constants.RI_MOUSE_RIGHT_BUTTON_UP || + // Mouse just moved + rawInput.mouse.usButtonFlags === 0; + + if (relevantMouseEvent && rawInput.header.hDevice !== 0) { + var wheelDistance = rawInput.mouse.usButtonData; + var wheelDirection = 0; + var button = 0; + if (rawInput.mouse.usButtonFlags === windows.API_constants.RI_MOUSE_WHEEL) { + // Unsigned to signed + if (wheelDistance >= 0x8000) { + wheelDistance -= 0x10000; + } + if (Math.sign(wheelDistance) === 1 || Math.sign(wheelDistance) === 0) { + wheelDirection = 1; + } else { + wheelDirection = 0; + } + button = "W"; + } else if (rawInput.mouse.usButtonFlags === windows.API_constants.RI_MOUSE_LEFT_BUTTON_UP) { + button = 1; + } else if (rawInput.mouse.usButtonFlags === windows.API_constants.RI_MOUSE_RIGHT_BUTTON_UP) { + button = 2; + } + + windows.metrics.recordMouseEvent( + that, + button, + { + x: rawInput.mouse.lLastX, + y: rawInput.mouse.lLastY, + wheel: wheelDirection + } + ); + } + } + }, function (err) { + // TODO: Check if this is the desired behavior. + fluid.log(err); + }); + break; case windows.API_constants.WM_SHELLHOOK: // Run the code in the next tick so this function can return soon, as it's a window procedure. process.nextTick(windows.metrics.shellMessage, that, wParam, lParamNumber); @@ -666,45 +769,32 @@ windows.metrics.startInputMetrics = function (that) { fluid.log(fluid.logLevel.WARN, "Input metrics disabled with GPII_NO_INPUT_METRICS."); that.logMetrics("input-disabled"); } else if (process.versions.electron || that.options.forceInputMetrics) { - var callHook = function (code, wparam, lparam) { - // Handle the hook in the next tick, to allow the hook callback to return quickly. The lparam data needs to - // be copied, otherwise the memory will be re-used. - var dup = new lparam.type(); - lparam.copy(dup.ref()); - process.nextTick(windows.metrics.inputHook, that, code, wparam, dup); - return windows.user32.CallNextHookEx(0, code, wparam, lparam); - }; - - // The callbacks need to be referenced outside here otherwise the GC will pull the rug from beneath it. - windows.metrics.keyboardHookCallback = ffi.Callback( - windows.types.HANDLE, [windows.types.INT, windows.types.HANDLE, windows.KBDLLHookStructPointer], callHook); - windows.metrics.mouseHookCallback = ffi.Callback( - windows.types.HANDLE, [windows.types.INT, windows.types.HANDLE, windows.MSDLLHookStructPointer], callHook); - - var WH_KEYBOARD_LL = 13, WH_MOUSE_LL = 14; - var hModule = windows.kernel32.GetModuleHandleW(0); - - // Start the keyboard hook - that.keyboardHookHandle = - windows.user32.SetWindowsHookExW(WH_KEYBOARD_LL, windows.metrics.keyboardHookCallback, hModule, 0); - - if (!that.keyboardHookHandle) { - var errCode = windows.kernel32.GetLastError(); - windows.metrics.stopInputMetrics(that); - fluid.fail("SetWindowsHookExW did not work (keyboard). win32 error: " + errCode); - } - - // Start the mouse hook - that.mouseHookHandle = - windows.user32.SetWindowsHookExW(WH_MOUSE_LL, windows.metrics.mouseHookCallback, hModule, 0); - - if (!that.mouseHookHandle) { - var errCode2 = windows.kernel32.GetLastError(); - windows.metrics.stopInputMetrics(that); - fluid.fail("SetWindowsHookExW did not work (mouse). win32 error: " + errCode2); - } - - windows.metrics.userInput(that); + var messageWindow = that.getMessageWindow(); + var keyboard = new windows.RAWINPUTDEVICE(); + + keyboard.dwFlags = + windows.flagConstants.RIDEV_NOLEGACY | + windows.flagConstants.RIDEV_INPUTSINK; + keyboard.usUsagePage = 1; + // Keyboard code + keyboard.usUsage = 6; + keyboard.hwndTarget = messageWindow; + + var mouse = new windows.RAWINPUTDEVICE(); + + mouse.dwFlags = + windows.flagConstants.RIDEV_NOLEGACY | + windows.flagConstants.RIDEV_INPUTSINK; + mouse.usUsagePage = 1; + // Mouse code + mouse.usUsage = 2; + mouse.hwndTarget = messageWindow; + + var devices = Buffer.alloc(windows.RAWINPUTDEVICE.size * 2); + mouse.ref().copy(devices, 0, 0, windows.RAWINPUTDEVICE.size); + keyboard.ref().copy(devices, windows.RAWINPUTDEVICE.size, 0, windows.RAWINPUTDEVICE.size); + + windows.user32.RegisterRawInputDevices(devices, 2, windows.RAWINPUTDEVICE.size); } else { // The keyboard hook's ability to work is a side-effect of running with electron. fluid.log(fluid.logLevel.WARN, "Input metrics not available without Electron."); @@ -857,7 +947,7 @@ windows.metrics.recordMouseEvent = function (that, button, pos) { var state = that.state.input; if (state.lastPos) { - state.distance += Math.sqrt(Math.pow(state.lastPos.x - pos.x, 2) + Math.pow(state.lastPos.y - pos.y, 2)); + state.distance += Math.sqrt(Math.pow(pos.x, 2) + Math.pow(pos.y, 2)); } state.lastPos = pos; From 2f2dcbc4269a399ca6f7e62fcbdde1b96a41693e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Tue, 26 Nov 2019 11:14:41 +0100 Subject: [PATCH 086/123] GPII-4208: Added work in progress helper function to convert old tests payload into the new format --- .../test/WindowsMetricsTests.js | 60 +++++++++++++++---- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js index 1053de3a9..a8435cca3 100644 --- a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js +++ b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js @@ -29,6 +29,7 @@ require("../../processHandling/processHandling.js"); var jqUnit = fluid.require("node-jqunit"); var gpii = fluid.registerNamespace("gpii"); +var ref = require("ref"); fluid.registerNamespace("gpii.tests.metrics"); require("../index.js"); @@ -586,8 +587,8 @@ gpii.tests.metrics.inputHookTests = fluid.freezeRecursive([ nCode: 0, wParam: gpii.windows.API_constants.WM_RBUTTONUP, lParam: { - ptX: 100 + 50, - ptY: 200 + 60, + ptX: 50, + ptY: 60, mouseData: 0, flags: 0, time: 0, @@ -606,8 +607,8 @@ gpii.tests.metrics.inputHookTests = fluid.freezeRecursive([ nCode: 0, wParam: gpii.windows.API_constants.WM_MOUSEMOVE, lParam: { - ptX: 150 - 25, - ptY: 260 - 30, + ptX: 25, + ptY: 30, mouseData: 0, flags: 0, time: 0, @@ -622,8 +623,8 @@ gpii.tests.metrics.inputHookTests = fluid.freezeRecursive([ nCode: 0, wParam: gpii.windows.API_constants.WM_LBUTTONUP, lParam: { - ptX: 125 + 10, - ptY: 230 + 20, + ptX: 10, + ptY: 20, mouseData: 0, flags: 0, time: 0, @@ -644,7 +645,7 @@ gpii.tests.metrics.inputHookTests = fluid.freezeRecursive([ lParam: { ptX: 1, ptY: 2, - mouseData: gpii.windows.makeLong(0, -120), + mouseData: gpii.windows.makeLong(0, -1), flags: 0, time: 0, dwExtraInfo: 0 @@ -663,7 +664,7 @@ gpii.tests.metrics.inputHookTests = fluid.freezeRecursive([ lParam: { ptX: 1, ptY: 2, - mouseData: gpii.windows.makeLong(0, 120), + mouseData: gpii.windows.makeLong(0, 1), flags: 0, time: 0, dwExtraInfo: 0 @@ -682,7 +683,7 @@ gpii.tests.metrics.inputHookTests = fluid.freezeRecursive([ lParam: { ptX: 1, ptY: 2, - mouseData: gpii.windows.makeLong(0, -120 * 5), + mouseData: gpii.windows.makeLong(0, 5), flags: 0, time: 0, dwExtraInfo: 0 @@ -701,7 +702,7 @@ gpii.tests.metrics.inputHookTests = fluid.freezeRecursive([ lParam: { ptX: 1, ptY: 2, - mouseData: gpii.windows.makeLong(0, 120 * 3), + mouseData: gpii.windows.makeLong(0, 3), flags: 0, time: 0, dwExtraInfo: 0 @@ -717,6 +718,37 @@ gpii.tests.metrics.inputHookTests = fluid.freezeRecursive([ gpii.tests.metrics.allLogLines = []; +gpii.tests.metrics.convertLParam = function (wParam, lParam) { + var mouseInput = + wParam === gpii.windows.API_constants.WM_MOUSEWHEEL || + wParam === gpii.windows.API_constants.WM_LBUTTONUP || + wParam === gpii.windows.API_constants.WM_RBUTTONUP; + + var keyboardInput = + wParam === gpii.windows.API_constants.WM_KEYUP || + wParam === gpii.windows.API_constants.WM_SYSKEYUP; + + var lParamBuf; + if (mouseInput) { + lParamBuf = ref.alloc(gpii.windows.RAWINPUTKEYBOARD.size); + + var rawMouse = new gpii.windows.RAWINPUTMOUSE(); + rawMouse.mouse.lLastX = lParam.ptX; + rawMouse.mouse.lLastY = lParam.ptY; + + if (wParam === gpii.windows.API_constants.WM_LBUTTONUP) { + rawMouse.mouse.usButtonFlags = gpii.windows.API_constants.RI_MOUSE_LEFT_BUTTON_UP; + } else if (wParam === gpii.windows.API_constants.WM_RBUTTONUP) { + rawMouse.mouse.usButtonFlags = gpii.windows.API_constants.RI_MOUSE_RIGHT_BUTTON_UP; + } else if (wParam === gpii.windows.API_constants.WM_MOUSEWHEEL) { + rawMouse.mouse.usButtonFlags = gpii.windows.API_constants.RI_MOUSE_WHEEL; + } + + + } else if (keyboardInput) { + lParamBuf = ref.alloc(gpii.windows.RAWINPUTKEYBOARD.size); + } +}; /** * Start a log server, to capture everything that's being logged in this test module. The logs will then be looked at @@ -1165,7 +1197,13 @@ jqUnit.asyncTest("Testing input metrics: inputHook", function () { var testInput = function () { fluid.each(testData, function (test) { currentTest = test.input; - gpii.windows.metrics.inputHook(windowsMetrics, currentTest.nCode, currentTest.wParam, currentTest.lParam); + gpii.windows.metrics.windowMessage( + windowsMetrics, + 0, + gpii.windows.API_constants.WM_INPUT, + currentTest.wParam, + currentTest.lParam + ); // insert the modifierKeys field to the expect result, if necessary. var expected = fluid.transform(fluid.makeArray(test.expect), function (expect) { From b50c50404485627cb809de50655d9c299f9f3bf1 Mon Sep 17 00:00:00 2001 From: ste Date: Tue, 26 Nov 2019 21:50:41 +0000 Subject: [PATCH 087/123] GPII-4244: Documented service.config --- gpii-service/src/service.js | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/gpii-service/src/service.js b/gpii-service/src/service.js index 25d71fab4..96cbac99f 100644 --- a/gpii-service/src/service.js +++ b/gpii-service/src/service.js @@ -59,6 +59,19 @@ service.logImportant = logging.important; service.logWarn = logging.warn; service.logDebug = logging.debug; +/** + * @typedef {Object} Config + * @property {Object} processes Child process. + * @property {Object} logging Logging settings + * @property {String} secretFile The file containing site-specific information. + * @property {AutoUpdateConfig} autoUpdate Auto update settings. + */ + +/** + * @type Config + */ +service.config = null; + /** * Loads the config file, which may be found in the first of the following locations: * - The file parameter. @@ -68,6 +81,7 @@ service.logDebug = logging.debug; * * @param {String} dir The directory form which relative paths are used. * @param {String} file [optional] The config file. + * @return {Config} The loaded configuration. */ service.loadConfig = function (dir, file) { // Load the config file. @@ -96,12 +110,14 @@ service.loadConfig = function (dir, file) { service.log("Loading config file", configFile); var config = JSON5.parse(fs.readFileSync(configFile)); // Expand all environment %variables% within the config. - service.config = windows.expandEnvironmentStrings(config); + config = windows.expandEnvironmentStrings(config); // Change to the configured log level (if it's not passed via command line) - if (!service.args.loglevel && service.config.logging && service.config.logging.level) { - logging.setLogLevel(service.config.logging.level); + if (!service.args.loglevel && config.logging && config.logging.level) { + logging.setLogLevel(config.logging.level); } + + return config; }; /** @@ -226,7 +242,7 @@ if (service.isExe) { process.chdir(dir); // Load the configuration -service.loadConfig(dir); +service.config = service.loadConfig(dir); module.exports = service; From c213e36df16920643906f6dd39e277c888769a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Thu, 28 Nov 2019 13:50:35 +0100 Subject: [PATCH 088/123] GPII-4208: Added comments and fixed wheel direction detection --- .../WindowsUtilities/WindowsUtilities.js | 17 +++++++++++++++-- .../windowsMetrics/src/windowsMetrics.js | 10 +++++++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js index 2b73fb00d..3a92462c2 100644 --- a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js +++ b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js @@ -411,6 +411,7 @@ windows.user32 = ffi.Library("user32", { "RegisterRawInputDevices": [ t.BOOL, [t.PVOID, t.UINT, t.UINT] ], + // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getrawinputdata "GetRawInputData": [ t.INT, [t.PVOID, t.UINT, t.PVOID, t.PVOID, t.UINT] ], @@ -1068,23 +1069,27 @@ windows.KEY_INPUT = new Struct([ [t.ULONG_PTR, "dwExtraInfo"] ]); +// https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawinputheader windows.RAWINPUTHEADER = new Struct([ [t.DWORD, "dwType"], [t.DWORD, "dwSize"], [t.HANDLE, "hDevice"], - [t.PUINT, "wParam"] + [t.HANDLE, "wParam"] ]); +// https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawmouse windows.RAWMOUSE = new Struct([ [t.WORD, "usFlags"], - [t.WORD, "usButtonData"], + [t.WORD, "padding"], [t.WORD, "usButtonFlags"], + [t.WORD, "usButtonData"], [t.ULONG, "ulRawButtons"], [t.LONG, "lLastX"], [t.LONG, "lLastY"], [t.ULONG, "ulExtraInformation"] ]); +// https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawkeyboard windows.RAWKEYBOARD = new Struct([ [t.WORD, "MakeCode"], [t.WORD, "Flags"], @@ -1094,27 +1099,35 @@ windows.RAWKEYBOARD = new Struct([ [t.ULONG, "ExtraInformation"] ]); +// https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawhid windows.RAWHID = new Struct([ [t.DWORD, "dwSizeHid"], [t.DWORD, "dwCount"], [arrayType("char", 1), "bRawData"] ]); +// https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawinput?redirectedfrom=MSDN +// Representes a case of the RAWINPUT structure in which the union holds a RAWMOUSE structure. windows.RAWINPUTMOUSE = new Struct([ [windows.RAWINPUTHEADER, "header"], [windows.RAWMOUSE, "mouse"] ]); +// https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawinput?redirectedfrom=MSDN +// Representes a case of the RAWINPUT structure in which the union holds a RAWKEYBOARD structure. windows.RAWINPUTKEYBOARD = new Struct([ [windows.RAWINPUTHEADER, "header"], [windows.RAWKEYBOARD, "keyboard"] ]); +// https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawinput?redirectedfrom=MSDN +// Representes a case of the RAWINPUT structure in which the union holds a RAWHID structure. windows.RAWINPUTHID = new Struct([ [windows.RAWINPUTHEADER, "header"], [windows.RAWHID, "mouse"] ]); +// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-rawinputdevice windows.RAWINPUTDEVICE = new Struct([ [t.WORD, "usUsagePage"], [t.WORD, "usUsage"], diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index 9b6787afc..f3fc7c994 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -309,6 +309,14 @@ windows.metrics.logAppActivate = function (that, windowInfo) { that.logMetric("app-active", data); }; +/** + * Extracts a RAWINPUTMOUSE or a RAWINPUTKEYBOARD from a received lParam. + * + * @param {Buffer} lParam A handle to a RAWINPUT structure received from the + * system within a WM_INPUT message. + * @return {Object} Either a RAWINPUTMOUSE or a RAWINPUTKEYBOARD depending on the + * contents of the lParam handle. + */ windows.metrics.getRawInputData = function (lParam) { var promise = fluid.promise(); @@ -395,7 +403,7 @@ windows.metrics.windowMessage = function (that, hwnd, msg, wParam, lParam) { if (Math.sign(wheelDistance) === 1 || Math.sign(wheelDistance) === 0) { wheelDirection = 1; } else { - wheelDirection = 0; + wheelDirection = -1; } button = "W"; } else if (rawInput.mouse.usButtonFlags === windows.API_constants.RI_MOUSE_LEFT_BUTTON_UP) { From 61be0045875cf3b13de1c30802163efb70a49493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Thu, 28 Nov 2019 13:51:59 +0100 Subject: [PATCH 089/123] GPII-4208: Fixed metrics tests using the new way of obtaining devices input --- .../test/WindowsMetricsTests.js | 161 +++++++++++++----- 1 file changed, 117 insertions(+), 44 deletions(-) diff --git a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js index a8435cca3..542265460 100644 --- a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js +++ b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js @@ -59,6 +59,13 @@ fluid.defaults("gpii.tests.metrics.windowsMetricsWrapper", { }, invokers: { "startMessages": "fluid.identity" + }, + members: { + config: { + input: { + testing: true + } + } } }); @@ -675,64 +682,98 @@ gpii.tests.metrics.inputHookTests = fluid.freezeRecursive([ event: "mouse", data: { wheel: 1 } } - }, - { // Mouse wheel up multiple - input: { - nCode: 0, - wParam: gpii.windows.API_constants.WM_MOUSEWHEEL, - lParam: { - ptX: 1, - ptY: 2, - mouseData: gpii.windows.makeLong(0, 5), - flags: 0, - time: 0, - dwExtraInfo: 0 - } - }, - expect: { - module: "metrics", - event: "mouse", - data: { wheel: -5 } - } - }, - { // Mouse wheel down multiple - input: { - nCode: 0, - wParam: gpii.windows.API_constants.WM_MOUSEWHEEL, - lParam: { - ptX: 1, - ptY: 2, - mouseData: gpii.windows.makeLong(0, 3), - flags: 0, - time: 0, - dwExtraInfo: 0 - } - }, - expect: { - module: "metrics", - event: "mouse", - data: { wheel: 3 } - } } ]); gpii.tests.metrics.allLogLines = []; -gpii.tests.metrics.convertLParam = function (wParam, lParam) { +gpii.tests.metrics.elapsedTime = 0; + +gpii.tests.metrics.eventTimes = []; + +/** + * Mock of the native function 'GetMessageTime' for being able to properly test the + * function 'windows.metrics.windowMessage' against mocked WM_INPUT system messages. + * + * @return {Number} The next element in the predefined sequence of message timestamps. + */ +gpii.tests.metrics.getMessageTime = function () { + return gpii.tests.metrics.eventTimes.shift(); +}; + +/** + * Mock of the native function 'GetRawInputData' that is used to extract the information + * received from a WM_INPUT, since this messages can't be mocked, this function is necessary + * for testing purposes. + * + * @param {Buffer} hRawInput The buffer with the mocked lParam message. + * @param {Number} uiCommand The command flag. + * @param {Buffer} pData The buffer that will contain the data holded in the hRawInput parameter. + * @param {Buffer} pcbSize Buffer holding a UINT to specifying the required size for pData to hold + * hRawInput. + * @param {Number} cbSizeHeader The size of the struct "windows.RAWINPUTHEADER". + * @return {Number} -1 in case of error, 0 if pData is empty or the number of bytes copied to it + * if it isn't. + */ +gpii.tests.metrics.getRawInputData = function (hRawInput, uiCommand, pData, pcbSize, cbSizeHeader) { + var invalidParams = + uiCommand !== gpii.windows.flagConstants.RID_INPUT || + cbSizeHeader !== gpii.windows.RAWINPUTHEADER.size; + + if (invalidParams) { + return -1; + } + + var result = 0; + + if (pData.length === 0) { + ref.set(pcbSize, 0, hRawInput.length, gpii.windows.types.UINT); + } else { + result = hRawInput.length; + hRawInput.copy(pData); + } + + return result; +}; + +/** + * Maps a input test payload into a buffer holding either a RAWINPUTMOUSE or a + * RAWINPUTKEYBOARD structure. + * + * @param {Object} input Test payload. + * @return {Object} A buffer holding either a RAWINPUTMOUSE or a RAWINPUTKEYBOARD, depending + * on payload contents. + */ +gpii.tests.metrics.convertLParam = function (input) { + var wParam = input.wParam; + var lParam = input.lParam; + var mouseInput = wParam === gpii.windows.API_constants.WM_MOUSEWHEEL || wParam === gpii.windows.API_constants.WM_LBUTTONUP || - wParam === gpii.windows.API_constants.WM_RBUTTONUP; + wParam === gpii.windows.API_constants.WM_RBUTTONUP || + wParam === gpii.windows.API_constants.WM_MOUSEMOVE; var keyboardInput = wParam === gpii.windows.API_constants.WM_KEYUP || wParam === gpii.windows.API_constants.WM_SYSKEYUP; + var keyValue = gpii.windows.user32.MapVirtualKeyW(lParam.vkCode, gpii.windows.API_constants.MAPVK_VK_TO_CHAR); + var specialKey = gpii.windows.metrics.specialKeys[lParam.vkCode]; + + if (specialKey || keyValue) { + gpii.tests.metrics.eventTimes.push(lParam.time); + } + var lParamBuf; if (mouseInput) { - lParamBuf = ref.alloc(gpii.windows.RAWINPUTKEYBOARD.size); + lParamBuf = Buffer.alloc(gpii.windows.RAWINPUTMOUSE.size); var rawMouse = new gpii.windows.RAWINPUTMOUSE(); + rawMouse.header.dwType = gpii.windows.flagConstants.RIM_TYPEMOUSE; + rawMouse.header.dwSize = gpii.windows.RAWINPUTKEYBOARD.size; + rawMouse.header.hDevice = 1; + rawMouse.mouse.lLastX = lParam.ptX; rawMouse.mouse.lLastY = lParam.ptY; @@ -742,12 +783,31 @@ gpii.tests.metrics.convertLParam = function (wParam, lParam) { rawMouse.mouse.usButtonFlags = gpii.windows.API_constants.RI_MOUSE_RIGHT_BUTTON_UP; } else if (wParam === gpii.windows.API_constants.WM_MOUSEWHEEL) { rawMouse.mouse.usButtonFlags = gpii.windows.API_constants.RI_MOUSE_WHEEL; + rawMouse.mouse.usButtonData = gpii.windows.hiWord(lParam.mouseData); } - + ref.set(lParamBuf, 0, rawMouse, gpii.windows.RAWINPUTMOUSE); } else if (keyboardInput) { - lParamBuf = ref.alloc(gpii.windows.RAWINPUTKEYBOARD.size); + lParamBuf = Buffer.alloc(gpii.windows.RAWINPUTKEYBOARD.size); + + var rawKeyboard = new gpii.windows.RAWINPUTKEYBOARD(); + rawKeyboard.header.dwType = gpii.windows.flagConstants.RIM_TYPEKEYBOARD; + rawKeyboard.header.dwSize = gpii.windows.RAWINPUTKEYBOARD.size; + rawKeyboard.header.hDevice = 1; + rawKeyboard.header.wParam = 0; + + rawKeyboard.keyboard.Flags = 1; + rawKeyboard.keyboard.VKey = lParam.vkCode; + rawKeyboard.keyboard.Message = gpii.windows.API_constants.WM_KEYUP; + rawKeyboard.keyboard.MakeCode = 0; + rawKeyboard.keyboard.VKey = lParam.vkCode; + + ref.set(lParamBuf, 0, rawKeyboard, gpii.windows.RAWINPUTKEYBOARD); + } else { + fluid.fail("Non-exhaustive conversion"); } + + return lParamBuf; }; /** @@ -1197,12 +1257,14 @@ jqUnit.asyncTest("Testing input metrics: inputHook", function () { var testInput = function () { fluid.each(testData, function (test) { currentTest = test.input; + + var lParam = gpii.tests.metrics.convertLParam(currentTest); gpii.windows.metrics.windowMessage( windowsMetrics, 0, gpii.windows.API_constants.WM_INPUT, currentTest.wParam, - currentTest.lParam + lParam ); // insert the modifierKeys field to the expect result, if necessary. @@ -1228,6 +1290,13 @@ jqUnit.asyncTest("Testing input metrics: inputHook", function () { return expectedModifiers; }; + // Mock the native RAW API function + var backup = gpii.windows.user32.GetRawInputData; + var msgBackup = gpii.windows.user32.GetMessageTime; + + gpii.windows.user32.GetRawInputData = gpii.tests.metrics.getRawInputData; + gpii.windows.user32.GetMessageTime = gpii.tests.metrics.getMessageTime; + // Run all of the input tests, with different combinations of modifier keys pressed. for (var pass = 0; pass < Math.pow(2, modifiers.length); pass++) { expectedModifiers = []; @@ -1243,6 +1312,10 @@ jqUnit.asyncTest("Testing input metrics: inputHook", function () { testInput(expectedModifiers); } + // Restore the original RAW API function + gpii.windows.user32.GetRawInputData = backup; + gpii.windows.user32.GetMessageTime = msgBackup; + // The events are put in the log in the next tick (to allow the hook handler to return quickly). setImmediate(function () { windowsMetrics.stopInputMetrics(); From 0e882651c5f9706b305ba6e823d1985751f090ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Thu, 28 Nov 2019 13:54:37 +0100 Subject: [PATCH 090/123] GPII-4208: Replaced number with constant representing WM_INPUT message --- gpii/node_modules/WindowsUtilities/WindowsUtilities.js | 3 +++ gpii/node_modules/windowsMetrics/src/windowsMetrics.js | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js index 3a92462c2..223daeb51 100644 --- a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js +++ b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js @@ -545,6 +545,9 @@ windows.API_constants = { // https://docs.microsoft.com/windows/desktop/inputdev/wm-mousewheel WM_MOUSEWHEEL: 0x20A, + // https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-input + WM_INPUT: 0xFF, + // https://msdn.microsoft.com/library/aa363480 WM_DEVICECHANGE: 0x219, // https://docs.microsoft.com/windows/desktop/winmsg/wm-user diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index f3fc7c994..3ff2f44ed 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -366,7 +366,7 @@ windows.metrics.getRawInputData = function (lParam) { windows.metrics.windowMessage = function (that, hwnd, msg, wParam, lParam) { var lParamNumber = (lParam && lParam.address) ? lParam.address() : lParam || 0; switch (msg) { - case 0xFF: + case windows.API_constants.WM_INPUT: windows.metrics.userInput(that); var pRawInput = windows.metrics.getRawInputData(lParam); From 7c4727a24c6a1a9d004871493740cff74a8b0a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Thu, 28 Nov 2019 15:55:02 +0100 Subject: [PATCH 091/123] GPII-4208: Removed duplicated definition of WM_INPUT --- gpii/node_modules/WindowsUtilities/WindowsUtilities.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js index 223daeb51..8a4512e6f 100644 --- a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js +++ b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js @@ -808,10 +808,7 @@ windows.API_constants = { RI_MOUSE_LEFT_BUTTON_UP: 0x0002, RI_MOUSE_MIDDLE_BUTTON_UP: 0x0020, RI_MOUSE_RIGHT_BUTTON_UP: 0x0008, - RI_MOUSE_WHEEL: 0x0400, - - // https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawmouse - WM_INPUT: 0x00FF + RI_MOUSE_WHEEL: 0x0400 }; fluid.each(windows.API_constants.returnCodesLookup, function (value, key) { From 6118b337ca0c624bd2d7d84fa1df29fc4b803624 Mon Sep 17 00:00:00 2001 From: ste Date: Mon, 2 Dec 2019 15:03:43 +0000 Subject: [PATCH 092/123] GPII-4244: Auto-updating configs implemented --- gpii-service/README.md | 18 +- gpii-service/config/service.dev.json5 | 10 +- gpii-service/config/service.testing.json5 | 17 + gpii-service/package.json | 4 +- gpii-service/src/configUpdater.js | 510 +++++++++ gpii-service/src/main.js | 1 + gpii-service/src/processHandling.js | 2 +- gpii-service/src/service.js | 40 +- gpii-service/tests/configUpdater-tests.js | 1238 +++++++++++++++++++++ gpii-service/tests/service-tests.js | 20 +- 10 files changed, 1842 insertions(+), 18 deletions(-) create mode 100644 gpii-service/config/service.testing.json5 create mode 100644 gpii-service/src/configUpdater.js create mode 100644 gpii-service/tests/configUpdater-tests.js diff --git a/gpii-service/README.md b/gpii-service/README.md index c16033f5c..da73b94f0 100644 --- a/gpii-service/README.md +++ b/gpii-service/README.md @@ -102,7 +102,23 @@ This gets installed in `c:\Program Files (x86)\Morphic\windows\service.json5`. "level": "DEBUG" }, // The file that contains the private site-specific information. - "secretFile": "%ProgramData%\\Morphic Credentials\\secret.txt" + "secretFile": "%ProgramData%\\Morphic Credentials\\secret.txt", + + // Auto update of files + "autoUpdate": { + "enabled": false, // true to enable + // Where to store the 'last update' info + "lastUpdatesFile": "%ProgramData%\\Morphic\\last-updates.json5", + // The files to update + "files": [{ + url: "https://raw.githubusercontent.com/GPII/gpii-app/master/siteconfig.json5", + path: "%ProgramData%\\Morphic\\siteConfig.json5", + isJSON: true // Perform JSON/JSON5 validation before overwriting + }, { + url: "https://example.com/${site}", // `site` field of the secrets file + path: "example.json" + }], + } } ``` diff --git a/gpii-service/config/service.dev.json5 b/gpii-service/config/service.dev.json5 index fe7cf5197..5073e09ea 100644 --- a/gpii-service/config/service.dev.json5 +++ b/gpii-service/config/service.dev.json5 @@ -10,5 +10,13 @@ "logging": { "level": "DEBUG" }, - "secretFile": "test-secret.json5" + "secretFile": "test-secret.json5", + "autoUpdate": { + "enabled": false, + "lastUpdatesFile": "%ProgramData%\\Morphic\\last-updates.json5", + "files": [{ + url: "https://raw.githubusercontent.com/GPII/gpii-app/master/siteconfig.json5", + path: "%ProgramData%\\Morphic\\siteConfig.json5" + }], + } } diff --git a/gpii-service/config/service.testing.json5 b/gpii-service/config/service.testing.json5 new file mode 100644 index 000000000..4cd87d521 --- /dev/null +++ b/gpii-service/config/service.testing.json5 @@ -0,0 +1,17 @@ +// Unit test configuration, opens a pipe and waits for GPII to connect. +{ + "processes": { + "gpii": { + "ipc": "gpii", + // Allow any process to connect. + "noAuth": true + } + }, + "logging": { + "level": "DEBUG" + }, + "secretFile": "test-secret.json5", + "autoUpdate": { + "enabled": false + } +} diff --git a/gpii-service/package.json b/gpii-service/package.json index 8d2d2ec1d..54078f284 100644 --- a/gpii-service/package.json +++ b/gpii-service/package.json @@ -19,11 +19,13 @@ "dependencies": { "ffi-napi": "2.4.4", "json5": "2.1.0", + "mkdirp": "0.5.1", "minimist": "1.2.0", "@gpii/os-service": "stegru/node-os-service#GPII-2338", "ref-array-di": "1.2.1", "ref-napi": "1.4.0", - "ref-struct-di": "1.1.0" + "ref-struct-di": "1.1.0", + "request": "2.88.0" }, "pkg": { "targets": [ diff --git a/gpii-service/src/configUpdater.js b/gpii-service/src/configUpdater.js new file mode 100644 index 000000000..ec428b603 --- /dev/null +++ b/gpii-service/src/configUpdater.js @@ -0,0 +1,510 @@ +/* Updates files from a remote server. + * + * Automatic updates are defined in the config file, under the `autoUpdate` block. Each file has a corresponding + * url, from where the file is downloaded. + * + * Copyright 2019 Raising the Floor - International + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * The R&D leading to these results received funding from the + * Department of Education - Grant H421A150005 (GPII-APCP). However, + * these results do not necessarily represent the policy of the + * Department of Education, and you should not assume endorsement by the + * Federal Government. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + +"use strict"; + +var service = require("./service.js"), + fs = require("fs"), + os = require("os"), + path = require("path"), + crypto = require("crypto"), + JSON5 = require("json5"), + mkdirp = require("mkdirp"), + request = require("request"); + +var configUpdater = {}; +module.exports = configUpdater; + +/** + * Configuration for the config auto update. + * @typedef {Object} AutoUpdateConfig + * @property {Boolean} enabled `true` to enable automatic config updates. + * @property {String} lastUpdatesFile The path to the last updates file. + * @property {Array} files The files to update. + */ +/** + * @typedef {Object} AutoUpdateFile + * @property {String} url The address from which the file will be downloaded. + * @property {String} path The local file to update. + * @property {Boolean} always `true` to always fetch the file. + * @property {Boolean} isJSON `true` if this file is a JSON (or JSON5) file. + */ + +/** + * The last-updates.json5 file. Contains information about the last update of each file. + * @typedef {Object} LastUpdatesFile + * @property String lastCheck ISO-8601 Timestamp of when the last check for updates occurred (only written). + * @property {Object} files Information about the last update of each local file. + */ +/** + * A file entry in the last-updates.json5 file. + * @typedef {Object} LastUpdate + * @property {String} etag The ETag header from the last successful download. + * @property {String} date The date of the last successful download. + * @property {String} previous The filename of the previous version. + */ + +/** + * Loads the last update journal (usually %ProgramData%\Morphic\last-updates.json5). + * + * @param {String} path [optional] The path to the file. + * @return {Promise} Resolves when loaded, or with an empty object if the file isn't found. + */ +configUpdater.loadLastUpdates = function (path) { + return new Promise(function (resolve) { + if (!path) { + path = service.config.autoUpdate.lastUpdatesFile; + } + fs.readFile(path, "utf8", function (err, content) { + var result; + if (err) { + service.log("Error loading last updates file ", path, err); + } else { + try { + result = JSON5.parse(content); + } catch (e) { + service.log("Error parsing last updates file ", path, err); + } + } + if (!result) { + result = { + files: {} + }; + } + resolve(result); + }); + }); +}; + +/** + * Saves the last update journal (usually %ProgramData%\Morphic\last-updates.json5). + * + * @param {LastUpdatesFile} lastUpdates The last updates data + * @param {String} path [optional] The path to the file. + * @return {Promise} Resolves when saved. + */ +configUpdater.saveLastUpdates = function (lastUpdates, path) { + return new Promise(function (resolve, reject) { + if (!path) { + path = service.config.autoUpdate.lastUpdatesFile; + } + + var saveData = Object.assign({}, lastUpdates); + saveData.lastCheck = new Date().toISOString(); + + var content = "// This file is automatically generated, and will be over-written.\n" + JSON.stringify(saveData); + fs.writeFile(path, content, function (err) { + if (err) { + service.logWarn("Error saving last updates file ", path, err); + reject({ + isError: true, + err: err, + path: path + }); + } else { + resolve(); + } + }); + }); +}; + +/** + * Updates all of the files in the auto update configuration (config.autoUpdate). + * @return {Promise} Resolves when complete (even if some fail). + */ +configUpdater.updateAll = function () { + service.logImportant("Checking for configuration updates"); + return configUpdater.loadLastUpdates().then(function (lastUpdates) { + if (!lastUpdates.files) { + lastUpdates.files = {}; + } + + var promises = service.config.autoUpdate.files.map(function (file) { + var lastUpdate = lastUpdates.files[file.path] || {}; + + return configUpdater.updateFile(file, lastUpdate)["catch"](function (err) { + service.logWarn("updateFile error", err); + }).then(function (newLastUpdate) { + if (newLastUpdate) { + lastUpdates.files[file.path] = newLastUpdate; + } + }); + }); + + return Promise.all(promises).then(function () { + configUpdater.saveLastUpdates(lastUpdates); + }); + }); +}; + +/** + * Updates a config file. + * + * The file copy is only performed if the new content doesn't match the current. + * + * @param {AutoUpdateFile} update Details about the file update. + * @param {LastUpdate} lastUpdate Information on the last update. + * @return {Promise} Resolves when complete. + */ +configUpdater.updateFile = function (update, lastUpdate) { + var togo = Object.assign({}, lastUpdate); + var downloadHash, localHash; + + var downloadOptions; + var hashPromise; + if (fs.existsSync(update.path)) { + // Hash the existing file. + hashPromise = configUpdater.hashFile(update.path, "sha512")["catch"](function (err) { + service.log("updateFile: hash failed", err); + }).then(function (hash) { + localHash = hash; + }); + + if (!update.always) { + // Ask the server to only send the file if it's newer. + downloadOptions = { + date: lastUpdate.date, + etag: lastUpdate.etag + }; + } + } + + // Download the file. + var url = configUpdater.expand(update.url, service.getSecrets()); + var tempFile = path.join(os.tmpdir(), "morphic-update." + Math.random()); + var downloadPromise = configUpdater.downloadFile(url, tempFile, downloadOptions).then(function (result) { + if (!result.notModified) { + // Validate the JSON + var p = update.isJSON + ? configUpdater.validateJSON(tempFile) + : Promise.resolve(true); + return p.then(function (isValid) { + if (isValid) { + downloadHash = result.hash; + togo.etag = result.etag; + togo.date = result.date; + } + }); + } + }, function (err) { + service.logWarn("updateFile: download failed", err); + }); + + return Promise.all([downloadPromise, hashPromise]).then(function () { + var updatePromise; + // Only perform the update if the file is different, to avoid needlessly overwriting the backup. + if (downloadHash && downloadHash !== localHash) { + updatePromise = configUpdater.applyUpdate(tempFile, update.path).then(function (backupPath) { + togo.previous = backupPath; + return togo; + }); + } + + return updatePromise || togo; + }); +}; + +/** + * Expands "${expanders}" in a string, whose content is a path to a field in the given object. + * + * Expanders are in the format of ${path} or ${path?default}. + * Examples: + * "${a.b.c}", {a:{b:{c:"result"}}} returns "result". + * "${a.x?no}", {a:{b:{c:"result"}}} returns "no". + * + * @param {String} unexpanded The input string, containing zero or more expanders. + * @param {Object} sourceObject The object which the paths in the expanders refer to. + * @return {String} The input string, with the expanders replaced by the value of the field they refer to. + */ +configUpdater.expand = function (unexpanded, sourceObject) { + // Replace all occurences of "${...}" + return unexpanded.replace(/\$\{([^?}]*)(\?([^}]*))?\}/g, function (match, expression, defaultGroup, defaultValue) { + // Resolve the path to a field, deep in the object. + var value = expression.split(".").reduce(function (parent, property) { + return (parent && parent.hasOwnProperty(property)) ? parent[property] : undefined; + }, sourceObject); + + if (value === undefined || typeof(value) === "object") { + value = defaultValue === undefined ? "" : defaultValue; + } + return value; + }); +}; + +/** + * Applies an updated file, by moving the newly downloaded file in the target location, after backing up the original + * file. + * + * @param {String} source The newly downloaded file. + * @param {String} destination The target path. + * @return {Promise} Resolves with the path to the back-up of the original (or undefined). + */ +configUpdater.applyUpdate = function (source, destination) { + var backupPromise; + var backupPath; + // Back up the current file + if (fs.existsSync(destination)) { + backupPath = destination + ".previous"; + backupPromise = configUpdater.moveFile(destination, backupPath)["catch"](function () { + // Try a different location. + backupPath = path.join(process.env.ProgramData, "Morphic", path.basename(backupPath)); + return configUpdater.moveFile(destination, backupPath); + }); + } else { + backupPromise = Promise.resolve(); + } + + // Move the new file in place + return backupPromise.then(function () { + // Copy + delete, because moving will cause the target file to retain the original permissions of the file which + // were inherited from the directory. + return configUpdater.moveFile(source, destination, true).then(function () { + fs.unlinkSync(source); + return backupPath; + }); + }); +}; + +/** + * Moves a file from one place to another, or if that fails then copy it (leaving the source in place). + * + * @param {String} source Path to the file. + * @param {String} destination Path to where the new file should be placed. + * @param {Boolean} copy Copy the file, instead of moving it. + * @return {Promise} Resolves when complete, with a value of `true` if the file was moved or `false` if it was copied. + */ +configUpdater.moveFile = function (source, destination, copy) { + return new Promise(function (resolve, reject) { + + // Make sure the target directory exists + var destDir = path.dirname(destination); + if (!fs.existsSync(destDir)) { + mkdirp.sync(destDir); + } + + var fn = copy ? fs.copyFile : fs.rename; + + fn(source, destination, function (err) { + if (err) { + if (copy) { + reject({ + isError: "moveFile failed (copyFile):" + err.message, + error: err + }); + } else { + // try to copy it + configUpdater.moveFile(source, destination, true).then(resolve, reject); + } + } else { + resolve(copy); + } + }); + }); +}; + + +/** + * Returns the current date/time in a format for HTTP. + * @return {String} The current date, like `Thu, 01 Jan 1970 00:00:00 GMT` + */ +configUpdater.getDateString = function () { + return new Date().toUTCString(); +}; + +/** + * Downloads a file. + * + * Can use an ETag and/or date value to ask the server to only respond with updated files. + * + * @param {String} url The remote uri. + * @param {String} localPath Destination path. + * @param {Object} options Extra options. + * @param {String} options.date The 'If-Modified-Since' header value, to ask the server to check if the remote file has + * been updated after that date. The date is in the format of 'Thu, 01 Jan 1970 00:00:00 GMT', and is usually the value + * of the Last-Modified header from the previous successful request to the same URL. + * @param {String} options.etag The ETag (If-None-Match header), to ask the server only return the file if it's a + * different ETag (or version). This value is from the previous successful request to the same URL. + * @param {String} options.hashAlgorithm The hash algorithm (default: sha512) + * @return {Promise} Resolves with the hash, etag, and server's date (if known) when the download is complete, + * or with `{notModified: true}` if the remote file has not changed, according to the options.date and/or options.etag values. + */ +configUpdater.downloadFile = function (url, localPath, options) { + options = Object.assign({ + date: null, + hashAlgorithm: "sha512" + }, options); + + return new Promise(function (resolve, reject) { + + service.log("Downloading", url, "to", localPath); + + var headers = {}; + if (options.etag) { + // If supported, the server should respond with "304 Not Updated" if the resource is the same. + headers["If-None-Match"] = "\"" + options.etag + "\""; + } + if (options.date) { + // If supported, the server should respond with "304 Not Updated" if the resource hasn't changed since + // the date. + headers["If-Modified-Since"] = options.date; + } + + var req = request.get({ + url: url, + headers: headers + }); + + req.on("error", function (err) { + reject({ + isError: true, + message: "Unable to download from " + url + ": " + err.message, + url: url, + error: err + }); + }); + + req.on("response", function (response) { + service.log("Download response", url, response.statusCode, response.statusMessage); + if (response.statusCode === 200) { + var content = fs.createWriteStream(localPath); + + response.pipe(content); + + content.on("finish", function () { + service.log("Download complete", url); + configUpdater.hashFile(localPath, options.hashAlgorithm).then(function (hash) { + var result = { + hash: hash + }; + + // Return the ETag header if it exists, but not weak ones. + var etag = response.headers.etag; + if (etag && !etag.startsWith("W/")) { + if (etag.startsWith("\"") && etag.endsWith("\"")) { + // remove the surrounding quotes. + result.etag = etag.substring(1, etag.length - 1); + } else { + result.etag = etag; + } + } + + result.date = response.headers["last-modified"] || undefined; + + resolve(result); + }, reject); + }); + content.on("error", function (err) { + reject({ + isError: true, + message: "Unable to download from " + url + " to " + localPath + ": " + err.message, + error: err, + url: url, + localPath: localPath + }); + }); + } else if (response.statusCode === 304) { + // "Not Modified" + resolve({notModified: true}); + } else { + reject({ + isError: true, + message: "Unable to download from " + url + ": " + response.statusCode + " " + response.statusMessage, + url: url + }); + } + }); + }); +}; + +/** + * Calculate the hash of a file. + * + * @param {String} file The file. + * @param {String} algorithm [optional] The algorithm. [default: sha512] + * @return {Promise} Resolves with the hash, as hex string. + */ +configUpdater.hashFile = function (file, algorithm) { + return new Promise(function (resolve, reject) { + if (!algorithm) { + algorithm = "sha512"; + } + + try { + var hash = crypto.createHash(algorithm); + + hash.on("error", function (e) { + reject(e); + }); + hash.on("finish", function () { + var result = hash.read().toString("hex"); + resolve(result); + }); + + var input = fs.createReadStream(file); + input.pipe(hash); + + input.on("error", function (e) { + reject(e); + }); + + } catch (e) { + reject(e); + } + }); +}; + +/** + * Validates the JSON (or JSON5) content of a file. + * @param {String} file The file whose content to check. + * @return {Promise} Resolves with a boolean indicating if the content is valid JSON/JSON5. + */ +configUpdater.validateJSON = function (file) { + return new Promise(function (resolve, reject) { + fs.readFile(file, "utf8", function (err, content) { + if (err) { + reject({ + isError: true, + err: err, + message: "validateJSON:" + err.message, + file: file + }); + } else { + var valid = false; + try { + JSON.parse(content); + valid = true; + } catch (e) { + try { + JSON5.parse(content); + valid = true; + } catch (e) { + // Ignore - invalid content is an expected condition for this function. + } + } + + resolve(valid); + } + }); + }); +}; + +if (service.config.autoUpdate.enabled) { + service.readyWhen(configUpdater.updateAll()); +} diff --git a/gpii-service/src/main.js b/gpii-service/src/main.js index 5bfa7a57b..8d597f647 100644 --- a/gpii-service/src/main.js +++ b/gpii-service/src/main.js @@ -24,5 +24,6 @@ require("./windows.js"); require("./gpii-ipc.js"); require("./processHandling.js"); require("./gpiiClient.js"); +require("./configUpdater.js"); service.start(); diff --git a/gpii-service/src/processHandling.js b/gpii-service/src/processHandling.js index f49704ede..0cbb83a9e 100644 --- a/gpii-service/src/processHandling.js +++ b/gpii-service/src/processHandling.js @@ -63,7 +63,7 @@ processHandling.sessionChange = function (eventType) { switch (eventType) { case "session-logon": // User just logged on - start the processes. - processHandling.startChildProcesses(); + service.isReady().then(processHandling.startChildProcesses); break; case "session-logoff": // User just logged off - stop the processes (windows should have done this already). diff --git a/gpii-service/src/service.js b/gpii-service/src/service.js index 96cbac99f..39ca1422f 100644 --- a/gpii-service/src/service.js +++ b/gpii-service/src/service.js @@ -70,7 +70,15 @@ service.logDebug = logging.debug; /** * @type Config */ -service.config = null; +service.config = { + processes: {}, + logging: { + level: "DEBUG" + }, + autoUpdate: { + lastUpdatesFile: path.join(process.env.ProgramData, "Morphic/last-updates.json5") + } +}; /** * Loads the config file, which may be found in the first of the following locations: @@ -98,6 +106,9 @@ service.loadConfig = function (dir, file) { if (service.isService) { // Use the built-in config file. configFile = path.join(__dirname, "../config/service.json5"); + } else if (fluid) { + // fluid is only defined during testing + configFile = "config/service.testing.json5"; } else { configFile = "config/service.dev.json5"; } @@ -120,13 +131,21 @@ service.loadConfig = function (dir, file) { return config; }; +/** + * The site-specific secrets file. + * @typedef {Object} SecretFile + * @property {String} site Site identifier ("domain"). + * @property {Object} clientCredentials Client credentials (private). + * @property {String} signKey Signing key (private). + */ + /** * Gets the secrets, which is the data stored in the secrets file. * * The secret is installed in a separate installer, which could occur after Morphic was installed. Also, the secret * may be later updated. Because of this, the secret is read each time it is used. * - * @return {Object} The secret, or null if the secret could not be read. This shouldn't be logged. + * @return {SecretFile} The secret, or null if the secret could not be read. This shouldn't be logged. */ service.getSecrets = function () { var secret = null; @@ -228,6 +247,20 @@ service.controlHandler = function (controlName, eventType) { service.emit("service." + controlName, eventType); }; +/** + * Returns a promise that resolves when the service is ready to start the child processes. That is, when all promises + * in `service.readyPromises` have resolved. + * @return {Promise} Resolves when the service is ready to start the child processes. + */ +service.isReady = function () { + return Promise.all(service.readyPromises); +}; + +service.readyWhen = function (promise) { + service.readyPromises.push(promise); +}; + +service.readyPromises = []; // Change directory to a sane location, allowing relative paths in the config file. var dir = null; @@ -242,7 +275,6 @@ if (service.isExe) { process.chdir(dir); // Load the configuration -service.config = service.loadConfig(dir); - +service.config = Object.assign(service.config, service.loadConfig(dir)); module.exports = service; diff --git a/gpii-service/tests/configUpdater-tests.js b/gpii-service/tests/configUpdater-tests.js new file mode 100644 index 000000000..f2f04f923 --- /dev/null +++ b/gpii-service/tests/configUpdater-tests.js @@ -0,0 +1,1238 @@ +/* Tests for configUpdater.js + * + * Copyright 2019 Raising the Floor - International + * + * Licensed under the New BSD license. You may not use this file except in + * compliance with this License. + * + * The R&D leading to these results received funding from the + * Department of Education - Grant H421A150005 (GPII-APCP). However, + * these results do not necessarily represent the policy of the + * Department of Education, and you should not assume endorsement by the + * Federal Government. + * + * You may obtain a copy of the License at + * https://github.com/GPII/universal/blob/master/LICENSE.txt + */ + +"use strict"; + +var jqUnit = require("node-jqunit"), + os = require("os"), + http = require("http"), + JSON5 = require("json5"), + path = require("path"), + fs = require("fs"); + +var configUpdater = require("../src/configUpdater.js"); + +var teardowns = []; + +jqUnit.module("GPII configUpdater tests", { + teardown: function () { + while (teardowns.length) { + teardowns.pop()(); + } + } +}); + +var configUpdaterTests = {}; + +configUpdaterTests.testDateOld = "Fri, 31 Dec 1999 23:59:59 GMT"; +configUpdaterTests.testDate = "Wed, 01 Jan 2020 12:34:56 GMT"; + +configUpdaterTests.validateJSONTests = [ + { + id: "JSON", + input: "{\"key\": \"value\"}", + expect: true + }, + { + id: "JSON5", + input: "/* */ {key: \"value\",}", + expect: true + }, + { + id: "invalid", + input: ":}", + expect: false + }, + { + id: "no file", + input: null, + expect: "reject" + } +]; + +configUpdaterTests.hashTests = [ + { + id: "valid", + input: "test subject", + algorithm: undefined, + expect: "0ec931a49e2dcfb265aa2b554be7d5d9c1b39e46dd81e24048796a54f32c361ff83f1dcec09162dc896f86cfacbb88f11c4a5a5365b556fda597efd8f77c6072" + }, + { + id: "valid md5", + input: "test subject", + algorithm: "md5", + expect: "850be8166b39e19a6ff11e998509954a" + }, + { + id: "no file", + input: null, + expect: "reject" + } +]; + +configUpdaterTests.expandTests = [ + { + obj: { + a: "A", + b: { + a: "BA", + b: "BB" + }, + c: { + b: "CB", + c: { + d: "CCD" + } + }, + d: "${a}", + e: "${c.b}", + f: "123${c.b}" + }, + tests: { + "": "", + "a": "a", + "${a}": "A", + "${a}${a}": "AA", + "1${a}2${c.b}3": "1A2CB3", + "${b.a}": "BA", + "${b.x}": "", + "${c.c.d}": "CCD", + "${b.c.d}": "", + + "${d}": "${a}", + "${e}": "${c.b}", + "${f}": "123${c.b}", + + "${a?XX}": "A", + "${a}?": "A?", + "${a}?}": "A?}", + "${a?XX}${a?XX}": "AA", + "1${a?XX}2${c.b?XX}3": "1A2CB3", + "${b.a?XX}": "BA", + "${b.x?XX}": "XX", + "${c.c.d?XX}": "CCD", + "${c?XX}": "XX", + "${c.a.a?XX}": "XX", + "${c.a.a?X.X}": "X.X", + "${c.a.a?X?X}": "X?X", + "${c.a.a?X.X?X.X}": "X.X?X.X", + "${c.a.a??}": "?", + + "${?}": "", + "${?X}": "X", + + "${a": "${a", + "${a?": "${a?", + "$": "$", + "$${a}": "$A", + "${}": "" + } + }, + { + obj: {}, + tests: { + "": "", + "a": "a", + "${a}": "", + "${a.b.c}": "", + "${a?XX}": "XX" + } + }, + { + obj: null, + tests: { + "": "", + "a": "a", + "${a}": "", + "${a.b.c}": "", + "${a?XX}": "XX" + } + } +]; + +configUpdaterTests.downloadTests = [ + { + id: "success", + content: "test subject", + hash: "0ec931a49e2dcfb265aa2b554be7d5d9c1b39e46dd81e24048796a54f32c361ff83f1dcec09162dc896f86cfacbb88f11c4a5a5365b556fda597efd8f77c6072" + }, + { + id: "etag in response", + content: "test subject", + hash: "0ec931a49e2dcfb265aa2b554be7d5d9c1b39e46dd81e24048796a54f32c361ff83f1dcec09162dc896f86cfacbb88f11c4a5a5365b556fda597efd8f77c6072", + etagResponse: "\"1234\"", + etagExpect: "1234" + }, + { + id: "weak etag in response", + content: "test subject", + hash: "0ec931a49e2dcfb265aa2b554be7d5d9c1b39e46dd81e24048796a54f32c361ff83f1dcec09162dc896f86cfacbb88f11c4a5a5365b556fda597efd8f77c6072", + etagResponse: "W/\"ABCD\"", + etagExpect: undefined + }, + { + id: "etag in request", + content: "test subject", + hash: "0ec931a49e2dcfb265aa2b554be7d5d9c1b39e46dd81e24048796a54f32c361ff83f1dcec09162dc896f86cfacbb88f11c4a5a5365b556fda597efd8f77c6072", + options: { + etag: "1234" + } + }, + { + id: "date", + content: "test subject", + hash: "0ec931a49e2dcfb265aa2b554be7d5d9c1b39e46dd81e24048796a54f32c361ff83f1dcec09162dc896f86cfacbb88f11c4a5a5365b556fda597efd8f77c6072", + options: { + date: configUpdaterTests.testDate + } + }, + { + id: "date + etag", + content: "test subject", + hash: "0ec931a49e2dcfb265aa2b554be7d5d9c1b39e46dd81e24048796a54f32c361ff83f1dcec09162dc896f86cfacbb88f11c4a5a5365b556fda597efd8f77c6072", + options: { + date: configUpdaterTests.testDate, + etag: "1234" + } + }, + { + id: "overwrite destination", + overwrite: true, + content: "test subject", + hash: "0ec931a49e2dcfb265aa2b554be7d5d9c1b39e46dd81e24048796a54f32c361ff83f1dcec09162dc896f86cfacbb88f11c4a5a5365b556fda597efd8f77c6072" + }, + { + id: "success (this file)", + file: __filename + }, + { + id: "success (large file)", + file: path.join(process.env.SystemRoot, "system32/WindowsCodecsRaw.dll") + }, + { + id: "error: 404", + content: "error", + statusCode: 404, + expect: "reject" + }, + { + id: "no update: 304", + hash: undefined, + content: "not updated", + statusCode: 304, + expect: null + }, + { + id: "bad destination", + content: "error", + destination: path.join(os.tmpdir(), "does/not/exist"), + expect: "reject" + }, + { + id: "ssl error", + url: "https://untrusted-root.badssl.com/", + expect: "reject" + }, + { + id: "remote server", + url: "https://raw.githubusercontent.com/GPII/windows/8152ce42da1091268c18f3f9e0f7d66a16cbaf32/README.md", + hash: "8c57e997c3cb28f6078648b47c881ef4e5b833f2f427d30e1c246b0e790decda8ab53c474d131b2500c26a2e16c1918b81caad5ed3cf975584f1eeed5f601c2f" + } +]; + +configUpdaterTests.applyUpdateTests = [ + { + id: "update (no current file)", + input: { + source: "new", + destination: null, + backup: null + }, + expect: { + source: null, + destination: "new", + backup: null + } + }, + { + id: "update", + input: { + source: "new", + destination: "current", + backup: null + }, + expect: { + source: null, + destination: "new", + backup: "current" + } + }, + { + id: "update, existing backup", + input: { + source: "new", + destination: "current", + backup: "old" + }, + expect: { + source: null, + destination: "new", + backup: "current" + } + }, + { + id: "update, no current file, existing backup", + input: { + source: "new", + destination: null, + backup: "old" + }, + expect: { + source: null, + destination: "new", + backup: "old" + } + } +]; + +configUpdaterTests.updateFileTests = [ + { + id: "not updated previously", + input: { + // No last update info + lastUpdate: {}, + content: "old" + }, + response: { + statusCode: 200, + headers: { + "Last-Modified": configUpdaterTests.testDate + }, + body: "new" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDate, + etag: undefined, + previous: true + }, + request: { + "if-modified-since": undefined, + "if-none-match": undefined + }, + content: "new" + } + }, + { + id: "updated (no ETag)", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld + }, + content: "old" + }, + response: { + statusCode: 200, + headers: { + "Last-Modified": configUpdaterTests.testDate + }, + body: "new" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDate, + etag: undefined, + previous: true + }, + request: { + "if-modified-since": configUpdaterTests.testDateOld, + "if-none-match": undefined + }, + content: "new" + } + }, + { + id: "updated (ETag)", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld + }, + content: "old" + }, + response: { + statusCode: 200, + headers: { + "Last-Modified": configUpdaterTests.testDate, + ETag: "\"the ETag value\"" + }, + body: "new" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDate, + etag: "the ETag value", + previous: true + }, + request: { + "if-modified-since": configUpdaterTests.testDateOld, + "if-none-match": undefined + }, + content: "new" + } + }, + { + id: "updated (new ETag)", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld, + etag: "the last ETag value" + }, + content: "old" + }, + response: { + statusCode: 200, + headers: { + "Last-Modified": configUpdaterTests.testDate, + ETag: "\"the new ETag value\"" + }, + body: "new" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDate, + etag: "the new ETag value", + previous: true + }, + request: { + "if-modified-since": configUpdaterTests.testDateOld, + "if-none-match": "\"the last ETag value\"" + }, + content: "new" + } + }, + { + id: "updated (new ETag, same content)", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld, + etag: "the last ETag value" + }, + content: "new" + }, + response: { + statusCode: 200, + headers: { + "Last-Modified": configUpdaterTests.testDate, + ETag: "\"the new ETag value\"" + }, + body: "new" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDate, + etag: "the new ETag value", + previous: false + }, + request: { + "if-modified-since": configUpdaterTests.testDateOld, + "if-none-match": "\"the last ETag value\"" + }, + content: "new" + } + }, + { + id: "always update", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld, + etag: "the old ETag value" + }, + content: "old", + always: true + }, + response: { + statusCode: 200, + headers: { + "Last-Modified": configUpdaterTests.testDate, + ETag: "\"the new ETag value\"" + }, + body: "new" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDate, + etag: "the new ETag value", + previous: true + }, + request: { + "if-modified-since": undefined, + "if-none-match": undefined + }, + content: "new" + } + }, + { + id: "always update (not required)", + input: { + lastUpdate: { + date: configUpdaterTests.testDate, + etag: "the new ETag value" + }, + content: "new", + always: true + }, + response: { + statusCode: 200, + headers: { + "Last-Modified": configUpdaterTests.testDate, + ETag: "\"the new ETag value\"" + }, + body: "new" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDate, + etag: "the new ETag value" + // no previous field - the content hasn't changed, so back-up is not required. + }, + request: { + "if-modified-since": undefined, + "if-none-match": undefined + }, + content: "new" + } + }, + { + id: "no update", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld, + etag: "the last ETag value" + }, + content: "old" + }, + response: { + statusCode: 304, + headers: { + "Last-Modified": configUpdaterTests.testDate, + ETag: "\"the new ETag value\"" + }, + body: null + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDateOld, + etag: "the last ETag value" + }, + request: { + "if-modified-since": configUpdaterTests.testDateOld, + "if-none-match": "\"the last ETag value\"" + }, + content: "old" + } + }, + { + id: "no update (with body)", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld, + etag: "the last ETag value" + }, + content: "old" + }, + response: { + statusCode: 304, + headers: { + "Last-Modified": configUpdaterTests.testDate, + ETag: "\"the new ETag value\"" + }, + body: "new" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDateOld, + etag: "the last ETag value", + previous: false + }, + request: { + "if-modified-since": configUpdaterTests.testDateOld, + "if-none-match": "\"the last ETag value\"" + }, + content: "old" + } + }, + { + id: "updated (JSON)", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld + }, + content: "old", + isJSON: true + }, + response: { + statusCode: 200, + headers: { + "Last-Modified": configUpdaterTests.testDate + }, + body: "{\"key\": \"value\",\n\"key2\":123\n}" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDate, + etag: undefined, + previous: true + }, + request: { + "if-modified-since": configUpdaterTests.testDateOld, + "if-none-match": undefined + }, + content: "{\"key\": \"value\",\n\"key2\":123\n}" + } + }, + { + id: "updated (JSON5)", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld + }, + content: "old", + isJSON: true + }, + response: { + statusCode: 200, + headers: { + "Last-Modified": configUpdaterTests.testDate + }, + body: "/* JSON5 */ {key: \"value\",\nkey2:123,\n}" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDate, + etag: undefined, + previous: true + }, + request: { + "if-modified-since": configUpdaterTests.testDateOld, + "if-none-match": undefined + }, + content: "/* JSON5 */ {key: \"value\",\nkey2:123,\n}" + } + }, + { + id: "updated (invalid JSON/JSON5)", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld + }, + content: "old", + isJSON: true + }, + response: { + statusCode: 200, + headers: { + "Last-Modified": configUpdaterTests.testDate + }, + body: "} invalid JSON" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDateOld + }, + request: { + "if-modified-since": configUpdaterTests.testDateOld, + "if-none-match": undefined + }, + content: "old" + } + }, + { + id: "error", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld, + etag: "the last ETag value" + }, + content: "old" + }, + response: { + statusCode: 418, + headers: { + "Last-Modified": configUpdaterTests.testDate, + ETag: "\"the new ETag value\"" + }, + body: null + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDateOld, + etag: "the last ETag value", + previous: false + }, + request: { + "if-modified-since": configUpdaterTests.testDateOld, + "if-none-match": "\"the last ETag value\"" + }, + content: "old" + } + }, + { + id: "error (with body)", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld, + etag: "the last ETag value" + }, + content: "old" + }, + response: { + statusCode: 418, + headers: { + "Last-Modified": configUpdaterTests.testDate, + ETag: "\"the new ETag value\"" + }, + body: "error message" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDateOld, + etag: "the last ETag value", + previous: false + }, + request: { + "if-modified-since": configUpdaterTests.testDateOld, + "if-none-match": "\"the last ETag value\"" + }, + content: "old" + } + } +]; + +configUpdaterTests.updateAllTest = { + input: { + lastUpdates: { + files: { + file1: { + id: "1" + }, + file3: { + id: "3" + }, + file4: { + id: "4" + } + } + }, + autoUpdateFiles: [ + {url: "1", path: "file1"}, + {url: undefined, path: "file2"}, + {url: "3", path: "file3", error: true}, + {url: "4", path: "file4"} + ] + }, + expect: { + lastUpdates: { + files: { + file1: { + id: "1", + handled: true + }, + file3: { + id: "3" + }, + file4: { + id: "4", + handled: true + }, + file2: { + handled: true + } + } + } + } +}; + +/** + * Gets a temporary file name, and deletes it at the end of the current test. + * @param {String} testName Name of the current test. + * @return {String} Path to the temporary file. + */ +configUpdaterTests.getTempFile = function (testName) { + var tempFile = path.join(os.tmpdir(), "gpii-test-" + testName + Math.random()); + + teardowns.push(function () { + try { + fs.unlinkSync(tempFile); + } catch (e) { + // ignore + } + }); + + return tempFile; +}; + +/** + * Asserts that a file's content matches what is expected. + * + * @param {String} message The assertion message. + * @param {String} expected The expected content, or null if the file is not expected to exist. + * @param {String} path The file to check. + */ +configUpdaterTests.assertFile = function (message, expected, path) { + var content ; + try { + content = fs.readFileSync(path, "utf8"); + } catch (err) { + if (err.code === "ENOENT") { + content = null; + } else { + jqUnit.fail(err); + content = err.message; + } + } + jqUnit.assertEquals(message, expected || "(no file)", content || "(no file)"); +}; + +jqUnit.asyncTest("Test validateJSON", function () { + + jqUnit.expect(configUpdaterTests.validateJSONTests.length); + + // For each test, write to a file and check the result of validateJSON. + var promises = configUpdaterTests.validateJSONTests.map(function (test) { + var tempFile = configUpdaterTests.getTempFile("validateJSON"); + + if (test.input !== null) { + fs.writeFileSync(tempFile, test.input); + } + + var suffix = " - test.id: " + test.id; + return configUpdater.validateJSON(tempFile).then(function (value) { + jqUnit.assertEquals("validateJSON should resolve with expected value" + suffix, test.expect, value); + }, function (err) { + if (test.expect !== "reject") { + console.log(err); + } + jqUnit.assertEquals("validateJSON rejected" + suffix, "reject", test.expect); + }); + }); + + Promise.all(promises).then(jqUnit.start, jqUnit.fail); +}); + +jqUnit.asyncTest("Test hashFile", function () { + + jqUnit.expect(configUpdaterTests.hashTests.length); + + // For each test, write to a file and check the result of hashFile. + var promises = configUpdaterTests.hashTests.map(function (test) { + var tempFile = configUpdaterTests.getTempFile("hashFile"); + + if (test.input !== null) { + fs.writeFileSync(tempFile, test.input); + } + + var suffix = " - test.id: " + test.id; + return configUpdater.hashFile(tempFile, test.algorithm).then(function (value) { + jqUnit.assertEquals("hashFile should resolve with expected value" + suffix, test.expect, value); + }, function (err) { + if (test.expect !== "reject") { + console.log(err); + } + jqUnit.assertEquals("hashFile rejected" + suffix, "reject", test.expect); + }); + }); + + Promise.all(promises).then(jqUnit.start, jqUnit.fail); +}); + +jqUnit.test("expand tests", function () { + configUpdaterTests.expandTests.forEach(function (test) { + console.log("expand object:", test.obj); + for (var unexpanded in test.tests) { + var expect = test.tests[unexpanded]; + var result = configUpdater.expand(unexpanded, test.obj); + console.log(unexpanded, result); + jqUnit.assertEquals("result of expand must match the expected result for " + unexpanded, expect, result); + } + }); +}); + +jqUnit.asyncTest("Test downloadFile", function () { + + jqUnit.expect(configUpdaterTests.downloadTests.length * 3); + + var server = http.createServer(); + server.listen(0, "127.0.0.1"); + + server.on("request", function (req, res) { + var index = parseInt(req.url.substr(1)); + var test = configUpdaterTests.downloadTests[index]; + + if (test.statusCode) { + res.statusCode = test.statusCode; + } + if (test.etagResponse) { + res.setHeader("ETag", test.etagResponse); + } + + jqUnit.expect(2); + var suffix = " - test.id: " + test.id; + + // Check the ETag in the request. + if (test.options && test.options.etag) { + var expectETag = "\"" + test.options.etag + "\""; + jqUnit.assertEquals("If-None-Match header must match the ETag" + suffix, + expectETag, req.headers["if-none-match"]); + } else { + jqUnit.assertNull("If-None-Match header should not have been sent" + suffix, + req.headers["if-none-match"]); + } + + // Check the date in the request. + if (test.options && test.options.date) { + jqUnit.assertEquals("If-Modified-Since header must match the date" + suffix, + test.options.date, req.headers["if-modified-since"]); + } else { + jqUnit.assertNull("If-Modified-Since header should not have been sent" + suffix, + req.headers["if-modified-since"]); + } + + // Send the response data. + if (test.file) { + fs.createReadStream(test.file).pipe(res); + } else { + res.end(test.content); + } + }); + + server.on("listening", function () { + var localUrl = "http://" + server.address().address + ":" + server.address().port + "/"; + console.log("http server listening on " + localUrl); + + // For each test, write to a file and check the result of hashFile. + var promises = configUpdaterTests.downloadTests.map(function (test, index) { + var destination; + if (test.destination) { + destination = test.destination; + } else { + destination = configUpdaterTests.getTempFile("downloadFile"); + + if (test.overwrite) { + // Testing overwriting an existing file; create one. + fs.writeFileSync(destination, "existing content"); + } + } + + var url = test.url || (localUrl + index); + + var suffix = " - test.id: " + test.id; + return configUpdater.downloadFile(url, destination, test.options).then(function (value) { + // Check the resolve value + if (test.hasOwnProperty("hash")) { + jqUnit.assertEquals("downloadFile should resolve with expected value.hash" + suffix, + test.hash, value.hash); + } else { + // Hash can't be precalculated, just check if it looks like one. + var isValid = /^[a-f0-9]{128}$/i.test(value.hash); + jqUnit.assertTrue("downloadFile should resolve with a hash" + suffix, isValid); + } + + // Check the ETag header has been read correctly + if (test.hasOwnProperty("etagExpect")) { + jqUnit.assertEquals("downloadFile should resolve with expected value.etag" + suffix, + test.etagExpect, value.etag); + } else { + jqUnit.assert("balancing assert count"); + } + + if (value.hash) { + // Check the content (downloadFile does do this, however let's check if it's written to the right file). + return configUpdater.hashFile(destination).then(function (hash) { + jqUnit.assertEquals("downloadFile content should be what's expected" + suffix, + test.hash || value.hash, hash); + }); + } else { + // Download did not provide a hash (304 response) + jqUnit.assertEquals("downloadFile should not return a hash for 304 response", 304, test.statusCode); + } + }, function (err) { + console.log("this error is probably expected:", err); + jqUnit.assertEquals("downloadFile rejected" + suffix, "reject", test.expect); + // This execution branch has 2 less asserts than the other. + jqUnit.expect(-2); + }); + }); + Promise.all(promises)["catch"](jqUnit.fail).then(function () { + jqUnit.start(); + }); + }); + + teardowns.push(function () { + server.close(); + server.unref(); + }); + +}); + +jqUnit.asyncTest("Test load/save last updates file", function () { + jqUnit.expect(4); + + // Test saving + var tempFile = configUpdaterTests.getTempFile("lastUpdates"); + + var original = { + lastCheck: "1999-12-31T12:34:56.789Z", + files: { + file1: {path: "first"}, + file2: {path: "second"} + } + }; + + var dateBefore = new Date().toISOString(); + + configUpdater.saveLastUpdates(original, tempFile).then(function () { + // Read and parse the written content (the content doesn't matter, as long as it parses back to the original) + var writtenContent = fs.readFileSync(tempFile, "utf8"); + var written = JSON5.parse(writtenContent); + + // Test the lastCheck has been updated (any time during saveLastUpdates call) + var dateAfter = new Date().toISOString(); + jqUnit.assertTrue("lastCheck field should be updated", + written.lastCheck >= dateBefore && written.lastCheck <= dateAfter); + + // Set it back to the original, just to force it through the next assert. + var lastCheck = written.lastCheck; + written.lastCheck = original.lastCheck; + + jqUnit.assertDeepEq("saveLastUpdates should have written the original data", original, written); + + // Test loading + return configUpdater.loadLastUpdates(tempFile).then(function (loaded) { + jqUnit.assertDeepEq("Re-loaded lastCheck field should match the new one", lastCheck, loaded.lastCheck); + loaded.lastCheck = original.lastCheck; + jqUnit.assertDeepEq("Re-loaded object should match the original object", original, loaded); + }); + })["catch"](jqUnit.fail).then(jqUnit.start); +}); + +jqUnit.asyncTest("Test load last updates file (failures)", function () { + + jqUnit.expect(2); + + var badFile = configUpdaterTests.getTempFile("badLastUpdate"); + fs.writeFileSync(badFile, "}:"); + + // Skeleton lastUpdated object. loadLastUpdates should return this if there's no current file. + var expect = { + files: {} + }; + + var tests = [ + configUpdater.loadLastUpdates(badFile).then(function (loaded) { + jqUnit.assertDeepEq("loadLastUpdates(badFile) should return skeleton data", expect, loaded); + }), + configUpdater.loadLastUpdates("/does/not/exist").then(function (loaded) { + jqUnit.assertDeepEq("loadLastUpdates(non-exisiting file) should return skeleton data", expect, loaded); + }) + ]; + + Promise.all(tests)["catch"](jqUnit.fail).then(jqUnit.start); +}); + +jqUnit.asyncTest("Test save last updates file (failures)", function () { + + jqUnit.expect(1); + configUpdater.saveLastUpdates({}, "/").then(function () { + jqUnit.fail("saveLastUpdates should not resolve on error"); + }, function () { + jqUnit.assert("saveLastUpdates should reject on error"); + }).then(jqUnit.start); +}); + +jqUnit.asyncTest("Test applyUpdate", function () { + jqUnit.expect(configUpdaterTests.applyUpdateTests.length * 3); + + var promises = configUpdaterTests.applyUpdateTests.map(function (test) { + var sourceFile = configUpdaterTests.getTempFile("applyUpdate-source"); + var destinationFile = configUpdaterTests.getTempFile("applyUpdate-dest"); + var backupFile = destinationFile + ".previous"; + + if (test.input.source !== null) { + fs.writeFileSync(sourceFile, test.input.source); + } + if (test.input.destination !== null) { + fs.writeFileSync(destinationFile, test.input.destination); + } + if (test.input.backup !== null) { + fs.writeFileSync(backupFile, test.input.backup); + } + + var suffix = " - test.id: " + test.id; + return configUpdater.applyUpdate(sourceFile, destinationFile).then(function () { + + configUpdaterTests.assertFile("source file should match expected" + suffix, test.expect.source, sourceFile); + configUpdaterTests.assertFile("destination file should match expected" + suffix, + test.expect.destination, destinationFile); + configUpdaterTests.assertFile("backup file should match expected" + suffix, test.expect.backup, backupFile); + }); + }); + + Promise.all(promises)["catch"](jqUnit.fail).then(jqUnit.start); +}); + +jqUnit.asyncTest("Test updateFile", function () { + + jqUnit.expect(configUpdaterTests.updateFileTests.length * 3); + + // Mock getDateString so it returns a predictable date. + var getDateStringOrig = configUpdater.getDateString; + configUpdater.getDateString = function () { + return configUpdaterTests.testDate; + }; + teardowns.push(function () { + configUpdater.getDateString = getDateStringOrig; + }); + + var server = http.createServer(); + server.listen(0, "127.0.0.1"); + + // Respond to requests using the details from the test's response object. + server.on("request", function (req, res) { + var index = parseInt(req.url.substr(1)); + var test = configUpdaterTests.updateFileTests[index]; + var suffix = " - test.id: " + test.id; + + // Check the request headers + var checkHeaders = {}; + for (var key in test.expect.request) { + checkHeaders[key] = req.headers[key]; + } + + jqUnit.assertDeepEq("All expected headers must match the requested headers" + suffix, + test.expect.request, checkHeaders); + + // Set the headers + if (test.response.headers) { + for (var header in test.response.headers) { + res.setHeader(header, test.response.headers[header]); + } + } + console.log(req.url, req.headers); + res.statusCode = test.response.statusCode; + + res.end(test.response.body); + }); + + server.on("listening", function () { + var localUrl = "http://" + server.address().address + ":" + server.address().port + "/"; + console.log("http server listening on " + localUrl); + + var promises = configUpdaterTests.updateFileTests.map(function (test, index) { + var suffix = " - test.id: " + test.id; + + /** @type {AutoUpdateFile} */ + var file = { + path: configUpdaterTests.getTempFile("updateFile"), + url: localUrl + index, + isJSON: test.input.isJSON, + always: test.input.always + }; + + var lastUpdate = Object.assign({}, test.input.lastUpdate); + + if (test.input.content) { + // Write the "old" content. + fs.writeFileSync(file.path, test.input.content); + } + + return configUpdater.updateFile(file, lastUpdate).then(function (result) { + // If the previous file is expected, set the field to the filename, otherwise remove it. + if (test.expect.lastUpdate.previous) { + test.expect.lastUpdate.previous = file.path + ".previous"; + } else { + delete test.expect.lastUpdate.previous; + } + + jqUnit.assertDeepEq("resolved value from updateFile should match the expected" + suffix, + test.expect.lastUpdate, result); + + // Check the file has been updated as expected. + configUpdaterTests.assertFile("Target file should contain the expected content", + test.expect.content, file.path); + + }, jqUnit.fail); + + }); + Promise.all(promises)["catch"](jqUnit.fail).then(function () { + jqUnit.start(); + }); + }); + + teardowns.push(function () { + server.close(); + server.unref(); + }); + +}); + + +jqUnit.asyncTest("Test updateAll", function () { + + var test = configUpdaterTests.updateAllTest; + jqUnit.expect(test.input.autoUpdateFiles.length + 2); + + var service = require("../src/service.js"); + service.config.autoUpdate = { + enabled: true, + files: test.input.autoUpdateFiles, + lastUpdatesFile: configUpdaterTests.getTempFile("lastUpdatesFile") + }; + + var files = []; + + var updateFileOrig = configUpdater.updateFile; + teardowns.push(function () { + configUpdater.updateFile = updateFileOrig; + }); + + // Check that updateFile gets called for every updated file, with the correct data. + configUpdater.updateFile = function (update, lastUpdate) { + files.push(update.path); + jqUnit.assertEquals("update argument must correspond with the correct lastUpdate", update.url, lastUpdate.id); + return update.error + ? Promise.reject({isError: true}) + : Promise.resolve(Object.assign({}, lastUpdate, {handled:true})); + }; + + // Save the data, to ensure the file gets loaded in updateAll + configUpdater.saveLastUpdates(test.input.lastUpdates).then(function () { + return configUpdater.updateAll().then(function () { + // Check every file was processed. + var allFiles = service.config.autoUpdate.files.map(function (file) { + return file.path; + }); + jqUnit.assertDeepEq("updateFile should be called for every file", allFiles.sort(), files.sort()); + + // Check that the last-updates file had been written to. + return configUpdater.loadLastUpdates().then(function (lastUpdates) { + // Remove the lastCheck field + delete lastUpdates.lastCheck; + jqUnit.assertDeepEq("lastUpdates should the expected value", test.expect.lastUpdates, lastUpdates); + }); + }); + })["catch"](jqUnit.fail).then(jqUnit.start); +}); diff --git a/gpii-service/tests/service-tests.js b/gpii-service/tests/service-tests.js index c4081da28..e3d8baace 100644 --- a/gpii-service/tests/service-tests.js +++ b/gpii-service/tests/service-tests.js @@ -20,8 +20,7 @@ var jqUnit = require("node-jqunit"), os = require("os"), path = require("path"), - fs = require("fs"), - service = require("../src/service.js"); + fs = require("fs"); var teardowns = []; @@ -34,6 +33,7 @@ jqUnit.module("GPII service tests", { }); jqUnit.test("Test config loader", function () { + var service = require("../src/service.js"); // service.js should already have called service.config. jqUnit.assertNotNull("service.config is called on startup", service.config); @@ -53,7 +53,6 @@ jqUnit.test("Test config loader", function () { fs.writeFileSync(testFile, "{testLoaded: true}"); - var origConfig = service.config; // Check a config file will be loaded if the process is running as a service try { service.config = null; @@ -62,22 +61,23 @@ jqUnit.test("Test config loader", function () { jqUnit.assertTrue("config should be loaded when running as a service", service.config && service.config.testLoaded); } finally { - service.isService = false; - service.config = origConfig; + // Ensure the next user of service.js gets a clean one + delete require.cache[require.resolve("../src/service.js")]; } }); jqUnit.test("Test secret loading", function () { - var secret = service.getSecrets(); - jqUnit.assertTrue("Secret should have been loaded", !!secret); - - var origFile = service.config.secretFile; try { + var service = require("../src/service.js"); + var secret = service.getSecrets(); + jqUnit.assertTrue("Secret should have been loaded", !!secret); + // Try a secret file that does not exist service.config.secretFile = "does/not/exist"; var secret2 = service.getSecrets(); jqUnit.assertNull("No secret should have been loaded", secret2); } finally { - service.config.secretFile = origFile; + // Ensure the next user of service.js gets a clean one + delete require.cache[require.resolve("../src/service.js")]; } }); From 5914bb33bf5bedae71a789194e9213c1dc2f7d7d Mon Sep 17 00:00:00 2001 From: ste Date: Tue, 3 Dec 2019 11:10:27 +0000 Subject: [PATCH 093/123] GPII-4244: Not updating files when no url is configured --- gpii-service/src/configUpdater.js | 119 ++++++++++++---------- gpii-service/tests/all-tests.js | 1 + gpii-service/tests/configUpdater-tests.js | 59 +++++++++-- 3 files changed, 117 insertions(+), 62 deletions(-) diff --git a/gpii-service/src/configUpdater.js b/gpii-service/src/configUpdater.js index ec428b603..e1b769cb3 100644 --- a/gpii-service/src/configUpdater.js +++ b/gpii-service/src/configUpdater.js @@ -167,58 +167,63 @@ configUpdater.updateFile = function (update, lastUpdate) { var togo = Object.assign({}, lastUpdate); var downloadHash, localHash; - var downloadOptions; - var hashPromise; - if (fs.existsSync(update.path)) { - // Hash the existing file. - hashPromise = configUpdater.hashFile(update.path, "sha512")["catch"](function (err) { - service.log("updateFile: hash failed", err); - }).then(function (hash) { - localHash = hash; - }); - - if (!update.always) { - // Ask the server to only send the file if it's newer. - downloadOptions = { - date: lastUpdate.date, - etag: lastUpdate.etag - }; - } - } - - // Download the file. var url = configUpdater.expand(update.url, service.getSecrets()); - var tempFile = path.join(os.tmpdir(), "morphic-update." + Math.random()); - var downloadPromise = configUpdater.downloadFile(url, tempFile, downloadOptions).then(function (result) { - if (!result.notModified) { - // Validate the JSON - var p = update.isJSON - ? configUpdater.validateJSON(tempFile) - : Promise.resolve(true); - return p.then(function (isValid) { - if (isValid) { - downloadHash = result.hash; - togo.etag = result.etag; - togo.date = result.date; - } + if (url) { + var downloadOptions; + var hashPromise; + if (fs.existsSync(update.path)) { + // Hash the existing file. + hashPromise = configUpdater.hashFile(update.path, "sha512")["catch"](function (err) { + service.log("updateFile: hash failed", err); + }).then(function (hash) { + localHash = hash; }); - } - }, function (err) { - service.logWarn("updateFile: download failed", err); - }); - return Promise.all([downloadPromise, hashPromise]).then(function () { - var updatePromise; - // Only perform the update if the file is different, to avoid needlessly overwriting the backup. - if (downloadHash && downloadHash !== localHash) { - updatePromise = configUpdater.applyUpdate(tempFile, update.path).then(function (backupPath) { - togo.previous = backupPath; - return togo; - }); + if (!update.always) { + // Ask the server to only send the file if it's newer. + downloadOptions = { + date: lastUpdate.date, + etag: lastUpdate.etag + }; + } } - return updatePromise || togo; - }); + // Download the file. + var tempFile = path.join(os.tmpdir(), "morphic-update." + Math.random()); + var downloadPromise = configUpdater.downloadFile(url, tempFile, downloadOptions).then(function (result) { + if (!result.notModified) { + // Validate the JSON + var p = update.isJSON + ? configUpdater.validateJSON(tempFile) + : Promise.resolve(true); + return p.then(function (isValid) { + if (isValid) { + downloadHash = result.hash; + togo.etag = result.etag; + togo.date = result.date; + } + }); + } + }, function (err) { + service.logWarn("updateFile: download failed", err); + }); + + return Promise.all([downloadPromise, hashPromise]).then(function () { + var updatePromise; + // Only perform the update if the file is different, to avoid needlessly overwriting the backup. + if (downloadHash && downloadHash !== localHash) { + updatePromise = configUpdater.applyUpdate(tempFile, update.path).then(function (backupPath) { + togo.previous = backupPath; + return togo; + }); + } + + return updatePromise || togo; + }); + } else { + service.log("Not updating " + update.path + ": no url"); + return Promise.resolve(togo); + } }; /** @@ -231,21 +236,33 @@ configUpdater.updateFile = function (update, lastUpdate) { * * @param {String} unexpanded The input string, containing zero or more expanders. * @param {Object} sourceObject The object which the paths in the expanders refer to. + * @param {String} alwaysExpand `true` to make expanders that resolve to null/undefined resolve to an empty + * string, otherwise the function returns null. * @return {String} The input string, with the expanders replaced by the value of the field they refer to. */ -configUpdater.expand = function (unexpanded, sourceObject) { +configUpdater.expand = function (unexpanded, sourceObject, alwaysExpand) { + var unresolved = false; // Replace all occurences of "${...}" - return unexpanded.replace(/\$\{([^?}]*)(\?([^}]*))?\}/g, function (match, expression, defaultGroup, defaultValue) { + var result = unexpanded.replace(/\$\{([^?}]*)(\?([^}]*))?\}/g, function (match, expression, defaultGroup, defaultValue) { // Resolve the path to a field, deep in the object. var value = expression.split(".").reduce(function (parent, property) { return (parent && parent.hasOwnProperty(property)) ? parent[property] : undefined; }, sourceObject); - if (value === undefined || typeof(value) === "object") { - value = defaultValue === undefined ? "" : defaultValue; + if (value === undefined || (typeof(value) === "object")) { + if (defaultGroup) { + value = defaultValue; + } + if (value === undefined || value === null) { + if (!alwaysExpand) { + unresolved = true; + } + value = ""; + } } return value; }); + return unresolved ? null : result; }; /** diff --git a/gpii-service/tests/all-tests.js b/gpii-service/tests/all-tests.js index d1ee04c52..fea026eb5 100644 --- a/gpii-service/tests/all-tests.js +++ b/gpii-service/tests/all-tests.js @@ -27,6 +27,7 @@ if (!global.fluid) { require("./processHandling-tests.js"); require("./pipe-messaging-tests.js"); require("./gpii-client-tests.js"); + require("./configUpdater-tests.js"); return; } diff --git a/gpii-service/tests/configUpdater-tests.js b/gpii-service/tests/configUpdater-tests.js index f2f04f923..c023b92d9 100644 --- a/gpii-service/tests/configUpdater-tests.js +++ b/gpii-service/tests/configUpdater-tests.js @@ -109,9 +109,11 @@ configUpdaterTests.expandTests = [ "${a}${a}": "AA", "1${a}2${c.b}3": "1A2CB3", "${b.a}": "BA", - "${b.x}": "", + "${b.x}": ["", null], + "12${b.x}34": ["1234", null], + "12${a}34${b.x}56": ["12A3456", null], "${c.c.d}": "CCD", - "${b.c.d}": "", + "${b.c.d}": ["", null], "${d}": "${a}", "${e}": "${c.b}", @@ -139,7 +141,7 @@ configUpdaterTests.expandTests = [ "${a?": "${a?", "$": "$", "$${a}": "$A", - "${}": "" + "${}": ["", null] } }, { @@ -147,8 +149,9 @@ configUpdaterTests.expandTests = [ tests: { "": "", "a": "a", - "${a}": "", - "${a.b.c}": "", + "${a}": ["", null], + "${a.b.c}": ["", null], + "${a?}": "", "${a?XX}": "XX" } }, @@ -157,8 +160,8 @@ configUpdaterTests.expandTests = [ tests: { "": "", "a": "a", - "${a}": "", - "${a.b.c}": "", + "${a}": ["", null], + "${a.b.c}": ["", null], "${a?XX}": "XX" } } @@ -545,6 +548,26 @@ configUpdaterTests.updateFileTests = [ content: "old" } }, + { + id: "no update (no url)", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld, + etag: "the last ETag value" + }, + content: "old", + url: "" + }, + response: null, + expect: { + lastUpdate: { + date: configUpdaterTests.testDateOld, + etag: "the last ETag value" + }, + request: null, + content: "old" + } + }, { id: "no update (with body)", input: { @@ -864,9 +887,18 @@ jqUnit.test("expand tests", function () { console.log("expand object:", test.obj); for (var unexpanded in test.tests) { var expect = test.tests[unexpanded]; - var result = configUpdater.expand(unexpanded, test.obj); - console.log(unexpanded, result); - jqUnit.assertEquals("result of expand must match the expected result for " + unexpanded, expect, result); + + if (!Array.isArray(expect)) { + expect = [expect, expect]; + } + + var result = configUpdater.expand(unexpanded, test.obj, true); + jqUnit.assertEquals("result of expand(alwaysExpand=true) must match the expected result for " + unexpanded, + expect[0], result); + + var result2 = configUpdater.expand(unexpanded, test.obj, false); + jqUnit.assertEquals("result of expand(alwaysExpand=false) must match the expected result for " + unexpanded, + expect[1], result2); } }); }); @@ -1147,7 +1179,7 @@ jqUnit.asyncTest("Test updateFile", function () { /** @type {AutoUpdateFile} */ var file = { path: configUpdaterTests.getTempFile("updateFile"), - url: localUrl + index, + url: test.input.hasOwnProperty("url") ? test.input.url : localUrl + index, isJSON: test.input.isJSON, always: test.input.always }; @@ -1174,6 +1206,11 @@ jqUnit.asyncTest("Test updateFile", function () { configUpdaterTests.assertFile("Target file should contain the expected content", test.expect.content, file.path); + if (!test.expect.request) { + // For tests with no requests, the request assertion isn't hit. + jqUnit.assert("no request"); + } + }, jqUnit.fail); }); From 2809cf2f728ef18fd97db82ea6523c3869bbe65c Mon Sep 17 00:00:00 2001 From: ste Date: Tue, 3 Dec 2019 12:22:07 +0000 Subject: [PATCH 094/123] GPII-3572: Reading the metrics switch from site config. --- gpii-service/README.md | 20 ++++ gpii-service/config/service.dev.child.json5 | 29 +++++- gpii-service/config/service.dev.json5 | 22 +++- gpii-service/src/processHandling.js | 83 ++++++++++++++-- gpii-service/src/service.js | 60 ++++++++--- gpii-service/test-secret.json5 | 1 + gpii-service/tests/processHandling-tests.js | 105 ++++++++++++++++++++ 7 files changed, 295 insertions(+), 25 deletions(-) diff --git a/gpii-service/README.md b/gpii-service/README.md index da73b94f0..0604ee9cc 100644 --- a/gpii-service/README.md +++ b/gpii-service/README.md @@ -118,6 +118,26 @@ This gets installed in `c:\Program Files (x86)\Morphic\windows\service.json5`. url: "https://example.com/${site}", // `site` field of the secrets file path: "example.json" }], + }, + // The path to the site config - The first successfully loaded file in the list is used + "siteConfigFile": [ + "%ProgramData%\\Morphic\\siteConfig.json5", + "%ProgramFiles(x86)%\\Morphic\\windows\\resources\\app\\siteConfig.json5", + "%ProgramFiles%\\Morphic\\windows\\resources\\app\\siteConfig.json5" + ], + // Set an environment variable based on the "metricsSwitch" value in the site config file + "gpiiConfig": { + // The environment variable name + "env": "NODE_ENV", + // The metricsSwitch value, and the value to set environment variable + // Morphic + metrics: + "on:on": "app.testing.metrics", + // No metrics or morphic: + "off:off": "app.disable", // A special case: Morphic does not get started. + // Metrics only: + "off:on": "app.metrics", + // No metrics: + "on:off": "app.testing" } } ``` diff --git a/gpii-service/config/service.dev.child.json5 b/gpii-service/config/service.dev.child.json5 index f4d3acdee..0c21a62fd 100644 --- a/gpii-service/config/service.dev.child.json5 +++ b/gpii-service/config/service.dev.child.json5 @@ -10,5 +10,32 @@ "logging": { "level": "DEBUG" }, - "secretFile": "test-secret.json5" + "secretFile": "test-secret.json5", + "autoUpdate": { + "enabled": false, + "lastUpdatesFile": "%ProgramData%\\Morphic\\last-updates.json5", + "files": [{ + // Auto-update the site config. + path: "%ProgramData%\\Morphic\\siteConfig.json5", + // Get the URL from the secrets file + url: "${siteConfigUrl}" + }], + }, + "siteConfigFile": [ + "%ProgramData%\\Morphic\\siteConfig.json5", + "%ProgramFiles(x86)%\\Morphic\\windows\\resources\\app\\siteConfig.json5", + "%ProgramFiles%\\Morphic\\windows\\resources\\app\\siteConfig.json5" + ], + + "gpiiConfig": { + "env": "NODE_ENV_EXAMPLE", // set to "NODE_ENV" when starting gpii-app + // Morphic + metrics: + "on:on": "app.testing.metrics", + // No metrics or morphic: + "off:off": "app.disable", + // Metrics only: + "off:on": "app.metrics", + // No metrics: + "on:off": "app.testing" + } } diff --git a/gpii-service/config/service.dev.json5 b/gpii-service/config/service.dev.json5 index 5073e09ea..0a21ffe6f 100644 --- a/gpii-service/config/service.dev.json5 +++ b/gpii-service/config/service.dev.json5 @@ -15,8 +15,26 @@ "enabled": false, "lastUpdatesFile": "%ProgramData%\\Morphic\\last-updates.json5", "files": [{ - url: "https://raw.githubusercontent.com/GPII/gpii-app/master/siteconfig.json5", - path: "%ProgramData%\\Morphic\\siteConfig.json5" + // Auto-update the site config. + path: "%ProgramData%\\Morphic\\siteConfig.json5", + // Get the URL from the secrets file + url: "${siteConfigUrl}" }], + }, + "siteConfigFile": [ + "%ProgramData%\\Morphic\\siteConfig.json5", + "%ProgramFiles(x86)%\\Morphic\\windows\\resources\\app\\siteConfig.json5", + "%ProgramFiles%\\Morphic\\windows\\resources\\app\\siteConfig.json5" + ], + "gpii-config": { + "env": "NODE_ENV_EXAMPLE", // set to "NODE_ENV" when starting gpii-app + // Morphic + metrics: + "on:on": "app.testing.metrics", + // No metrics or morphic: + "off:off": "app.disable", + // Metrics only: + "off:on": "app.metrics", + // No metrics: + "on:off": "app.testing" } } diff --git a/gpii-service/src/processHandling.js b/gpii-service/src/processHandling.js index 0cbb83a9e..cfb3f251a 100644 --- a/gpii-service/src/processHandling.js +++ b/gpii-service/src/processHandling.js @@ -72,25 +72,92 @@ processHandling.sessionChange = function (eventType) { } }; +/** + * Gets the list of child processes to start. + * @param {Object} allProcesses [optional] Map of processes (default:`service.config.processes`) + * @return {Array} Array of processes to start. + */ +processHandling.getProcessList = function (allProcesses) { + if (!allProcesses) { + allProcesses = service.config.processes; + } + + var config = processHandling.getGpiiConfig(); + var processesKeys = Object.keys(allProcesses); + var processes = []; + + processesKeys.forEach(function (key) { + if (allProcesses[key].disabled) { + service.logWarn("startChildProcess not starting", key, "(disabled via config)"); + } else { + var proc = Object.assign({key: key}, allProcesses[key]); + var start = true; + if (config && proc.ipc === "gpii") { + // Get the NODE_ENV value ("the metrics switch") + if (config.value === "off:off") { + service.logWarn("startChildProcess not starting", key, "(disabled via metrics switch)"); + start = false; + } else if (config.config) { + if (!proc.env) { + proc.env = {}; + } + proc.env[config.envName] = config.config; + } + } + + if (start) { + processes.push(proc); + } + } + }); + + return processes; +}; /** * Starts the configured processes. */ processHandling.startChildProcesses = function () { - var processes = Object.keys(service.config.processes); + + var processes = processHandling.getProcessList(); + + if (processes.length === 0) { + service.logWarn("No processes have been configured to start."); + } + // Start each child process sequentially. var startNext = function () { - var key = processes.shift(); - if (key && !service.config.processes[key].disabled) { - var proc = Object.assign({key: key}, service.config.processes[key]); - processHandling.startChildProcess(proc).then(startNext, function (err) { - service.logError("startChildProcess failed for " + key, err); - startNext(); - }); + var proc = processes.shift(); + if (proc) { + processHandling.startChildProcess(proc)["catch"](function (err) { + service.logError("startChildProcess failed for " + proc, err); + }).then(startNext); } }; startNext(); }; +/** + * Gets the config for the GPII process (the metrics switch). + * @return {Object} Object containing the metricsSwitch (`value`), the config to use (`config`), and the name of the + * environment variable to set (`envName`) + */ +processHandling.getGpiiConfig = function () { + var togo; + + if (service.config.gpiiConfig) { + var siteConfig = service.getSiteConfig(); + if (siteConfig && siteConfig.metricsSwitch) { + togo = { + value: siteConfig.metricsSwitch.toLowerCase(), + envName: service.config.gpiiConfig.env || "NODE_ENV" + }; + togo.config = service.config.gpiiConfig[togo.value]; + } + } + + return togo; +}; + /** * Starts a process. * diff --git a/gpii-service/src/service.js b/gpii-service/src/service.js index 39ca1422f..7c95cdc3d 100644 --- a/gpii-service/src/service.js +++ b/gpii-service/src/service.js @@ -64,6 +64,7 @@ service.logDebug = logging.debug; * @property {Object} processes Child process. * @property {Object} logging Logging settings * @property {String} secretFile The file containing site-specific information. + * @property {String} siteConfigFile The site-config file. * @property {AutoUpdateConfig} autoUpdate Auto update settings. */ @@ -131,6 +132,31 @@ service.loadConfig = function (dir, file) { return config; }; +/** + * Loads a JSON/JSON5 file, parsing the content. + * + * @param {String} file Path to the file. + * @param {String} description A descriptive name of the file, for logging. + * @return {Object|null} The de-serialised file, or null on error. + */ +service.loadJSON = function (file, description) { + var obj = null; + + try { + file = file && path.resolve(file); + if (file) { + service.log("Reading", description, file); + obj = JSON5.parse(fs.readFileSync(file)); + } else { + service.logError("The path for", description, "is not configured"); + } + } catch (e) { + service.logWarn("Unable to read", description, file, e); + } + + return obj ? obj : null; +}; + /** * The site-specific secrets file. * @typedef {Object} SecretFile @@ -148,21 +174,27 @@ service.loadConfig = function (dir, file) { * @return {SecretFile} The secret, or null if the secret could not be read. This shouldn't be logged. */ service.getSecrets = function () { - var secret = null; - - try { - var file = service.config.secretFile && path.resolve(service.config.secretFile); - if (file) { - service.log("Reading secrets from " + file); - secret = JSON5.parse(fs.readFileSync(file)); - } else { - service.logError("The secrets file is not configured"); - } - } catch (e) { - service.logWarn("Unable to read the secrets file " + service.config.secretFile, e); - } + return service.loadJSON(service.config.secretFile, "secrets file"); +}; - return secret ? secret : null; +/** + * Loads the site-config file, from `service.config.siteConfigFile`. + * + * siteConfigFile can be an array, where the first successful loaded file is returned. + * + * @return {Object} The de-serialised site-config file. + */ +service.getSiteConfig = function () { + var files = Array.isArray(service.config.siteConfigFile) + ? service.config.siteConfigFile + : [service.config.siteConfigFile]; + var result = null; + + files.some(function (file) { + result = service.loadJSON(file, "site-config file"); + return result; + }); + return result; }; /** diff --git a/gpii-service/test-secret.json5 b/gpii-service/test-secret.json5 index e89f0b6b9..03adecaae 100644 --- a/gpii-service/test-secret.json5 +++ b/gpii-service/test-secret.json5 @@ -1,6 +1,7 @@ // Secret for testing { "site": "testing.gpii.net", + "siteConfigUrl": "https://raw.githubusercontent.com/GPII/gpii-app/master/siteconfig.json5", "clientCredentials": { "client_id": "pilot-computer", "client_secret": "pilot-computer-secret" diff --git a/gpii-service/tests/processHandling-tests.js b/gpii-service/tests/processHandling-tests.js index d1a90fd21..fc44308f7 100644 --- a/gpii-service/tests/processHandling-tests.js +++ b/gpii-service/tests/processHandling-tests.js @@ -37,6 +37,89 @@ jqUnit.module("GPII Service processHandling tests", { } }); +processHandlingTests.testData.processList = { + allProcesses: { + proc1: {command: "p1"}, + proc2: {command: "p2", disabled: true}, + proc3: {command: "p3", disabled: false}, + proc4: {command: "p4", ipc: "gpii"}, + proc5: {command: "p5", ipc: "other"}, + proc6: {command: "p6"} + }, + tests: [ + { + id: "no config", + gpiiConfig: null, + expect: [ + {key: "proc1", command: "p1"}, + {key: "proc3", command: "p3", disabled: false}, + {key: "proc4", command: "p4", ipc: "gpii"}, + {key: "proc5", command: "p5", ipc: "other"}, + {key: "proc6", command: "p6"} + ] + }, + { + id: "with config, on:on", + siteConfig: { + metricsSwitch: "on:on" + }, + gpiiConfig: { + env: "NODE_ENV", + "on:on": "app.testing.metrics", + "off:off": "app.disable", + "off:on": "app.metrics", + "on:off": "app.testing" + }, + expect: [ + {key: "proc1", command: "p1"}, + {key: "proc3", command: "p3", disabled: false}, + {key: "proc4", command: "p4", ipc: "gpii", env: {"NODE_ENV": "app.testing.metrics"}}, + {key: "proc5", command: "p5", ipc: "other"}, + {key: "proc6", command: "p6"} + ] + }, + { + id: "with config, off:off", + siteConfig: { + metricsSwitch: "off:off" + }, + gpiiConfig: { + env: "NODE_ENV", + "on:on": "app.testing.metrics", + "off:off": "app.disable", + "off:on": "app.metrics", + "on:off": "app.testing" + }, + expect: [ + {key: "proc1", command: "p1"}, + {key: "proc3", command: "p3", disabled: false}, + {key: "proc5", command: "p5", ipc: "other"}, + {key: "proc6", command: "p6"} + ] + }, + { + id: "with config, invalid", + siteConfig: { + metricsSwitch: "invalid" + }, + gpiiConfig: { + env: "NODE_ENV", + "on:on": "app.testing.metrics", + "off:off": "app.disable", + "off:on": "app.metrics", + "on:off": "app.testing" + }, + expect: [ + {key: "proc1", command: "p1"}, + {key: "proc3", command: "p3", disabled: false}, + {key: "proc4", command: "p4", ipc: "gpii"}, + {key: "proc5", command: "p5", ipc: "other"}, + {key: "proc6", command: "p6"} + ] + } + ] +}; + processHandlingTests.testData.startChildProcess = [ // Shouldn't restart if stopChildProcess is used. { @@ -211,6 +294,28 @@ processHandlingTests.waitForMutex = function (mutexName, timeout) { }); }; +// Tests getProcessList +jqUnit.test("testing getProcessList", function () { + + var service = require("../src/service.js"); + var getSiteConfig = service.getSiteConfig; + + try { + processHandlingTests.testData.processList.tests.forEach(function (test) { + service.getSiteConfig = function () { + return test.siteConfig; + }; + service.config.gpiiConfig = test.gpiiConfig; + var result = processHandling.getProcessList(processHandlingTests.testData.processList.allProcesses); + jqUnit.assertDeepEq("getProcessList should return the expected processes (test.id:" + test.id + ")", + test.expect, result); + }); + } finally { + service.getSiteConfig = getSiteConfig; + } + +}); + // Tests getProcessCreationTime jqUnit.test("Test getProcessCreationTime", function () { var value; From 417e743d42ad29a417c0a32ca666354a1c92819e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Hern=C3=A1ndez?= Date: Tue, 3 Dec 2019 22:26:43 +0100 Subject: [PATCH 095/123] GPII-4214.GPII-3572: Updated universal reference --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bab887462..e5f3b49f0 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "dependencies": { "edge-js": "10.3.1", "ffi-napi": "2.4.3", - "gpii-universal": "javihernandez/universal#98913212f87e07f432a9d36e4e6ff63b334a6d1d", + "gpii-universal": "javihernandez/universal#f840342232b452e678b0dfb6b2582dece040b947", "@pokusew/pcsclite": "0.4.18", "ref": "1.3.4", "ref-struct": "1.1.0", From c2ce74cd0eb96e6f63cb2d994f254dc7b0a6e2fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Hern=C3=A1ndez?= Date: Wed, 4 Dec 2019 15:55:02 +0100 Subject: [PATCH 096/123] GPII-4214.GPII-3572: Updated universal reference --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e5f3b49f0..5ab999056 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "dependencies": { "edge-js": "10.3.1", "ffi-napi": "2.4.3", - "gpii-universal": "javihernandez/universal#f840342232b452e678b0dfb6b2582dece040b947", + "gpii-universal": "javihernandez/universal#9b3bf12fef7de7f4d6ac2215fb91de53176892bd", "@pokusew/pcsclite": "0.4.18", "ref": "1.3.4", "ref-struct": "1.1.0", From 6436511647bd4dd70cfaa54e7f09fa09775e0a1d Mon Sep 17 00:00:00 2001 From: ste Date: Wed, 4 Dec 2019 23:35:10 +0000 Subject: [PATCH 097/123] GPII-3852: Applied refactoring made in universal. --- .../test/WindowsMetricsTests.js | 40 +++++++------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js index c6e2c7671..ecac4beec 100644 --- a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js +++ b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js @@ -868,8 +868,10 @@ gpii.tests.metrics.createLogServer = function (port) { gpii.tests.metrics.captureLog = function (that) { - that.logHost = "127.0.0.1"; - that.logPort = gpii.tests.metrics.logPort; + that.logServer = { + host: "127.0.0.1", + port: gpii.tests.metrics.logPort + }; }; gpii.tests.metrics.logPort = 50000 + Math.floor(Math.random() * 5000); @@ -883,17 +885,17 @@ gpii.tests.metrics.logServer = gpii.tests.metrics.createLogServer(gpii.tests.met * @return {String} The path of the log file. */ gpii.tests.metrics.setLogFile = function () { - var previousLogFile = gpii.eventLog.logFilePath; + var previousLogFile = gpii.eventLog.logPath; var logFile = os.tmpdir() + "/gpii-test-metrics-" + Date.now(); teardowns.push(function () { - gpii.eventLog.logFilePath = previousLogFile; + gpii.eventLog.logPath = previousLogFile; try { fs.unlinkSync(logFile); } catch (e) { // Ignored. } }); - gpii.eventLog.logFilePath = logFile; + gpii.eventLog.logPath = logFile; return logFile; }; @@ -1072,9 +1074,7 @@ jqUnit.asyncTest("Testing application metrics", function () { var logFile = gpii.tests.metrics.setLogFile(); var windowsMetrics = gpii.tests.metrics.windowsMetricsWrapper({ - members: { - logFilePath: logFile - } + logDestination: logFile }); windowsMetrics.metrics = windowsMetrics; @@ -1186,9 +1186,7 @@ jqUnit.asyncTest("Testing system configuration notifications", function () { var logFile = gpii.tests.metrics.setLogFile(); var windowsMetrics = gpii.tests.metrics.windowsMetricsWrapper({ - members: { - logFilePath: logFile - } + logDestination: logFile }); var untestedMessages = fluid.arrayToHash(gpii.windows.metrics.settingsMessages); @@ -1227,9 +1225,7 @@ gpii.tests.metrics.recordKeyTimingTests = function (theTests) { var defaultConfig = theTests.defaultConfig; var windowsMetrics = gpii.tests.metrics.windowsMetricsWrapper({ - members: { - logFilePath: logFile - } + logDestination: logFile }); var expectedLines = []; @@ -1266,9 +1262,7 @@ jqUnit.asyncTest("Testing input metrics: inputHook", function () { var logFile = gpii.tests.metrics.setLogFile(); var windowsMetrics = gpii.tests.metrics.windowsMetricsWrapper({ - members: { - logFilePath: logFile - } + logDestination: logFile }); // Get the initial state, so it can be reset (otherwise tests will depend on the previous tests, making it tricky @@ -1376,9 +1370,7 @@ jqUnit.asyncTest("Testing input metrics: inputHook - only logging desired keys", var logFile = gpii.tests.metrics.setLogFile(); var windowsMetrics = gpii.tests.metrics.windowsMetricsWrapper({ - members: { - logFilePath: logFile - } + logDestination: logFile }); // Disable typing sessions @@ -1478,8 +1470,8 @@ jqUnit.asyncTest("Testing input metrics: inactivity", function () { var logFile = gpii.tests.metrics.setLogFile(); var windowsMetrics = gpii.tests.metrics.windowsMetricsWrapper({ + logDestination: logFile, members: { - logFilePath: logFile, eventData: { sessionID: "test-session-id" } @@ -1574,9 +1566,7 @@ jqUnit.asyncTest("Testing version and system info logging", function () { var logFile = gpii.tests.metrics.setLogFile(); var windowsMetrics = gpii.tests.metrics.windowsMetricsWrapper({ - members: { - logFilePath: logFile - } + logDestination: logFile }); windowsMetrics.events.onStartMetrics.fire(); @@ -1619,8 +1609,8 @@ jqUnit.asyncTest("Testing power notifications", function () { var logFile = gpii.tests.metrics.setLogFile(); var windowsMetrics = gpii.tests.metrics.windowsMetricsWrapper({ + logDestination: logFile, members: { - logFilePath: logFile, eventData: { sessionID: "test-session-id" } From f22983a1a71e58dced030ef411f01aa6681086dd Mon Sep 17 00:00:00 2001 From: ste Date: Thu, 5 Dec 2019 00:07:28 +0000 Subject: [PATCH 098/123] GPII-4244: Added null reference check --- gpii-service/src/configUpdater.js | 39 +++++++++++++++++-------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/gpii-service/src/configUpdater.js b/gpii-service/src/configUpdater.js index e1b769cb3..1d17e147d 100644 --- a/gpii-service/src/configUpdater.js +++ b/gpii-service/src/configUpdater.js @@ -242,26 +242,31 @@ configUpdater.updateFile = function (update, lastUpdate) { */ configUpdater.expand = function (unexpanded, sourceObject, alwaysExpand) { var unresolved = false; + var result; // Replace all occurences of "${...}" - var result = unexpanded.replace(/\$\{([^?}]*)(\?([^}]*))?\}/g, function (match, expression, defaultGroup, defaultValue) { - // Resolve the path to a field, deep in the object. - var value = expression.split(".").reduce(function (parent, property) { - return (parent && parent.hasOwnProperty(property)) ? parent[property] : undefined; - }, sourceObject); - - if (value === undefined || (typeof(value) === "object")) { - if (defaultGroup) { - value = defaultValue; - } - if (value === undefined || value === null) { - if (!alwaysExpand) { - unresolved = true; + if (unexpanded === null || unexpanded === undefined) { + result = null; + } else { + result = unexpanded.replace(/\$\{([^?}]*)(\?([^}]*))?\}/g, function (match, expression, defaultGroup, defaultValue) { + // Resolve the path to a field, deep in the object. + var value = expression.split(".").reduce(function (parent, property) { + return (parent && parent.hasOwnProperty(property)) ? parent[property] : undefined; + }, sourceObject); + + if (value === undefined || (typeof (value) === "object")) { + if (defaultGroup) { + value = defaultValue; + } + if (value === undefined || value === null) { + if (!alwaysExpand) { + unresolved = true; + } + value = ""; } - value = ""; } - } - return value; - }); + return value; + }); + } return unresolved ? null : result; }; From ec8fc7bd88e206f66677a9df78aeb09430c98d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Hern=C3=A1ndez?= Date: Thu, 5 Dec 2019 10:38:50 +0100 Subject: [PATCH 099/123] GPII-4214.GPII-3572: Updated universal reference --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5ab999056..15d9037dd 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "dependencies": { "edge-js": "10.3.1", "ffi-napi": "2.4.3", - "gpii-universal": "javihernandez/universal#9b3bf12fef7de7f4d6ac2215fb91de53176892bd", + "gpii-universal": "javihernandez/universal#cbe26785209145d28c2250365bb1157d5398c18e", "@pokusew/pcsclite": "0.4.18", "ref": "1.3.4", "ref-struct": "1.1.0", From ce69daea4efb9862eff7330395057e533d63112e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Thu, 5 Dec 2019 12:30:50 +0100 Subject: [PATCH 100/123] GPII-4208: Removed unnecessary info from comments links --- .../WindowsUtilities/WindowsUtilities.js | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js index 8a4512e6f..de7805ae0 100644 --- a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js +++ b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js @@ -384,11 +384,11 @@ windows.user32 = ffi.Library("user32", { "GetKeyState": [ "short", ["int"] ], - // https://docs.microsoft.com/es-es/windows/desktop/api/winuser/nf-winuser-setdisplayconfig + // https://docs.microsoft.com/windows/desktop/api/winuser/nf-winuser-setdisplayconfig "SetDisplayConfig": [ "int32", ["uint32", "void*", "uint32", "void*", "uint32"] ], - // https://docs.microsoft.com/es-es/windows/desktop/api/winuser/nf-winuser-querydisplayconfig + // https://docs.microsoft.com/windows/desktop/api/winuser/nf-winuser-querydisplayconfig "QueryDisplayConfig": [ "int32", ["uint32", "uint32*", t.PVOID, "uint32*", t.PVOID, "uint32*"] ], @@ -415,7 +415,7 @@ windows.user32 = ffi.Library("user32", { "GetRawInputData": [ t.INT, [t.PVOID, t.UINT, t.PVOID, t.PVOID, t.UINT] ], - // https://docs.microsoft.com/es-es/windows/win32/api/winuser/nf-winuser-getmessagetime + // https://docs.microsoft.com/windows/win32/api/winuser/nf-winuser-getmessagetime "GetMessageTime": [ t.LONG, [] ] @@ -786,7 +786,7 @@ windows.API_constants = { DISPLAYCONFIG_TOPOLOGY_EXTERNAL: 3, DISPLAYCONFIG_TOPOLOGY_FORCE_UINT32: 4, - // https://docs.microsoft.com/es-es/windows/desktop/api/winuser/nf-winuser-setdisplayconfig + // https://docs.microsoft.com/windows/desktop/api/winuser/nf-winuser-setdisplayconfig SDC_TOPOLOGY_INTERNAL: 0x00000001, SDC_TOPOLOGY_CLONE: 0x00000002, SDC_TOPOLOGY_EXTEND: 0x00000004, @@ -804,7 +804,7 @@ windows.API_constants = { SW_SHOWMINIMIZED: 2, SW_SHOWMAXIMIZED: 3, SW_SHOWNOACTIVATE: 4, - // https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawmouse + // https://docs.microsoft.com/windows/win32/api/winuser/ns-winuser-rawmouse RI_MOUSE_LEFT_BUTTON_UP: 0x0002, RI_MOUSE_MIDDLE_BUTTON_UP: 0x0020, RI_MOUSE_RIGHT_BUTTON_UP: 0x0008, @@ -923,7 +923,7 @@ windows.StickyKeys = new Struct([ ]); windows.StickyKeysPointer = ref.refType(windows.StickyKeys); -// https://docs.microsoft.com/es-es/windows/desktop/api/winuser/ns-winuser-tagtogglekeys +// https://docs.microsoft.com/windows/desktop/api/winuser/ns-winuser-tagtogglekeys windows.ToggleKeys = new Struct({ "cbSize": "uint32", "dwFlags": "int32" @@ -1069,7 +1069,7 @@ windows.KEY_INPUT = new Struct([ [t.ULONG_PTR, "dwExtraInfo"] ]); -// https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawinputheader +// https://docs.microsoft.com/windows/win32/api/winuser/ns-winuser-rawinputheader windows.RAWINPUTHEADER = new Struct([ [t.DWORD, "dwType"], [t.DWORD, "dwSize"], @@ -1077,7 +1077,7 @@ windows.RAWINPUTHEADER = new Struct([ [t.HANDLE, "wParam"] ]); -// https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawmouse +// https://docs.microsoft.com/windows/win32/api/winuser/ns-winuser-rawmouse windows.RAWMOUSE = new Struct([ [t.WORD, "usFlags"], [t.WORD, "padding"], @@ -1089,7 +1089,7 @@ windows.RAWMOUSE = new Struct([ [t.ULONG, "ulExtraInformation"] ]); -// https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawkeyboard +// https://docs.microsoft.com/windows/win32/api/winuser/ns-winuser-rawkeyboard windows.RAWKEYBOARD = new Struct([ [t.WORD, "MakeCode"], [t.WORD, "Flags"], @@ -1099,28 +1099,28 @@ windows.RAWKEYBOARD = new Struct([ [t.ULONG, "ExtraInformation"] ]); -// https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawhid +// https://docs.microsoft.com/windows/win32/api/winuser/ns-winuser-rawhid windows.RAWHID = new Struct([ [t.DWORD, "dwSizeHid"], [t.DWORD, "dwCount"], [arrayType("char", 1), "bRawData"] ]); -// https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawinput?redirectedfrom=MSDN +// https://docs.microsoft.com/windows/win32/api/winuser/ns-winuser-rawinput // Representes a case of the RAWINPUT structure in which the union holds a RAWMOUSE structure. windows.RAWINPUTMOUSE = new Struct([ [windows.RAWINPUTHEADER, "header"], [windows.RAWMOUSE, "mouse"] ]); -// https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawinput?redirectedfrom=MSDN +// https://docs.microsoft.com/windows/win32/api/winuser/ns-winuser-rawinput // Representes a case of the RAWINPUT structure in which the union holds a RAWKEYBOARD structure. windows.RAWINPUTKEYBOARD = new Struct([ [windows.RAWINPUTHEADER, "header"], [windows.RAWKEYBOARD, "keyboard"] ]); -// https://docs.microsoft.com/es-es/windows/win32/api/winuser/ns-winuser-rawinput?redirectedfrom=MSDN +// https://docs.microsoft.com/windows/win32/api/winuser/ns-winuser-rawinput // Representes a case of the RAWINPUT structure in which the union holds a RAWHID structure. windows.RAWINPUTHID = new Struct([ [windows.RAWINPUTHEADER, "header"], @@ -1184,7 +1184,7 @@ windows.flagConstants = { "MKF_RIGHTBUTTONSEL": 0x20000000, // TOGGLEKEYS flags - // https://docs.microsoft.com/es-es/windows/desktop/api/winuser/ns-winuser-tagtogglekeys + // https://docs.microsoft.com/windows/desktop/api/winuser/ns-winuser-tagtogglekeys "TKF_AVAILABLE": 0x00000002, "TKF_CONFIRMHOTKEY": 0x00000008, "TKF_HOTKEYACTIVE": 0x00000004, @@ -1193,7 +1193,7 @@ windows.flagConstants = { "TKF_TOGGLEKEYSON": 0x00000001, // FORMATMESSAGE flags - // https://docs.microsoft.com/es-es/windows/desktop/Debug/retrieving-the-last-error-code + // https://docs.microsoft.com/windows/desktop/Debug/retrieving-the-last-error-code "FORMAT_MESSAGE_ALLOCATE_BUFFER": 0x00000100, "FORMAT_MESSAGE_FROM_SYSTEM": 0x00001000, "FORMAT_MESSAGE_IGNORE_INSERTS": 0x00000200, From fd549c56ad1fcbea65632c5a93d087dc3ceb7076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Thu, 5 Dec 2019 12:36:25 +0100 Subject: [PATCH 101/123] GPII-4208: Changed check in favor of simpler comparison against zero --- gpii/node_modules/windowsMetrics/src/windowsMetrics.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index 3ff2f44ed..92db58935 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -19,8 +19,6 @@ "use strict"; -// var ffi = require("ffi-napi"); - var ref = require("ref"); var fluid = require("gpii-universal"), path = require("path"); @@ -329,7 +327,7 @@ windows.metrics.getRawInputData = function (lParam) { windows.RAWINPUTHEADER.size ); - if (res >= 0) { + if (res === 0) { var rawInputBuf = Buffer.alloc(dataSz.deref()); res = windows.user32.GetRawInputData( lParam, From dc26d6a68c606c57eae335d4fb70173f4e81c746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Thu, 5 Dec 2019 18:30:44 +0100 Subject: [PATCH 102/123] GPII-4208: Improved documentation of the returned value and used helper function 'win32error' for error construction --- gpii/node_modules/windowsMetrics/src/windowsMetrics.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index 92db58935..30864c086 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -312,8 +312,9 @@ windows.metrics.logAppActivate = function (that, windowInfo) { * * @param {Buffer} lParam A handle to a RAWINPUT structure received from the * system within a WM_INPUT message. - * @return {Object} Either a RAWINPUTMOUSE or a RAWINPUTKEYBOARD depending on the - * contents of the lParam handle. + * @return {Promise} Promise holding either a RAWINPUTMOUSE or a RAWINPUTKEYBOARD, depending on the + * contents of the lParam handle. In case of error the promise is rejected with and error of the + * following kind: {isError:true, returnCode: -1, errorCode: GetLastError()}. */ windows.metrics.getRawInputData = function (lParam) { var promise = fluid.promise(); @@ -346,7 +347,7 @@ windows.metrics.getRawInputData = function (lParam) { } } else { var errCode = windows.kernel32.GetLastError(); - promise.reject({ code: errCode, message: "Failed to get GetRawInputData."}); + promise.reject(windows.win32error("Failed to get GetRawInputData.", -1, errCode)); } return promise; @@ -422,7 +423,6 @@ windows.metrics.windowMessage = function (that, hwnd, msg, wParam, lParam) { } } }, function (err) { - // TODO: Check if this is the desired behavior. fluid.log(err); }); break; From fb2820e8cfa780ed15e03ffb160aa6dd3de81a8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Fri, 6 Dec 2019 12:36:49 +0100 Subject: [PATCH 103/123] GPII-4208: Fixed constants location, bitmask based logic message handling and tests --- .../WindowsUtilities/WindowsUtilities.js | 58 ++--- .../windowsMetrics/src/windowsMetrics.js | 213 ++++++++++++------ .../test/WindowsMetricsTests.js | 56 +++-- 3 files changed, 201 insertions(+), 126 deletions(-) diff --git a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js index de7805ae0..68f67ccd7 100644 --- a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js +++ b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js @@ -221,18 +221,6 @@ windows.user32 = ffi.Library("user32", { "GetForegroundWindow": [ t.HANDLE, [] ], - // https://msdn.microsoft.com/library/ms644990 - "SetWindowsHookExW": [ - t.HANDLE, [ t.INT, "void*", t.HANDLE, t.DWORD ] - ], - // https://msdn.microsoft.com/library/ms644993 - "UnhookWindowsHookEx": [ - t.BOOL, [ t.HANDLE ] - ], - // https://msdn.microsoft.com/library/ms644974 - "CallNextHookEx": [ - t.HANDLE, [ t.HANDLE, "int", t.HANDLE, t.PVOID ] - ], // https://msdn.microsoft.com/library/ms646306 "MapVirtualKeyW": [ t.UINT, [ t.UINT, t.UINT ] @@ -808,7 +796,28 @@ windows.API_constants = { RI_MOUSE_LEFT_BUTTON_UP: 0x0002, RI_MOUSE_MIDDLE_BUTTON_UP: 0x0020, RI_MOUSE_RIGHT_BUTTON_UP: 0x0008, - RI_MOUSE_WHEEL: 0x0400 + RI_MOUSE_WHEEL: 0x0400, + + // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-rawinputdevice + RIDEV_APPKEYS: 0x00000400, + RIDEV_CAPTUREMOUSE: 0x00000200, + RIDEV_DEVNOTIFY: 0x00002000, + RIDEV_EXCLUDE: 0x00000010, + RIDEV_EXINPUTSINK: 0x00001000, + RIDEV_INPUTSINK: 0x00000100, + RIDEV_NOHOTKEYS: 0x00000200, + RIDEV_NOLEGACY: 0x00000030, + RIDEV_PAGEONLY: 0x00000020, + RIDEV_REMOVE: 0x00000001, + + // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getrawinputdata + RID_HEADER: 0x10000005, + RID_INPUT: 0x10000003, + + // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-rid_device_info + RIM_TYPEHID: 2, + RIM_TYPEKEYBOARD: 1, + RIM_TYPEMOUSE: 0 }; fluid.each(windows.API_constants.returnCodesLookup, function (value, key) { @@ -1200,28 +1209,7 @@ windows.flagConstants = { // LANGUAGE Identifiers flags "LANG_NEUTRAL": 0x00, - "SUBLANG_DEFAULT": 0x01, - - // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-rawinputdevice - "RIDEV_APPKEYS": 0x00000400, - "RIDEV_CAPTUREMOUSE": 0x00000200, - "RIDEV_DEVNOTIFY": 0x00002000, - "RIDEV_EXCLUDE": 0x00000010, - "RIDEV_EXINPUTSINK": 0x00001000, - "RIDEV_INPUTSINK": 0x00000100, - "RIDEV_NOHOTKEYS": 0x00000200, - "RIDEV_NOLEGACY": 0x00000030, - "RIDEV_PAGEONLY": 0x00000020, - "RIDEV_REMOVE": 0x00000001, - - // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getrawinputdata - "RID_HEADER": 0x10000005, - "RID_INPUT": 0x10000003, - - // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-rid_device_info - "RIM_TYPEHID": 2, - "RIM_TYPEKEYBOARD": 1, - "RIM_TYPEMOUSE": 0 + "SUBLANG_DEFAULT": 0x01 // TODO Define additional flags used across various structures here. }; diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index 30864c086..f25dfd996 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -145,9 +145,7 @@ fluid.defaults("gpii.windowsMetrics", { // Mouse position lastPos: null, distance: 0, - lastInputTime: 0, - // Marks the end of wheel movement - wheelMovement: 0 + lastInputTime: 0 } }, keyboardHookHandle: null, @@ -322,7 +320,7 @@ windows.metrics.getRawInputData = function (lParam) { var dataSz = ref.alloc(windows.types.UINT, 0); var res = windows.user32.GetRawInputData( lParam, - windows.flagConstants.RID_INPUT, + windows.API_constants.RID_INPUT, ref.NULL, dataSz, windows.RAWINPUTHEADER.size @@ -332,14 +330,14 @@ windows.metrics.getRawInputData = function (lParam) { var rawInputBuf = Buffer.alloc(dataSz.deref()); res = windows.user32.GetRawInputData( lParam, - windows.flagConstants.RID_INPUT, + windows.API_constants.RID_INPUT, rawInputBuf, dataSz, windows.RAWINPUTHEADER.size ); var rawInput = ref.get(rawInputBuf, 0, windows.RAWINPUTKEYBOARD); - if (rawInput.header.dwType === windows.flagConstants.RIM_TYPEMOUSE) { + if (rawInput.header.dwType === windows.API_constants.RIM_TYPEMOUSE) { var rawMouse = ref.get(rawInputBuf, 0, windows.RAWINPUTMOUSE); promise.resolve(rawMouse); } else { @@ -354,63 +352,92 @@ windows.metrics.getRawInputData = function (lParam) { }; /** - * Called when an event has been received by the message window. + * Function that handles the WM_INPUT message received with event information + * from the registered devices. * - * @param {Component} that The gpii.windowsMetrics component. - * @param {Number} hwnd The window handle of the message window. - * @param {Number|String} msg The message identifier. - * @param {Number} wParam Message specific data. - * @param {Object} lParam Additional message specific data (passed, but not used). + * @param {Component} that The gpii.windowsMetrics instance. + * @param {Object} lParam Message specific data. */ -windows.metrics.windowMessage = function (that, hwnd, msg, wParam, lParam) { - var lParamNumber = (lParam && lParam.address) ? lParam.address() : lParam || 0; - switch (msg) { - case windows.API_constants.WM_INPUT: - windows.metrics.userInput(that); +windows.metrics.handleWMINPUT = function (that, lParam) { + windows.metrics.userInput(that); - var pRawInput = windows.metrics.getRawInputData(lParam); + var pRawInput = windows.metrics.getRawInputData(lParam); - pRawInput.then(function (rawInput) { - if (rawInput.header.dwType === windows.flagConstants.RIM_TYPEKEYBOARD) { - // Ignore injected keys - if (rawInput.header.hDevice !== 0 && rawInput.keyboard.Message === windows.API_constants.WM_KEYUP) { - var keyValue = windows.user32.MapVirtualKeyW(rawInput.keyboard.VKey, windows.API_constants.MAPVK_VK_TO_CHAR); - var specialKey = windows.metrics.specialKeys[rawInput.keyboard.VKey]; + pRawInput.then(function (rawInput) { + if (rawInput.header.dwType === windows.API_constants.RIM_TYPEKEYBOARD) { + // Ignore injected keys + if (rawInput.header.hDevice !== 0 && rawInput.keyboard.Message === windows.API_constants.WM_KEYUP) { + var keyValue = windows.user32.MapVirtualKeyW(rawInput.keyboard.VKey, windows.API_constants.MAPVK_VK_TO_CHAR); + var specialKey = windows.metrics.specialKeys[rawInput.keyboard.VKey]; - if (specialKey || keyValue) { - var timestamp = windows.user32.GetMessageTime(); - windows.metrics.recordKeyTiming(that, timestamp, specialKey, String.fromCharCode(keyValue)); - } + if (specialKey || keyValue) { + var timestamp = windows.user32.GetMessageTime(); + windows.metrics.recordKeyTiming(that, timestamp, specialKey, String.fromCharCode(keyValue)); } - } else if (rawInput.header.dwType === windows.flagConstants.RIM_TYPEMOUSE) { - var relevantMouseEvent = - rawInput.mouse.usButtonFlags === windows.API_constants.RI_MOUSE_WHEEL || - rawInput.mouse.usButtonFlags === windows.API_constants.RI_MOUSE_LEFT_BUTTON_UP || - rawInput.mouse.usButtonFlags === windows.API_constants.RI_MOUSE_RIGHT_BUTTON_UP || - // Mouse just moved - rawInput.mouse.usButtonFlags === 0; - - if (relevantMouseEvent && rawInput.header.hDevice !== 0) { - var wheelDistance = rawInput.mouse.usButtonData; - var wheelDirection = 0; - var button = 0; - if (rawInput.mouse.usButtonFlags === windows.API_constants.RI_MOUSE_WHEEL) { - // Unsigned to signed - if (wheelDistance >= 0x8000) { - wheelDistance -= 0x10000; + } + } else if (rawInput.header.dwType === windows.API_constants.RIM_TYPEMOUSE) { + var relevantMouseEvent = + // Is a relevant event + ( ( rawInput.mouse.usButtonFlags & ( + windows.API_constants.RI_MOUSE_WHEEL | + windows.API_constants.RI_MOUSE_LEFT_BUTTON_UP | + windows.API_constants.RI_MOUSE_RIGHT_BUTTON_UP + ) + ) !== 0) || + // Mouse just moved + rawInput.mouse.usButtonFlags === 0; + + if (relevantMouseEvent && rawInput.header.hDevice !== 0) { + var wheelDistance = rawInput.mouse.usButtonData; + var wheelDirection = 0; + var button = 0; + + if ((rawInput.mouse.usButtonFlags & windows.API_constants.RI_MOUSE_WHEEL) !== 0) { + // Unsigned to signed + if (wheelDistance >= 0x8000) { + wheelDistance -= 0x10000; + } + if (Math.sign(wheelDistance) === 1 || Math.sign(wheelDistance) === 0) { + wheelDirection = 1; + } else { + wheelDirection = -1; + } + button = "W"; + + windows.metrics.recordMouseEvent( + that, + button, + { + x: rawInput.mouse.lLastX, + y: rawInput.mouse.lLastY, + wheel: wheelDirection } - if (Math.sign(wheelDistance) === 1 || Math.sign(wheelDistance) === 0) { - wheelDirection = 1; - } else { - wheelDirection = -1; + ); + } else if ((rawInput.mouse.usButtonFlags & windows.API_constants.RI_MOUSE_LEFT_BUTTON_UP) !== 0) { + button = 1; + + windows.metrics.recordMouseEvent( + that, + button, + { + x: rawInput.mouse.lLastX, + y: rawInput.mouse.lLastY, + wheel: wheelDirection } - button = "W"; - } else if (rawInput.mouse.usButtonFlags === windows.API_constants.RI_MOUSE_LEFT_BUTTON_UP) { - button = 1; - } else if (rawInput.mouse.usButtonFlags === windows.API_constants.RI_MOUSE_RIGHT_BUTTON_UP) { - button = 2; - } + ); + } else if ((rawInput.mouse.usButtonFlags & windows.API_constants.RI_MOUSE_RIGHT_BUTTON_UP) !== 0) { + button = 2; + windows.metrics.recordMouseEvent( + that, + button, + { + x: rawInput.mouse.lLastX, + y: rawInput.mouse.lLastY, + wheel: wheelDirection + } + ); + } else { windows.metrics.recordMouseEvent( that, button, @@ -422,10 +449,29 @@ windows.metrics.windowMessage = function (that, hwnd, msg, wParam, lParam) { ); } } - }, function (err) { - fluid.log(err); - }); + } + }, function (err) { + fluid.log(err); + }); +}; + +/** + * Called when an event has been received by the message window. + * + * @param {Component} that The gpii.windowsMetrics component. + * @param {Number} hwnd The window handle of the message window. + * @param {Number|String} msg The message identifier. + * @param {Number} wParam Message specific data. + * @param {Object} lParam Additional message specific data (passed, but not used). + */ +windows.metrics.windowMessage = function (that, hwnd, msg, wParam, lParam) { + var lParamNumber = (lParam && lParam.address) ? lParam.address() : lParam || 0; + switch (msg) { + case windows.API_constants.WM_INPUT: + // Handle the WM_INPUT message holding devices input information + windows.metrics.handleWMINPUT(that, lParam); break; + case windows.API_constants.WM_SHELLHOOK: // Run the code in the next tick so this function can return soon, as it's a window procedure. process.nextTick(windows.metrics.shellMessage, that, wParam, lParamNumber); @@ -750,16 +796,13 @@ windows.metrics.genericisePath = function (rawPath, env) { /** * Starts the input metrics. * - * This sets up low-level keyboard and mouse hooks (WH_KEYBOARD_LL, WH_MOUSE_LL) that makes the system invoke a - * call-back whenever a key is pressed or released, or the mouse is moved/clicked. - * Hooks overview: https://msdn.microsoft.com/library/ms644959 + * This register devices that supply RAW input data to the process using the raw input Windows API. This makes + * the system send a WM_INPUT message to this process each time the mouse is moved/clicked or a keyboard key is + * pressed. API overview: https://docs.microsoft.com/en-us/windows/win32/inputdev/raw-input * * This comes with the following limitations: * - The process needs a window-message loop. Fortunately, Electron has one so this means it will only work if running * via gpii-app. - * - It can't see the keys that are destined to a window owned by a process running as Administrator (and rightly so). - * - The hook call-back has to return in a timely manner - not only because it would cause key presses to lag, but also - * Windows will silently remove the hook if it times out. The time is unspecified (but it can be set in the registry). * - Anti-virus software may question this. * * The environment variable GPII_NO_INPUT_METRICS can be set to disable input metrics. @@ -779,8 +822,8 @@ windows.metrics.startInputMetrics = function (that) { var keyboard = new windows.RAWINPUTDEVICE(); keyboard.dwFlags = - windows.flagConstants.RIDEV_NOLEGACY | - windows.flagConstants.RIDEV_INPUTSINK; + windows.API_constants.RIDEV_NOLEGACY | + windows.API_constants.RIDEV_INPUTSINK; keyboard.usUsagePage = 1; // Keyboard code keyboard.usUsage = 6; @@ -789,8 +832,8 @@ windows.metrics.startInputMetrics = function (that) { var mouse = new windows.RAWINPUTDEVICE(); mouse.dwFlags = - windows.flagConstants.RIDEV_NOLEGACY | - windows.flagConstants.RIDEV_INPUTSINK; + windows.API_constants.RIDEV_NOLEGACY | + windows.API_constants.RIDEV_INPUTSINK; mouse.usUsagePage = 1; // Mouse code mouse.usUsage = 2; @@ -823,13 +866,35 @@ windows.metrics.stopInputMetrics = function (that) { } } - // remove the hooks - if (that.keyboardHookHandle) { - windows.user32.UnhookWindowsHookEx(that.keyboardHookHandle); - } - if (that.mouseHookHandle) { - windows.user32.UnhookWindowsHookEx(that.mouseHookHandle); - } + // Unregister RAWINPUT devices + var keyboard = new windows.RAWINPUTDEVICE(); + + // Flags for removing the keyboard device + keyboard.dwFlags = + windows.API_constants.RIDEV_NOLEGACY | + windows.API_constants.RIDEV_INPUTSINK | + windows.API_constants.RIDEV_REMOVE; + keyboard.usUsagePage = 1; + keyboard.usUsage = 6; + keyboard.hwndTarget = 0; + + var mouse = new windows.RAWINPUTDEVICE(); + + // Flags for removing the mouse device + mouse.dwFlags = + windows.API_constants.RIDEV_NOLEGACY | + windows.API_constants.RIDEV_INPUTSINK | + windows.API_constants.RIDEV_REMOVE; + mouse.usUsagePage = 1; + mouse.usUsage = 2; + mouse.hwndTarget = 0; + + var devices = Buffer.alloc(windows.RAWINPUTDEVICE.size * 2); + mouse.ref().copy(devices, 0, 0, windows.RAWINPUTDEVICE.size); + keyboard.ref().copy(devices, windows.RAWINPUTDEVICE.size, 0, windows.RAWINPUTDEVICE.size); + + windows.user32.RegisterRawInputDevices(devices, 2, windows.RAWINPUTDEVICE.size); + that.keyboardHookHandle = null; that.mouseHookHandle = null; windows.metrics.keyboardHookCallback = null; diff --git a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js index 542265460..6187d5cb0 100644 --- a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js +++ b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js @@ -357,8 +357,8 @@ gpii.tests.metrics.typingSessionTests = fluid.freezeRecursive({ ] }); -// Tests for inputHook. -gpii.tests.metrics.inputHookTests = fluid.freezeRecursive([ +// Tests for WM_INPUT messages. +gpii.tests.metrics.WMInputTests = fluid.freezeRecursive([ { // Invalid nCode. input: { nCode: -1, @@ -717,7 +717,7 @@ gpii.tests.metrics.getMessageTime = function () { */ gpii.tests.metrics.getRawInputData = function (hRawInput, uiCommand, pData, pcbSize, cbSizeHeader) { var invalidParams = - uiCommand !== gpii.windows.flagConstants.RID_INPUT || + uiCommand !== gpii.windows.API_constants.RID_INPUT || cbSizeHeader !== gpii.windows.RAWINPUTHEADER.size; if (invalidParams) { @@ -770,7 +770,7 @@ gpii.tests.metrics.convertLParam = function (input) { lParamBuf = Buffer.alloc(gpii.windows.RAWINPUTMOUSE.size); var rawMouse = new gpii.windows.RAWINPUTMOUSE(); - rawMouse.header.dwType = gpii.windows.flagConstants.RIM_TYPEMOUSE; + rawMouse.header.dwType = gpii.windows.API_constants.RIM_TYPEMOUSE; rawMouse.header.dwSize = gpii.windows.RAWINPUTKEYBOARD.size; rawMouse.header.hDevice = 1; @@ -791,7 +791,7 @@ gpii.tests.metrics.convertLParam = function (input) { lParamBuf = Buffer.alloc(gpii.windows.RAWINPUTKEYBOARD.size); var rawKeyboard = new gpii.windows.RAWINPUTKEYBOARD(); - rawKeyboard.header.dwType = gpii.windows.flagConstants.RIM_TYPEKEYBOARD; + rawKeyboard.header.dwType = gpii.windows.API_constants.RIM_TYPEKEYBOARD; rawKeyboard.header.dwSize = gpii.windows.RAWINPUTKEYBOARD.size; rawKeyboard.header.hDevice = 1; rawKeyboard.header.wParam = 0; @@ -810,6 +810,33 @@ gpii.tests.metrics.convertLParam = function (input) { return lParamBuf; }; +/** + * Backup of the original 'handleWMINPUT'. + */ +gpii.tests.metrics.handleWMINPUT = gpii.windows.metrics.handleWMINPUT; + +/** + * Wrapper function that mocks the required system calls for being to properly + * test 'windows.metrics.handleWMINPUT' + * + * @param {Component} that The gpii.windowsMetrics instance. + * @param {Object} lParam Message specific data. + */ +gpii.tests.metrics.handleWMINPUTMock = function (that, lParam) { + // Mock the native RAW API function + var backup = gpii.windows.user32.GetRawInputData; + var msgBackup = gpii.windows.user32.GetMessageTime; + + gpii.windows.user32.GetRawInputData = gpii.tests.metrics.getRawInputData; + gpii.windows.user32.GetMessageTime = gpii.tests.metrics.getMessageTime; + + gpii.tests.metrics.handleWMINPUT(that, lParam); + + // Restore the original RAW API function + gpii.windows.user32.GetRawInputData = backup; + gpii.windows.user32.GetMessageTime = msgBackup; +}; + /** * Start a log server, to capture everything that's being logged in this test module. The logs will then be looked at * in the final test. @@ -1220,7 +1247,7 @@ jqUnit.asyncTest("Testing keyboard metrics: recordKeyTiming (typing sessions)", }); // This also tests recordMouseEvent (indirectly) -jqUnit.asyncTest("Testing input metrics: inputHook", function () { +jqUnit.asyncTest("Testing input metrics: windowMessage WM_INPUT", function () { jqUnit.expect(1); var logFile = gpii.tests.metrics.setLogFile(); @@ -1240,7 +1267,7 @@ jqUnit.asyncTest("Testing input metrics: inputHook", function () { windowsMetrics.config.input.maxRecords = 1; windowsMetrics.startInputMetrics(); - var testData = gpii.tests.metrics.inputHookTests; + var testData = gpii.tests.metrics.WMInputTests; var expectedLines = []; var currentTest = null; @@ -1290,12 +1317,8 @@ jqUnit.asyncTest("Testing input metrics: inputHook", function () { return expectedModifiers; }; - // Mock the native RAW API function - var backup = gpii.windows.user32.GetRawInputData; - var msgBackup = gpii.windows.user32.GetMessageTime; - - gpii.windows.user32.GetRawInputData = gpii.tests.metrics.getRawInputData; - gpii.windows.user32.GetMessageTime = gpii.tests.metrics.getMessageTime; + // Mock the 'handleWMINPUT' function + gpii.windows.metrics.handleWMINPUT = gpii.tests.metrics.handleWMINPUTMock; // Run all of the input tests, with different combinations of modifier keys pressed. for (var pass = 0; pass < Math.pow(2, modifiers.length); pass++) { @@ -1312,14 +1335,13 @@ jqUnit.asyncTest("Testing input metrics: inputHook", function () { testInput(expectedModifiers); } - // Restore the original RAW API function - gpii.windows.user32.GetRawInputData = backup; - gpii.windows.user32.GetMessageTime = msgBackup; - // The events are put in the log in the next tick (to allow the hook handler to return quickly). setImmediate(function () { windowsMetrics.stopInputMetrics(); gpii.tests.metrics.completeLogTest(logFile, expectedLines); + + // Recover the original handleWMINPUT function + gpii.windows.metrics.handleWMINPUT = gpii.tests.metrics.handleWMINPUT; }); }); From ed71fa534fdc22dadce1f85f8fa27a24303dd986 Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 6 Dec 2019 17:08:05 +0000 Subject: [PATCH 104/123] GPII-3853: Correctly identifying UWP/modern app windows --- gpii/node_modules/windowsMetrics/src/windowsMetrics.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index f2ceea542..4b03867a4 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -583,13 +583,13 @@ windows.metrics.getWindowInfo = function (hwnd) { } // For UWP apps, the main window doesn't belong to the real process; the application window is a child. - var appClass = "Windows.UI.Core.CoreWindow"; - if (windowInfo.className !== appClass && windowInfo.exe && windowInfo.exe.toLowerCase().endsWith("applicationframehost.exe")) { + var isAppFrame = windowInfo.className === "ApplicationFrameWindow"; + if (isAppFrame && windowInfo.exe && windowInfo.exe.toLowerCase().endsWith("applicationframehost.exe")) { classBuffer = Buffer.alloc(0xff); var child = windows.enumerateWindows(hwnd, function (hwndChild) { if (windows.user32.GetClassNameW(hwndChild, classBuffer, classBuffer.length)) { var cls = windows.stringFromWideChar(classBuffer); - return cls === appClass ? hwndChild : undefined; + return cls === "Windows.UI.Core.CoreWindow" ? hwndChild : undefined; } }); if (child) { From c98594225292998ec017c7a4c9cb5cbe3174d784 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Hern=C3=A1ndez?= Date: Thu, 19 Dec 2019 20:10:20 +0100 Subject: [PATCH 105/123] GPII-4214.GPII-3572: Updated universal reference --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f60e78cb4..48710dd69 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "dependencies": { "edge-js": "10.3.1", "ffi-napi": "2.4.3", - "gpii-universal": "javihernandez/universal#cbe26785209145d28c2250365bb1157d5398c18e", + "gpii-universal": "javihernandez/universal#4231d15715a0df7540abb99a348a7f3b1aa06057", "@pokusew/pcsclite": "0.4.18", "ref": "1.3.4", "ref-struct": "1.1.0", From 7416dc69ff4feb8be7811029d8b1b90ed3dc44ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Mon, 23 Dec 2019 13:35:44 +0100 Subject: [PATCH 106/123] GPII-4208: Simplified check for checking number sign --- gpii/node_modules/windowsMetrics/src/windowsMetrics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index f25dfd996..d980c1e78 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -397,7 +397,7 @@ windows.metrics.handleWMINPUT = function (that, lParam) { if (wheelDistance >= 0x8000) { wheelDistance -= 0x10000; } - if (Math.sign(wheelDistance) === 1 || Math.sign(wheelDistance) === 0) { + if (wheelDistance >= 0) { wheelDirection = 1; } else { wheelDirection = -1; From 3b1c7ffb63487d36405f4d94a2b7b1a5f0e3c4b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Jaramago=20Fern=C3=A1ndez?= Date: Mon, 23 Dec 2019 13:36:41 +0100 Subject: [PATCH 107/123] GPII-4208: Extracted common block from if-else logic --- .../windowsMetrics/src/windowsMetrics.js | 50 ++++--------------- 1 file changed, 10 insertions(+), 40 deletions(-) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index d980c1e78..4b228e777 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -403,51 +403,21 @@ windows.metrics.handleWMINPUT = function (that, lParam) { wheelDirection = -1; } button = "W"; - - windows.metrics.recordMouseEvent( - that, - button, - { - x: rawInput.mouse.lLastX, - y: rawInput.mouse.lLastY, - wheel: wheelDirection - } - ); } else if ((rawInput.mouse.usButtonFlags & windows.API_constants.RI_MOUSE_LEFT_BUTTON_UP) !== 0) { button = 1; - - windows.metrics.recordMouseEvent( - that, - button, - { - x: rawInput.mouse.lLastX, - y: rawInput.mouse.lLastY, - wheel: wheelDirection - } - ); } else if ((rawInput.mouse.usButtonFlags & windows.API_constants.RI_MOUSE_RIGHT_BUTTON_UP) !== 0) { button = 2; - - windows.metrics.recordMouseEvent( - that, - button, - { - x: rawInput.mouse.lLastX, - y: rawInput.mouse.lLastY, - wheel: wheelDirection - } - ); - } else { - windows.metrics.recordMouseEvent( - that, - button, - { - x: rawInput.mouse.lLastX, - y: rawInput.mouse.lLastY, - wheel: wheelDirection - } - ); } + + windows.metrics.recordMouseEvent( + that, + button, + { + x: rawInput.mouse.lLastX, + y: rawInput.mouse.lLastY, + wheel: wheelDirection + } + ); } } }, function (err) { From 5cb8e6475a372d4bfa2b81fe91ff476558cf98a7 Mon Sep 17 00:00:00 2001 From: ste Date: Thu, 9 Jan 2020 22:37:18 +0000 Subject: [PATCH 108/123] GPII-4208: Removed RIDEV_NOLEGACY, which disabled input on the QS. --- gpii/node_modules/windowsMetrics/src/windowsMetrics.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index 9b48308a3..d97ee5a66 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -825,9 +825,7 @@ windows.metrics.startInputMetrics = function (that) { var messageWindow = that.getMessageWindow(); var keyboard = new windows.RAWINPUTDEVICE(); - keyboard.dwFlags = - windows.API_constants.RIDEV_NOLEGACY | - windows.API_constants.RIDEV_INPUTSINK; + keyboard.dwFlags = windows.API_constants.RIDEV_INPUTSINK; keyboard.usUsagePage = 1; // Keyboard code keyboard.usUsage = 6; @@ -835,9 +833,7 @@ windows.metrics.startInputMetrics = function (that) { var mouse = new windows.RAWINPUTDEVICE(); - mouse.dwFlags = - windows.API_constants.RIDEV_NOLEGACY | - windows.API_constants.RIDEV_INPUTSINK; + mouse.dwFlags = windows.API_constants.RIDEV_INPUTSINK; mouse.usUsagePage = 1; // Mouse code mouse.usUsage = 2; @@ -875,7 +871,6 @@ windows.metrics.stopInputMetrics = function (that) { // Flags for removing the keyboard device keyboard.dwFlags = - windows.API_constants.RIDEV_NOLEGACY | windows.API_constants.RIDEV_INPUTSINK | windows.API_constants.RIDEV_REMOVE; keyboard.usUsagePage = 1; @@ -886,7 +881,6 @@ windows.metrics.stopInputMetrics = function (that) { // Flags for removing the mouse device mouse.dwFlags = - windows.API_constants.RIDEV_NOLEGACY | windows.API_constants.RIDEV_INPUTSINK | windows.API_constants.RIDEV_REMOVE; mouse.usUsagePage = 1; From dcedeb6ea85b161293763443b261658a9d0a5565 Mon Sep 17 00:00:00 2001 From: ste Date: Thu, 9 Jan 2020 22:44:06 +0000 Subject: [PATCH 109/123] GPII-4208: Fixed bad merge. --- gpii/node_modules/windowsMetrics/src/windowsMetrics.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index d97ee5a66..25d009921 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -1020,7 +1020,7 @@ windows.metrics.recordMouseEvent = function (that, button, pos) { var state = that.state.input; if (state.lastPos) { - state.distance += Math.sqrt(Math.pow(state.lastPos.x - pos.x, 2) + Math.pow(state.lastPos.y - pos.y, 2)); + state.distance += Math.sqrt(Math.pow(pos.x, 2) + Math.pow(pos.y, 2)); // There have been some very large mouse distances captured (billions of pixels). The cause is unknown, so let's // just ignore anything that's larger than expected [GPII-3878]. if (state.distance > 0xffff) { From b8c43a3472b04b562fdcaa089750f6979bc40bbf Mon Sep 17 00:00:00 2001 From: ste Date: Thu, 9 Jan 2020 22:50:41 +0000 Subject: [PATCH 110/123] GPII-4208: Removed redundant inputHook --- .../windowsMetrics/src/windowsMetrics.js | 59 ------------------- .../test/WindowsMetricsTests.js | 13 ++-- 2 files changed, 5 insertions(+), 67 deletions(-) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index 25d009921..dfcaebd9d 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -1112,62 +1112,3 @@ windows.metrics.userInactive = function (that, inactiveEvent) { inactiveEvent.fire(); } }; - -/** - * The keyboard hook callback. Called when a key is pressed or released. - * https://msdn.microsoft.com/library/ms644985 - * - * @param {Component} that The gpii.windowsMetrics instance. - * @param {Number} code If less than zero, then don't process. - * @param {Number} message The input message (eg, WM_KEYDOWN or WM_MOUSEMOVE). - * @param {Number} eventData A KBDLLHOOKSTRUCT or MSLLHOOKSTRUCT structure. - */ -windows.metrics.inputHook = function (that, code, message, eventData) { - if (code >= 0) { - windows.metrics.userInput(that); - - var wheelDistance; - - switch (message) { - case windows.API_constants.WM_SYSKEYUP: - case windows.API_constants.WM_KEYUP: - // Key press - var specialKey = windows.metrics.specialKeys[eventData.vkCode]; - - // Ignore injected keys. - var ignoreFlags = windows.API_constants.LLKHF_INJECTED; - - if ((eventData.flags & ignoreFlags) === 0) { - // If the key doesn't generate a character, then don't count it. - var keyValue = windows.user32.MapVirtualKeyW(eventData.vkCode, windows.API_constants.MAPVK_VK_TO_CHAR); - if (specialKey || keyValue) { - windows.metrics.recordKeyTiming(that, eventData.time, specialKey, String.fromCharCode(keyValue)); - } - } - break; - - case windows.API_constants.WM_MOUSEWHEEL: - // "The high-order word indicates the distance the wheel is rotated, expressed in multiples or divisions of - // WHEEL_DELTA, which is 120" - var WHEEL_DELTA = 120; - wheelDistance = windows.hiWord(eventData.mouseData); - // unsigned to signed - if (wheelDistance >= 0x8000) { - wheelDistance -= 0x10000; - } - // number of notches - wheelDistance /= WHEEL_DELTA; - - // fall-through - case windows.API_constants.WM_MOUSEMOVE: - case windows.API_constants.WM_LBUTTONUP: - case windows.API_constants.WM_RBUTTONUP: - // Log events that are not injected. - if ((eventData.flags & windows.API_constants.LLMHF_INJECTED) === 0) { - var button = wheelDistance ? 0 : (message >> 1) & 3; // 2nd + 3rd bits happen to map to the button - windows.metrics.recordMouseEvent(that, button, { x: eventData.ptX, y: eventData.ptY, wheel: wheelDistance }); - } - break; - } - } -}; diff --git a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js index efa8a7888..1902c515f 100644 --- a/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js +++ b/gpii/node_modules/windowsMetrics/test/WindowsMetricsTests.js @@ -1512,17 +1512,14 @@ jqUnit.asyncTest("Testing input metrics: inputHook - only logging desired keys", windowsMetrics.config.input.maxRecords = 1; windowsMetrics.startInputMetrics(); - var lParam = { - flags: 0, - time: 0, - dwExtraInfo: 0 - }; - var keyCount = 0; // Send every key. for (var keyCode = 0; keyCode <= 0xff; keyCode++) { - lParam.vkCode = keyCode; - gpii.windows.metrics.inputHook(windowsMetrics, 0, gpii.windows.API_constants.WM_KEYUP, lParam); + var keyValue = gpii.windows.user32.MapVirtualKeyW(keyCode, gpii.windows.API_constants.MAPVK_VK_TO_CHAR); + var specialKey = gpii.windows.metrics.specialKeys[keyCode]; + if (specialKey || keyValue) { + gpii.windows.metrics.recordKeyTiming(windowsMetrics, 0, specialKey, String.fromCharCode(keyValue)); + } keyCount++; } From 2c4d82a2847c365ee5514c5eab12a08569dc3df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Hern=C3=A1ndez?= Date: Fri, 10 Jan 2020 11:26:53 +0100 Subject: [PATCH 111/123] GPII-4214.GPII-3572: Updated universal reference --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 48710dd69..51d316251 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,7 @@ "dependencies": { "edge-js": "10.3.1", "ffi-napi": "2.4.3", - "gpii-universal": "javihernandez/universal#4231d15715a0df7540abb99a348a7f3b1aa06057", + "gpii-universal": "javihernandez/universal#d5575a770e22467593cd0ddc039435d635342b9c", "@pokusew/pcsclite": "0.4.18", "ref": "1.3.4", "ref-struct": "1.1.0", From 62dab1be974b8a98685feff1776486834e859cf9 Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 17 Jan 2020 15:46:45 +0000 Subject: [PATCH 112/123] GPII-3853: Supporting absolute mouse positioning (and removed disk filling error log) --- .../WindowsUtilities/WindowsUtilities.js | 4 +++ .../src/displaySettingsHandler.js | 22 ++++++++++--- .../windowsMetrics/src/windowsMetrics.js | 31 +++++++++++++++++-- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js index eaec81323..ec3488a41 100644 --- a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js +++ b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js @@ -770,6 +770,10 @@ windows.API_constants = { // https://docs.microsoft.com/windows/desktop/api/winuser/nf-winuser-getsystemmetrics SM_CXSCREEN: 0, SM_CYSCREEN: 1, + SM_XVIRTUALSCREEN: 76, + SM_YVIRTUALSCREEN: 77, + SM_CXVIRTUALSCREEN: 78, + SM_CYVIRTUALSCREEN: 79, SM_CXDOUBLECLK: 36, SM_CYDOUBLECLK: 37, diff --git a/gpii/node_modules/displaySettingsHandler/src/displaySettingsHandler.js b/gpii/node_modules/displaySettingsHandler/src/displaySettingsHandler.js index 93b520415..31bd63637 100644 --- a/gpii/node_modules/displaySettingsHandler/src/displaySettingsHandler.js +++ b/gpii/node_modules/displaySettingsHandler/src/displaySettingsHandler.js @@ -197,13 +197,25 @@ windows.display.setScreenResolution = function (newRes) { * Gets the size of the desktop in logical pixels, taking the current DPI setting into consideration. * This is different to the screen resolution, which counts the physical pixels. * + * @param {Boolean} virtualScreen [optional] true to return the entire virtual screen, otherwise the primary screen. * @return {Object} width and height of the desktop. */ -windows.display.getDesktopSize = function () { - return { - width: windows.user32.GetSystemMetrics(windows.API_constants.SM_CXSCREEN), - height: windows.user32.GetSystemMetrics(windows.API_constants.SM_CYSCREEN) - }; +windows.display.getDesktopSize = function (virtualScreen) { + var size; + if (virtualScreen) { + size = { + width: windows.user32.GetSystemMetrics(windows.API_constants.SM_CXVIRTUALSCREEN) + - windows.user32.GetSystemMetrics(windows.API_constants.SM_CVIRTUALSCREEN), + height: windows.user32.GetSystemMetrics(windows.API_constants.SM_CYVIRTUALSCREEN) + - windows.user32.GetSystemMetrics(windows.API_constants.SM_YVIRTUALSCREEN) + }; + } else { + size = { + width: windows.user32.GetSystemMetrics(windows.API_constants.SM_CXSCREEN), + height: windows.user32.GetSystemMetrics(windows.API_constants.SM_CYSCREEN) + }; + } + return size; }; windows.display.setImpl = function (payload) { diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index dfcaebd9d..f5df71dc7 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -416,12 +416,29 @@ windows.metrics.handleWMINPUT = function (that, lParam) { button = 2; } + var MOUSE_MOVE_ABSOLUTE = 1; + var MOUSE_VIRTUAL_DESKTOP = 2; + var absolute = !!(rawInput.mouse.usFlags & MOUSE_MOVE_ABSOLUTE); + if (absolute) { + // The coordinates appear to range from 0 to 65535 in each direction. + if (!windows.metrics.screenSize) { + windows.metrics.screenSize = gpii.windows.display.getDesktopSize(); + windows.metrics.screenSize.virtual = gpii.windows.display.getDesktopSize(true); + } + var size = (rawInput.mouse.usFlags & MOUSE_VIRTUAL_DESKTOP) + ? windows.metrics.screenSize + : windows.metrics.screenSize.virtual; + rawInput.mouse.lLastX = (rawInput.mouse.lLastX / 0xffff) * size.width; + rawInput.mouse.lLastY = (rawInput.mouse.lLastY / 0xffff) * size.height; + } + windows.metrics.recordMouseEvent( that, button, { x: rawInput.mouse.lLastX, y: rawInput.mouse.lLastY, + absolute: absolute, wheel: wheelDirection } ); @@ -465,6 +482,12 @@ windows.metrics.windowMessage = function (that, hwnd, msg, wParam, lParam) { } break; + case windows.API_constants.WM_DISPLAYCHANGE: + case windows.API_constants.WM_SETTINGCHANGE: + // Cause the screen size to be updated, for the mouse location. + windows.metrics.screenSize = null; + + // fall through default: if (windows.metrics.settingsMessages.indexOf(msg) > -1) { process.nextTick(windows.metrics.configMessage, that, hwnd, msg, wParam, lParamNumber); @@ -1020,11 +1043,15 @@ windows.metrics.recordMouseEvent = function (that, button, pos) { var state = that.state.input; if (state.lastPos) { - state.distance += Math.sqrt(Math.pow(pos.x, 2) + Math.pow(pos.y, 2)); + if (pos.absolute) { + state.distance += Math.sqrt(Math.pow(state.lastPos.x - pos.x, 2) + Math.pow(state.lastPos.y - pos.y, 2)); + } else { + state.distance += Math.sqrt(Math.pow(pos.x, 2) + Math.pow(pos.y, 2)); + } + // There have been some very large mouse distances captured (billions of pixels). The cause is unknown, so let's // just ignore anything that's larger than expected [GPII-3878]. if (state.distance > 0xffff) { - fluid.log("Dropping large mouse distance"); state.distance = 0; } } From 26b52d568c57a58e4d5cfc965af45ef23d6e27fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Hern=C3=A1ndez?= Date: Fri, 17 Jan 2020 23:32:40 +0100 Subject: [PATCH 113/123] GPII-4214.GPII-3572: Removed disk filling error log --- gpii/node_modules/windowsMetrics/src/windowsMetrics.js | 1 - 1 file changed, 1 deletion(-) diff --git a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js index dfcaebd9d..e28a0c40b 100644 --- a/gpii/node_modules/windowsMetrics/src/windowsMetrics.js +++ b/gpii/node_modules/windowsMetrics/src/windowsMetrics.js @@ -1024,7 +1024,6 @@ windows.metrics.recordMouseEvent = function (that, button, pos) { // There have been some very large mouse distances captured (billions of pixels). The cause is unknown, so let's // just ignore anything that's larger than expected [GPII-3878]. if (state.distance > 0xffff) { - fluid.log("Dropping large mouse distance"); state.distance = 0; } } From 37ed48d6cc00d71a0c5ef05d6dd20a4c23a0bef4 Mon Sep 17 00:00:00 2001 From: ste Date: Tue, 21 Jan 2020 17:32:53 +0000 Subject: [PATCH 114/123] GPII-4244: Removing/showing the desktop icons, depending on the metrics switch. --- gpii-service/src/processHandling.js | 47 ++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/gpii-service/src/processHandling.js b/gpii-service/src/processHandling.js index cfb3f251a..1deb59216 100644 --- a/gpii-service/src/processHandling.js +++ b/gpii-service/src/processHandling.js @@ -20,7 +20,9 @@ var service = require("./service.js"), ipc = require("./gpii-ipc.js"), windows = require("./windows.js"), - winapi = require("./winapi.js"); + winapi = require("./winapi.js"), + path = require("path"), + fs = require("fs"); var processHandling = {}; module.exports = processHandling; @@ -113,11 +115,54 @@ processHandling.getProcessList = function (allProcesses) { return processes; }; + +/** + * Hide or show the morphic desktop icon, depending on the value of the metrics switch. + * When hiding, they're moved from C:\Users\Public\Desktop to C:\ProgramData\Morphic\Icons. Showing will move them back. + * This needs to be performed by the service, because they're owned by administrator. + */ +processHandling.toggleDesktopIcons = function () { + var config = processHandling.getGpiiConfig(); + var morphicHide = config && config.value && config.value.startsWith("off:"); + + var iconFiles = [ "Morphic QuickStrip.lnk", "Reset to Standard.lnk" ]; + + var desktopPath = path.join(process.env.PUBLIC || "C:\\Users\\Public", "Desktop"); + var stashPath = path.join(process.env.PROGRAMDATA || "C:\\ProgramData", "Morphic"); + + + iconFiles.forEach(function (file) { + + var desktop = path.join(desktopPath, file); + var stashed = path.join(stashPath, file); + + service.logDebug("Setting desktop icon: ", desktop, stashed, morphicHide ? "hide" : "show"); + + try { + // Ensure there's a copy in the stash location + if (!fs.existsSync(stashed)) { + fs.copyFileSync(desktop, stashed); + } + + if (morphicHide) { + // Remove the desktop icon + if (fs.existsSync(desktop)) { + fs.unlinkSync(desktop); + } + } + } catch (e) { + service.logError("Error setting desktop icon", e.message, e); + } + }); +}; + /** * Starts the configured processes. */ processHandling.startChildProcesses = function () { + processHandling.toggleDesktopIcons(); + var processes = processHandling.getProcessList(); if (processes.length === 0) { From 13e1f5dc4eab55d9aa349435cf2f72d7e0bdcb31 Mon Sep 17 00:00:00 2001 From: ste Date: Tue, 21 Jan 2020 19:16:16 +0000 Subject: [PATCH 115/123] GPII-4244: Made test pass --- gpii-service/tests/service-tests.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/gpii-service/tests/service-tests.js b/gpii-service/tests/service-tests.js index e3d8baace..b3afa4b74 100644 --- a/gpii-service/tests/service-tests.js +++ b/gpii-service/tests/service-tests.js @@ -55,11 +55,10 @@ jqUnit.test("Test config loader", function () { // Check a config file will be loaded if the process is running as a service try { - service.config = null; service.isService = true; - service.loadConfig(testDir); + var config = service.loadConfig(testDir); jqUnit.assertTrue("config should be loaded when running as a service", - service.config && service.config.testLoaded); + config && config.testLoaded); } finally { // Ensure the next user of service.js gets a clean one delete require.cache[require.resolve("../src/service.js")]; From 5794364767055405b602c6528ad140fb45c5cffd Mon Sep 17 00:00:00 2001 From: ste Date: Wed, 22 Jan 2020 19:54:18 +0000 Subject: [PATCH 116/123] GPII-4244: Putting the desktop icons back --- gpii-service/src/processHandling.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/gpii-service/src/processHandling.js b/gpii-service/src/processHandling.js index 1deb59216..11c492de2 100644 --- a/gpii-service/src/processHandling.js +++ b/gpii-service/src/processHandling.js @@ -144,12 +144,17 @@ processHandling.toggleDesktopIcons = function () { fs.copyFileSync(desktop, stashed); } + var iconExists = fs.existsSync(desktop); if (morphicHide) { // Remove the desktop icon - if (fs.existsSync(desktop)) { + if (iconExists) { fs.unlinkSync(desktop); } + } else if (!iconExists) { + // Copy it back from the stash + fs.copyFileSync(stashed, desktop); } + } catch (e) { service.logError("Error setting desktop icon", e.message, e); } From 31eb1c04eaed6b23f8874156e541a99018096a71 Mon Sep 17 00:00:00 2001 From: ste Date: Thu, 23 Jan 2020 11:27:18 +0000 Subject: [PATCH 117/123] GPII-4244: Stringify objects and errors in the log. --- gpii-service/src/logging.js | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/gpii-service/src/logging.js b/gpii-service/src/logging.js index 5bbe06daf..b7d3653ec 100644 --- a/gpii-service/src/logging.js +++ b/gpii-service/src/logging.js @@ -71,7 +71,31 @@ logging.doLog = function (level, args) { var timestamp = new Date().toISOString(); args.unshift(timestamp, level.name); if (logging.logFile) { - var text = args.join(" ") + "\n"; + // Serialise any objects in the arguments. + var text = args.map(function (arg) { + var argOut; + var type = typeof(arg); + var isPrimitive = !arg || type === "string" || type === "number" || type === "boolean"; + if (isPrimitive) { + argOut = arg; + } else { + var obj; + argOut = JSON.stringify(obj || arg, function (key, value) { + if (value instanceof Error) { + // Error doesn't serialise - make it a normal object. + obj = {}; + Object.getOwnPropertyNames(value).forEach(function (a) { + obj[a] = value[a]; + }); + return obj; + } else { + return value; + } + }); + } + return argOut; + }).join(" "); + fs.appendFileSync(logging.logFile, text); } else { console.log.apply(console, args); @@ -164,5 +188,4 @@ logging.logLevel = logging.defaultLevel; /** @name logging.debug * @function */ - module.exports = logging; From fd4f9b36a6cfeb42f1bef219907ea24c145e26ed Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 24 Jan 2020 11:10:17 +0000 Subject: [PATCH 118/123] GPII-4244: Retrying failed downloads. --- gpii-service/README.md | 6 +++- gpii-service/src/configUpdater.js | 30 ++++++++++++++-- gpii-service/tests/configUpdater-tests.js | 43 ++++++++++++++++++----- 3 files changed, 66 insertions(+), 13 deletions(-) diff --git a/gpii-service/README.md b/gpii-service/README.md index 0604ee9cc..7b1e9fc62 100644 --- a/gpii-service/README.md +++ b/gpii-service/README.md @@ -109,6 +109,10 @@ This gets installed in `c:\Program Files (x86)\Morphic\windows\service.json5`. "enabled": false, // true to enable // Where to store the 'last update' info "lastUpdatesFile": "%ProgramData%\\Morphic\\last-updates.json5", + // Number of times to retry a failed update (default: 3). + "retries": 3, + // Milliseconds to wait after failure (default: 5000). + "retryDelay": 5000, // The files to update "files": [{ url: "https://raw.githubusercontent.com/GPII/gpii-app/master/siteconfig.json5", @@ -169,7 +173,7 @@ The installer will install and start the service. ## Command line options -`index.js` recognises the following command-line arguments +`index.js` recognises the following command-line arguments ``` --install Install the Windows Service. diff --git a/gpii-service/src/configUpdater.js b/gpii-service/src/configUpdater.js index 1d17e147d..d6a84726a 100644 --- a/gpii-service/src/configUpdater.js +++ b/gpii-service/src/configUpdater.js @@ -38,6 +38,8 @@ module.exports = configUpdater; * @property {Boolean} enabled `true` to enable automatic config updates. * @property {String} lastUpdatesFile The path to the last updates file. * @property {Array} files The files to update. + * @property {Number} retries Number of times to retry a failed update (default: 3). + * @property {Number} retryDelay Milliseconds to wait after failure (default: 1000). */ /** * @typedef {Object} AutoUpdateFile @@ -127,19 +129,32 @@ configUpdater.saveLastUpdates = function (lastUpdates, path) { /** * Updates all of the files in the auto update configuration (config.autoUpdate). + * @param {Number} retries Number of times to retry a failed update (default: 3). + * @param {Boolean} failedOnly Only update the ones that have failed on a previous call. * @return {Promise} Resolves when complete (even if some fail). */ -configUpdater.updateAll = function () { +configUpdater.updateAll = function (retries, failedOnly) { service.logImportant("Checking for configuration updates"); return configUpdater.loadLastUpdates().then(function (lastUpdates) { + var failed = false; if (!lastUpdates.files) { lastUpdates.files = {}; } - var promises = service.config.autoUpdate.files.map(function (file) { + var updates = service.config.autoUpdate.files; + if (failedOnly) { + updates = updates.filter(function (file) { + return file.failed; + }); + } + + var promises = updates.map(function (file) { var lastUpdate = lastUpdates.files[file.path] || {}; + delete file.failed; return configUpdater.updateFile(file, lastUpdate)["catch"](function (err) { + file.failed = true; + failed = true; service.logWarn("updateFile error", err); }).then(function (newLastUpdate) { if (newLastUpdate) { @@ -150,6 +165,15 @@ configUpdater.updateAll = function () { return Promise.all(promises).then(function () { configUpdater.saveLastUpdates(lastUpdates); + // retry the failed ones + if (failed && retries && retries > 0) { + return new Promise(function (resolve, reject) { + setTimeout(function () { + service.log("Retrying failed downloads", retries); + configUpdater.updateAll(retries - 1, true).then(resolve, reject); + }, service.config.autoUpdate.retryDelay || 5000); + }) ; + } }); }); }; @@ -528,5 +552,5 @@ configUpdater.validateJSON = function (file) { }; if (service.config.autoUpdate.enabled) { - service.readyWhen(configUpdater.updateAll()); + service.readyWhen(configUpdater.updateAll(service.config.autoUpdate.retries || 3)); } diff --git a/gpii-service/tests/configUpdater-tests.js b/gpii-service/tests/configUpdater-tests.js index c023b92d9..9f0f7e084 100644 --- a/gpii-service/tests/configUpdater-tests.js +++ b/gpii-service/tests/configUpdater-tests.js @@ -757,6 +757,9 @@ configUpdaterTests.updateAllTest = { }, file4: { id: "4" + }, + file5: { + id: "5" } } }, @@ -764,10 +767,18 @@ configUpdaterTests.updateAllTest = { {url: "1", path: "file1"}, {url: undefined, path: "file2"}, {url: "3", path: "file3", error: true}, - {url: "4", path: "file4"} + {url: "4", path: "file4"}, + {url: "5", path: "file5", retry: 3} ] }, expect: { + updateAttempts: [ + "file1", + "file2", + "file3", "file3", "file3", "file3", "file3", "file3", + "file4", + "file5", "file5", "file5" + ], lastUpdates: { files: { file1: { @@ -783,6 +794,10 @@ configUpdaterTests.updateAllTest = { }, file2: { handled: true + }, + file5: { + id: "5", + handled: true } } } @@ -1230,13 +1245,14 @@ jqUnit.asyncTest("Test updateFile", function () { jqUnit.asyncTest("Test updateAll", function () { var test = configUpdaterTests.updateAllTest; - jqUnit.expect(test.input.autoUpdateFiles.length + 2); + jqUnit.expect(2); var service = require("../src/service.js"); service.config.autoUpdate = { enabled: true, files: test.input.autoUpdateFiles, - lastUpdatesFile: configUpdaterTests.getTempFile("lastUpdatesFile") + lastUpdatesFile: configUpdaterTests.getTempFile("lastUpdatesFile"), + retryDelay: 1 }; var files = []; @@ -1246,23 +1262,32 @@ jqUnit.asyncTest("Test updateAll", function () { configUpdater.updateFile = updateFileOrig; }); + var retryCount = {}; + // Check that updateFile gets called for every updated file, with the correct data. configUpdater.updateFile = function (update, lastUpdate) { files.push(update.path); + jqUnit.expect(1); jqUnit.assertEquals("update argument must correspond with the correct lastUpdate", update.url, lastUpdate.id); - return update.error + + var error = update.error; + + if (update.retry) { + var retries = retryCount[update.path] || 0; + retryCount[update.path] = ++retries; + error = retries < update.retry; + } + return (error) ? Promise.reject({isError: true}) : Promise.resolve(Object.assign({}, lastUpdate, {handled:true})); }; // Save the data, to ensure the file gets loaded in updateAll configUpdater.saveLastUpdates(test.input.lastUpdates).then(function () { - return configUpdater.updateAll().then(function () { + return configUpdater.updateAll(5).then(function () { // Check every file was processed. - var allFiles = service.config.autoUpdate.files.map(function (file) { - return file.path; - }); - jqUnit.assertDeepEq("updateFile should be called for every file", allFiles.sort(), files.sort()); + jqUnit.assertDeepEq("updateFile should be called for every file", + test.expect.updateAttempts.sort(), files.sort()); // Check that the last-updates file had been written to. return configUpdater.loadLastUpdates().then(function (lastUpdates) { From 82e2b7c3866c80cf97945ffaa05bbb427bc69388 Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 24 Jan 2020 12:35:28 +0000 Subject: [PATCH 119/123] GPII-4244: Added morphic version and siteconfig to update urls. --- gpii-service/README.md | 21 +++ gpii-service/config/service.testing.json5 | 1 + gpii-service/src/configUpdater.js | 19 ++- gpii-service/src/service.js | 9 +- gpii-service/tests/configUpdater-tests.js | 163 +++++++++++++++++++++- gpii-service/tests/service-tests.js | 5 + gpii-service/tests/test-package.json | 5 + 7 files changed, 219 insertions(+), 4 deletions(-) create mode 100644 gpii-service/tests/test-package.json diff --git a/gpii-service/README.md b/gpii-service/README.md index 7b1e9fc62..08f422854 100644 --- a/gpii-service/README.md +++ b/gpii-service/README.md @@ -103,6 +103,8 @@ This gets installed in `c:\Program Files (x86)\Morphic\windows\service.json5`. }, // The file that contains the private site-specific information. "secretFile": "%ProgramData%\\Morphic Credentials\\secret.txt", + // The gpii-app package.json file (default: "resources/app/package.json") + "package.json": "resources/app/package.json", // Auto update of files "autoUpdate": { @@ -119,8 +121,27 @@ This gets installed in `c:\Program Files (x86)\Morphic\windows\service.json5`. path: "%ProgramData%\\Morphic\\siteConfig.json5", isJSON: true // Perform JSON/JSON5 validation before overwriting }, { + // ${Expanders} can be used to take fields from the secrets file. + // See configUpdater.js:configUpdater.expand for syntax. + // In addition, there is: + // ${version}: "version" field of gpii-app package.json. + // ${siteConfig.xyz}: "xyz" field of the current siteConfig (at the time of download). url: "https://example.com/${site}", // `site` field of the secrets file path: "example.json" + }, { + // If an ${expander} resolves to null, the entire string will resolve to null. + // With this in mind, multiple urls can be specified so fallbacks can be used + // when the information isn't available. + // (note: if there's a download error, the next one is NOT used) + url: [ + // If the site config has a `updateUrl` value, this url is used. + "${siteConfig.updateUrl}", + // If the secrets contains a `site` field, this url is used. + "https://example.com/${site}", + // Otherwise, this url is used. + "https://example.com/default", + ], + path: "example.json" }], }, // The path to the site config - The first successfully loaded file in the list is used diff --git a/gpii-service/config/service.testing.json5 b/gpii-service/config/service.testing.json5 index 4cd87d521..f6f8433ee 100644 --- a/gpii-service/config/service.testing.json5 +++ b/gpii-service/config/service.testing.json5 @@ -11,6 +11,7 @@ "level": "DEBUG" }, "secretFile": "test-secret.json5", + "package.json": "tests/test-package.json", "autoUpdate": { "enabled": false } diff --git a/gpii-service/src/configUpdater.js b/gpii-service/src/configUpdater.js index d6a84726a..13a92565b 100644 --- a/gpii-service/src/configUpdater.js +++ b/gpii-service/src/configUpdater.js @@ -191,7 +191,24 @@ configUpdater.updateFile = function (update, lastUpdate) { var togo = Object.assign({}, lastUpdate); var downloadHash, localHash; - var url = configUpdater.expand(update.url, service.getSecrets()); + var expanderSource = Object.assign({ + version: service.config.morphicVersion, + siteConfig: service.getSiteConfig() + }, service.getSecrets()); + + var url; + if (Array.isArray(update.url)) { + // Use the first url that resolves + for (var i = 0; i < update.url.length; i++) { + url = configUpdater.expand(update.url[i], expanderSource); + if (url) { + break; + } + } + } else { + url = configUpdater.expand(update.url, expanderSource); + } + if (url) { var downloadOptions; var hashPromise; diff --git a/gpii-service/src/service.js b/gpii-service/src/service.js index 7c95cdc3d..359343a63 100644 --- a/gpii-service/src/service.js +++ b/gpii-service/src/service.js @@ -64,8 +64,10 @@ service.logDebug = logging.debug; * @property {Object} processes Child process. * @property {Object} logging Logging settings * @property {String} secretFile The file containing site-specific information. + * @property {String} package.json The package.json file for gpii-app. * @property {String} siteConfigFile The site-config file. * @property {AutoUpdateConfig} autoUpdate Auto update settings. + * @property {String} morphicVersion [generated] The version field from gpii-app's package.json. */ /** @@ -78,7 +80,8 @@ service.config = { }, autoUpdate: { lastUpdatesFile: path.join(process.env.ProgramData, "Morphic/last-updates.json5") - } + }, + "package.json": "resources/app/package.json" }; /** @@ -129,6 +132,10 @@ service.loadConfig = function (dir, file) { logging.setLogLevel(config.logging.level); } + // Get the gpii-app version + var packageJson = service.loadJSON(config["package.json"], "package.json file"); + config.morphicVersion = packageJson && packageJson.version; + return config; }; diff --git a/gpii-service/tests/configUpdater-tests.js b/gpii-service/tests/configUpdater-tests.js index 9f0f7e084..24223ca2e 100644 --- a/gpii-service/tests/configUpdater-tests.js +++ b/gpii-service/tests/configUpdater-tests.js @@ -22,6 +22,7 @@ var jqUnit = require("node-jqunit"), http = require("http"), JSON5 = require("json5"), path = require("path"), + URL = require("url").URL, fs = require("fs"); var configUpdater = require("../src/configUpdater.js"); @@ -742,6 +743,146 @@ configUpdaterTests.updateFileTests = [ }, content: "old" } + }, + { + id: "updated (expander in url)", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld + }, + content: "old", + urlQuery: "${site}" + }, + response: { + statusCode: 200, + headers: { + "Last-Modified": configUpdaterTests.testDate + }, + body: "new" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDate, + etag: undefined, + previous: true + }, + request: { + "if-modified-since": configUpdaterTests.testDateOld, + "if-none-match": undefined + }, + content: "new", + search: "?testing.gpii.net" + } + }, + { + id: "updated (multiple urls, no expanders, uses first)", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld + }, + content: "old", + urlQuery: ["it-worked", "you-broke-it"] + }, + response: { + statusCode: 200, + headers: { + "Last-Modified": configUpdaterTests.testDate + }, + body: "new" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDate, + etag: undefined, + previous: true + }, + request: { + "if-modified-since": configUpdaterTests.testDateOld, + "if-none-match": undefined + }, + content: "new", + search: "?it-worked" + } + }, + { + id: "updated (multiple urls, with expander, uses first)", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld + }, + content: "old", + urlQuery: ["it-worked-${site}", "you-broke-it"] + }, + response: { + statusCode: 200, + headers: { + "Last-Modified": configUpdaterTests.testDate + }, + body: "new" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDate, + etag: undefined, + previous: true + }, + request: { + "if-modified-since": configUpdaterTests.testDateOld, + "if-none-match": undefined + }, + content: "new", + search: "?it-worked-testing.gpii.net" + } + }, + { + id: "updated (multiple urls, with expander, uses second)", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld + }, + content: "old", + urlQuery: ["you-broke-it-${stupidValue}", "it-worked-${site}", "you-broke-it-last"] + }, + response: { + statusCode: 200, + headers: { + "Last-Modified": configUpdaterTests.testDate + }, + body: "new" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDate, + etag: undefined, + previous: true + }, + request: { + "if-modified-since": configUpdaterTests.testDateOld, + "if-none-match": undefined + }, + content: "new", + search: "?it-worked-testing.gpii.net" + } + }, + { + id: "no update (multiple urls)", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld, + etag: "the last ETag value" + }, + content: "old", + url: ["", "${stupidValue}", ""] + }, + response: null, + expect: { + lastUpdate: { + date: configUpdaterTests.testDateOld, + etag: "the last ETag value" + }, + request: null, + content: "old" + } } ]; @@ -1156,13 +1297,20 @@ jqUnit.asyncTest("Test updateFile", function () { var server = http.createServer(); server.listen(0, "127.0.0.1"); + var localUrl; // Respond to requests using the details from the test's response object. server.on("request", function (req, res) { - var index = parseInt(req.url.substr(1)); + var url = new URL(localUrl + req.url.substr(1)); + var index = parseInt(url.pathname.substr(1)); var test = configUpdaterTests.updateFileTests[index]; var suffix = " - test.id: " + test.id; + if (url.search || test.expect.search) { + jqUnit.expect(1); + jqUnit.assertEquals("URL search must match the expected value" + suffix, + test.expect.search, url.search); + } // Check the request headers var checkHeaders = {}; for (var key in test.expect.request) { @@ -1185,7 +1333,7 @@ jqUnit.asyncTest("Test updateFile", function () { }); server.on("listening", function () { - var localUrl = "http://" + server.address().address + ":" + server.address().port + "/"; + localUrl = "http://" + server.address().address + ":" + server.address().port + "/"; console.log("http server listening on " + localUrl); var promises = configUpdaterTests.updateFileTests.map(function (test, index) { @@ -1199,6 +1347,17 @@ jqUnit.asyncTest("Test updateFile", function () { always: test.input.always }; + if (test.input.urlQuery) { + if (Array.isArray(test.input.urlQuery)) { + var urls = test.input.urlQuery.map(function (query) { + return file.url + "?" + query; + }); + file.url = urls; + } else { + file.url += "?" + test.input.urlQuery; + } + } + var lastUpdate = Object.assign({}, test.input.lastUpdate); if (test.input.content) { diff --git a/gpii-service/tests/service-tests.js b/gpii-service/tests/service-tests.js index b3afa4b74..07597a3bf 100644 --- a/gpii-service/tests/service-tests.js +++ b/gpii-service/tests/service-tests.js @@ -37,6 +37,11 @@ jqUnit.test("Test config loader", function () { // service.js should already have called service.config. jqUnit.assertNotNull("service.config is called on startup", service.config); + // Check the package.json was read from + var packageJson = require("./test-package.json"); + jqUnit.assertEquals("Version from package.json should be in the config", + packageJson.version, service.config.morphicVersion); + // Create a temporary config file. var testDir = path.join(os.tmpdir(), "gpii-service-test" + Math.random()); var testFile = path.join(testDir, "service.json5"); diff --git a/gpii-service/tests/test-package.json b/gpii-service/tests/test-package.json new file mode 100644 index 000000000..443f89627 --- /dev/null +++ b/gpii-service/tests/test-package.json @@ -0,0 +1,5 @@ +{ + "name": "test-package.json", + "version": "testing-1.2.3", + "description": "Only a test file" +} From b25cc7148220ebc59cb34a4ebf701411fc8703dd Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 24 Jan 2020 12:41:08 +0000 Subject: [PATCH 120/123] GPII-4244: Using certificates from the Windows certificate stores --- gpii-service/package.json | 3 ++- gpii-service/src/configUpdater.js | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/gpii-service/package.json b/gpii-service/package.json index 7cd7319f8..55d524cec 100644 --- a/gpii-service/package.json +++ b/gpii-service/package.json @@ -25,7 +25,8 @@ "ref-array-di": "1.2.1", "ref-napi": "1.4.0", "ref-struct-di": "1.1.0", - "request": "2.88.0" + "request": "2.88.0", + "win-ca": "3.1.1" }, "pkg": { "targets": [ diff --git a/gpii-service/src/configUpdater.js b/gpii-service/src/configUpdater.js index 13a92565b..b667788f2 100644 --- a/gpii-service/src/configUpdater.js +++ b/gpii-service/src/configUpdater.js @@ -32,6 +32,13 @@ var service = require("./service.js"), var configUpdater = {}; module.exports = configUpdater; +// Use certificates from the Windows certificate stores [GPII-4186] +var ca = require("win-ca/api"); +ca({ + store: ["MY", "Root", "Trust", "CA"], + inject: "+" +}); + /** * Configuration for the config auto update. * @typedef {Object} AutoUpdateConfig From ec9e92f7604276939f11b5cf035f4250147f9342 Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 24 Jan 2020 12:55:04 +0000 Subject: [PATCH 121/123] GPII-4244: Added tests for ${siteConfig} in url --- gpii-service/config/service.testing.json5 | 1 + gpii-service/tests/configUpdater-tests.js | 69 ++++++++++++++++++++++- gpii-service/tests/test-siteConfig.json | 4 ++ 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 gpii-service/tests/test-siteConfig.json diff --git a/gpii-service/config/service.testing.json5 b/gpii-service/config/service.testing.json5 index f6f8433ee..61c21adee 100644 --- a/gpii-service/config/service.testing.json5 +++ b/gpii-service/config/service.testing.json5 @@ -12,6 +12,7 @@ }, "secretFile": "test-secret.json5", "package.json": "tests/test-package.json", + "siteConfigFile": "tests/test-siteConfig.json", "autoUpdate": { "enabled": false } diff --git a/gpii-service/tests/configUpdater-tests.js b/gpii-service/tests/configUpdater-tests.js index 24223ca2e..13d8fbc21 100644 --- a/gpii-service/tests/configUpdater-tests.js +++ b/gpii-service/tests/configUpdater-tests.js @@ -774,6 +774,66 @@ configUpdaterTests.updateFileTests = [ search: "?testing.gpii.net" } }, + { + id: "updated (expander in url, ${version})", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld + }, + content: "old", + urlQuery: "${version}" + }, + response: { + statusCode: 200, + headers: { + "Last-Modified": configUpdaterTests.testDate + }, + body: "new" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDate, + etag: undefined, + previous: true + }, + request: { + "if-modified-since": configUpdaterTests.testDateOld, + "if-none-match": undefined + }, + content: "new", + search: "?testing-1.2.3" + } + }, + { + id: "updated (expander in url, ${siteConfig.xxx})", + input: { + lastUpdate: { + date: configUpdaterTests.testDateOld + }, + content: "old", + urlQuery: "${siteConfig.someValue}" + }, + response: { + statusCode: 200, + headers: { + "Last-Modified": configUpdaterTests.testDate + }, + body: "new" + }, + expect: { + lastUpdate: { + date: configUpdaterTests.testDate, + etag: undefined, + previous: true + }, + request: { + "if-modified-since": configUpdaterTests.testDateOld, + "if-none-match": undefined + }, + content: "new", + search: "?value-from-site-config" + } + }, { id: "updated (multiple urls, no expanders, uses first)", input: { @@ -835,13 +895,18 @@ configUpdaterTests.updateFileTests = [ } }, { - id: "updated (multiple urls, with expander, uses second)", + id: "updated (multiple urls, with expander, uses third)", input: { lastUpdate: { date: configUpdaterTests.testDateOld }, content: "old", - urlQuery: ["you-broke-it-${stupidValue}", "it-worked-${site}", "you-broke-it-last"] + urlQuery: [ + "you-broke-it-${stupidValue}", + "you-broke-it-again-${siteConfig.stupid}", + "it-worked-${site}", + "you-broke-it-last" + ] }, response: { statusCode: 200, diff --git a/gpii-service/tests/test-siteConfig.json b/gpii-service/tests/test-siteConfig.json new file mode 100644 index 000000000..cf8493d1b --- /dev/null +++ b/gpii-service/tests/test-siteConfig.json @@ -0,0 +1,4 @@ +{ + "this-is": "a test site config file", + "someValue": "value-from-site-config" +} From 745ffe89a025ff7a036d0b4dd539ae08f1766de3 Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 24 Jan 2020 14:24:08 +0000 Subject: [PATCH 122/123] GPII-4244: Handling more download error --- gpii-service/config/service.dev.json5 | 2 +- gpii-service/src/configUpdater.js | 22 ++++++++++++++++++---- gpii-service/src/logging.js | 2 +- 3 files changed, 20 insertions(+), 6 deletions(-) diff --git a/gpii-service/config/service.dev.json5 b/gpii-service/config/service.dev.json5 index 0a21ffe6f..677caceb3 100644 --- a/gpii-service/config/service.dev.json5 +++ b/gpii-service/config/service.dev.json5 @@ -26,7 +26,7 @@ "%ProgramFiles(x86)%\\Morphic\\windows\\resources\\app\\siteConfig.json5", "%ProgramFiles%\\Morphic\\windows\\resources\\app\\siteConfig.json5" ], - "gpii-config": { + "gpiiConfig": { "env": "NODE_ENV_EXAMPLE", // set to "NODE_ENV" when starting gpii-app // Morphic + metrics: "on:on": "app.testing.metrics", diff --git a/gpii-service/src/configUpdater.js b/gpii-service/src/configUpdater.js index b667788f2..97e9e112e 100644 --- a/gpii-service/src/configUpdater.js +++ b/gpii-service/src/configUpdater.js @@ -171,6 +171,9 @@ configUpdater.updateAll = function (retries, failedOnly) { }); return Promise.all(promises).then(function () { + failed = failed || service.config.autoUpdate.files.some(function (file) { + return file.failed; + }); configUpdater.saveLastUpdates(lastUpdates); // retry the failed ones if (failed && retries && retries > 0) { @@ -253,6 +256,7 @@ configUpdater.updateFile = function (update, lastUpdate) { }); } }, function (err) { + update.failed = true; service.logWarn("updateFile: download failed", err); }); @@ -436,10 +440,20 @@ configUpdater.downloadFile = function (url, localPath, options) { headers["If-Modified-Since"] = options.date; } - var req = request.get({ - url: url, - headers: headers - }); + var req; + try { + req = request.get({ + url: url, + headers: headers + }); + } catch (e) { + reject({ + isError: true, + message: "Unable to download from " + url + ": " + e.message, + url: url, + exception: e + }); + } req.on("error", function (err) { reject({ diff --git a/gpii-service/src/logging.js b/gpii-service/src/logging.js index b7d3653ec..7d792b88f 100644 --- a/gpii-service/src/logging.js +++ b/gpii-service/src/logging.js @@ -96,7 +96,7 @@ logging.doLog = function (level, args) { return argOut; }).join(" "); - fs.appendFileSync(logging.logFile, text); + fs.appendFileSync(logging.logFile, text + "\n"); } else { console.log.apply(console, args); } From 52a7de981cf34a7ae18053af99a8ccd8abec921c Mon Sep 17 00:00:00 2001 From: ste Date: Sat, 25 Jan 2020 23:05:28 +0000 Subject: [PATCH 123/123] GPII-4244: Making tests pass from all-tests.js --- gpii-service/tests/all-tests.js | 2 +- gpii-service/tests/gpii-client-tests.js | 2 +- gpii-service/tests/processHandling-tests.js | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/gpii-service/tests/all-tests.js b/gpii-service/tests/all-tests.js index fea026eb5..76c147e6f 100644 --- a/gpii-service/tests/all-tests.js +++ b/gpii-service/tests/all-tests.js @@ -21,13 +21,13 @@ if (!global.fluid) { // In child process. - require("./service-tests.js"); require("./windows-tests.js"); require("./gpii-ipc-tests.js"); require("./processHandling-tests.js"); require("./pipe-messaging-tests.js"); require("./gpii-client-tests.js"); require("./configUpdater-tests.js"); + require("./service-tests.js"); return; } diff --git a/gpii-service/tests/gpii-client-tests.js b/gpii-service/tests/gpii-client-tests.js index a0b41a1de..5221b69e3 100644 --- a/gpii-service/tests/gpii-client-tests.js +++ b/gpii-service/tests/gpii-client-tests.js @@ -184,7 +184,7 @@ jqUnit.asyncTest("Test request handlers", function () { var tests = gpiiClientTests.requestTests; jqUnit.expect(tests.length * 3); - service.loadConfig(); + service.config = service.loadConfig(); var testIndex = -1; var nextTest = function () { diff --git a/gpii-service/tests/processHandling-tests.js b/gpii-service/tests/processHandling-tests.js index fc44308f7..be4afbda6 100644 --- a/gpii-service/tests/processHandling-tests.js +++ b/gpii-service/tests/processHandling-tests.js @@ -21,6 +21,7 @@ var jqUnit = require("node-jqunit"), path = require("path"), child_process = require("child_process"), processHandling = require("../src/processHandling.js"), + service = require("../src/service.js"), windows = require("../src/windows.js"), winapi = require("../src/winapi.js"); @@ -297,7 +298,7 @@ processHandlingTests.waitForMutex = function (mutexName, timeout) { // Tests getProcessList jqUnit.test("testing getProcessList", function () { - var service = require("../src/service.js"); + var getSiteConfig = service.getSiteConfig; try { @@ -631,8 +632,6 @@ jqUnit.asyncTest("Test unmonitorProcess", function () { jqUnit.asyncTest("Service start+stop", function () { jqUnit.expect(1); - var service = require("../src/service.js"); - var mutexName = "gpii-test-" + Math.random().toString(32); // Configure a child process