From dc9599924db58591fde47c21fdd71711e356cfa3 Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 27 Jul 2018 00:05:49 +0100 Subject: [PATCH 1/7] GPII-3220: Setting the high-contrast themes via their DisplayName. --- .../WindowsUtilities/WindowsUtilities.js | 50 +++++++++++++++++++ .../src/SpiSettingsHandler.js | 40 ++++++++++++++- 2 files changed, 89 insertions(+), 1 deletion(-) diff --git a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js index f6cb9ef5f..e22bbbf63 100644 --- a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js +++ b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js @@ -220,6 +220,13 @@ gpii.windows.advapi32 = new ffi.Library("advapi32", { ] }); +gpii.windows.shlwapi = new ffi.Library("shlwapi", { + // https://docs.microsoft.com/en-us/windows/desktop/api/shlwapi/nf-shlwapi-shloadindirectstring + "SHLoadIndirectString": [ + t.HANDLE, [ "char*", "char*", t.UINT, t.PVOID ] + ] +}); + gpii.windows.shcore = new ffi.Library("Shcore", { // https://msdn.microsoft.com/library/dn302122 "SetProcessDpiAwareness": [ @@ -924,6 +931,49 @@ windows.getWindowProcessId = function (hwnd) { return ptr.deref(); }; +/** + * Gets a localised resource string from an executable. + * + * @param {String} dll The dll name that contains the string. + * @param {Number} resourceID The numeric resource identifier of the string. + * @param {String} defaultValue [optional] The string to return if no string was found. + * @return {String} The localised string, or defaultValue. + */ +windows.getResourceString = function (dll, resourceID, defaultValue) { + // "@filename,-resId" + return fluid.firstDefined(windows.getIndirectString("@" + dll + ",-" + resourceID), defaultValue); +}; + +/** + * Wrapper for SHLoadIndirectString: + * "Extracts a specified text resource when given that resource in the form of an indirect string (a string that begins + * with the '@' symbol)." + * + * This function accepts a string in the form of "@dllname,-resourceID". This will load the resource string identified + * by the numeric resourceID, from the specified dll. Strings that do not begin with "@" will be returned as-is. + * + * See: https://docs.microsoft.com/en-gb/windows/desktop/api/shlwapi/nf-shlwapi-shloadindirectstring + * + * @param indirectString {String} A string in the form of "@dllname,-resourceID" (consult MSDN for other forms). + * @return {String} The localised resource string which the indirectString resolves do, or undefined on error. + */ +windows.getIndirectString = function (indirectString) { + var ret; + if (indirectString.startsWith("@")) { + var src = windows.stringToWideChar(indirectString); + var buf = Buffer.alloc(0xfff); + if (gpii.windows.shlwapi.SHLoadIndirectString(src, buf, buf.length, ref.NULL) === 0) { + ret = gpii.windows.stringFromWideChar(buf); + } else { + ret = undefined; + } + } else { + ret = indirectString; + } + + return ret; +}; + /** * Waits for a condition, by polling a given function. A promise is returned, resolving when the condition is met or * rejecting upon timeout. If the condition is already met, then the returned promise will be resolved. diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index 47ecb7df4..c493687fb 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -206,7 +206,6 @@ gpii.windows.spi.applySettings = function (payload) { // Wait for sethc.exe to end before making the SPI call gpii.windows.spi.waitForSpi() .then(function () { - if (problematicSpiCalls.indexOf(action) >= 0) { var p = gpii.windows.spi.callProblematicSpi(pvParamType, action, uiParam, pvParam); fluid.promise.follow(p, promiseTogo); @@ -444,3 +443,42 @@ gpii.windows.spi.GPII1873_HighContrastBug = function (method, payload, results) } } }; + +/** + * Configures a high-contrast theme, specified from the given .theme file. This setting tells SystemParametersInfo which + * theme to use. + * + * In the registry, the themes are referred to by their display name. This is identified in the .theme file by the + * DisplayName value in the [Theme] section. + * + * There is an additional complexity where the display names of the built-in themes are localised. Fortunately, there + * is an API call that performs this localisation. + * + * This function reads this display name from a .theme file, gets the localised name (if required) and sets it in the + * registry so when SystemParametersInfo is called, the specified theme will be used. + * + * @param {String} filename The .theme file, which is in INI file format. + * @param {Boolean} dryRun True to only return the display name, and not apply it [default: false]. + * @return {String} The display name of the theme specified in the .theme file. + */ +gpii.windows.setHighContrastTheme = function (filename, dryRun) { + var themeData = gpii.iniFile.readFile(filename); + + var displayName; + if (themeData.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"); + } + } + + return displayName; +}; + +fluid.defaults("gpii.windows.setHighContrastTheme", { + gradeNames: "fluid.function", + argumentMap: { + filename: 0 + } +}); From 9a06e94a90a8bcd8bb01261b9a73142675ce32d3 Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 27 Jul 2018 00:18:07 +0100 Subject: [PATCH 2/7] GPII-3220: Tests for setHighContrastTheme --- .../src/SpiSettingsHandler.js | 5 +-- .../test/testSpiSettingsHandler.js | 39 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index c493687fb..c1ed80997 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -458,10 +458,9 @@ gpii.windows.spi.GPII1873_HighContrastBug = function (method, payload, results) * registry so when SystemParametersInfo is called, the specified theme will be used. * * @param {String} filename The .theme file, which is in INI file format. - * @param {Boolean} dryRun True to only return the display name, and not apply it [default: false]. * @return {String} The display name of the theme specified in the .theme file. */ -gpii.windows.setHighContrastTheme = function (filename, dryRun) { +gpii.windows.spiSettingsHandler.setHighContrastTheme = function (filename, dryRun) { var themeData = gpii.iniFile.readFile(filename); var displayName; @@ -476,7 +475,7 @@ gpii.windows.setHighContrastTheme = function (filename, dryRun) { return displayName; }; -fluid.defaults("gpii.windows.setHighContrastTheme", { +fluid.defaults("gpii.windows.spiSettingsHandler.setHighContrastTheme", { gradeNames: "fluid.function", argumentMap: { filename: 0 diff --git a/gpii/node_modules/spiSettingsHandler/test/testSpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/test/testSpiSettingsHandler.js index 40ccd7d35..41faecf14 100644 --- a/gpii/node_modules/spiSettingsHandler/test/testSpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/test/testSpiSettingsHandler.js @@ -95,3 +95,42 @@ jqUnit.asyncTest("SpiSettingsHandler API test - ", function () { }); }); }); + +var highContrastThemes = [ + { + file: "%SystemRoot%\\resources\\Ease of Access Themes\\hc1.theme", + expect: "High Contrast #1" + }, + { + file: "%SystemRoot%\\resources\\Ease of Access Themes\\hc2.theme", + expect: "High Contrast #2" + }, + { + file: "%SystemRoot%\\resources\\Ease of Access Themes\\hcwhite.theme", + expect: "High Contrast White" + }, + { + file: "%SystemRoot%\\resources\\Ease of Access Themes\\hcblack.theme", + expect: "High Contrast Black" + }, + { + file: __dirname + "/test1.theme", + expect: "hard coded display name" + }, + { + file: __dirname + "/test2.theme", + expect: undefined + } +]; + +jqUnit.test("Testing setHighContrastTheme", function () { + + // See what the setHighContrastTheme returns. + fluid.each(highContrastThemes, function (test) { + var file = test.file.replace("%SystemRoot%", process.env.SystemRoot); + var result = gpii.windows.spiSettingsHandler.setHighContrastTheme(file, true); + + jqUnit.assertEquals("setHighContrastTheme must return the expected result, file=" + file, test.expect, result); + + }); +}); From f239c0320a441ba98d19a888e5ea70ef0a4f5222 Mon Sep 17 00:00:00 2001 From: ste Date: Fri, 27 Jul 2018 00:19:02 +0100 Subject: [PATCH 3/7] GPII-3220: Added uncommitted test files. --- gpii/node_modules/spiSettingsHandler/test/test1.theme | 2 ++ gpii/node_modules/spiSettingsHandler/test/test2.theme | 2 ++ 2 files changed, 4 insertions(+) create mode 100644 gpii/node_modules/spiSettingsHandler/test/test1.theme create mode 100644 gpii/node_modules/spiSettingsHandler/test/test2.theme diff --git a/gpii/node_modules/spiSettingsHandler/test/test1.theme b/gpii/node_modules/spiSettingsHandler/test/test1.theme new file mode 100644 index 000000000..a350000d6 --- /dev/null +++ b/gpii/node_modules/spiSettingsHandler/test/test1.theme @@ -0,0 +1,2 @@ +[Theme] +DisplayName=hard coded display name diff --git a/gpii/node_modules/spiSettingsHandler/test/test2.theme b/gpii/node_modules/spiSettingsHandler/test/test2.theme new file mode 100644 index 000000000..39402d800 --- /dev/null +++ b/gpii/node_modules/spiSettingsHandler/test/test2.theme @@ -0,0 +1,2 @@ +[Theme] + From de84ce243bfd3f3d9c5e9c7c3869d643a032d340 Mon Sep 17 00:00:00 2001 From: ste Date: Sun, 29 Jul 2018 12:15:27 +0100 Subject: [PATCH 4/7] GPII-3187: Added custom pre-defined themes --- .../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 c1ed80997..77c2a64c7 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -461,10 +461,10 @@ gpii.windows.spi.GPII1873_HighContrastBug = function (method, payload, results) * @return {String} The display name of the theme specified in the .theme file. */ gpii.windows.spiSettingsHandler.setHighContrastTheme = function (filename, dryRun) { - var themeData = gpii.iniFile.readFile(filename); + var themeData = filename && gpii.iniFile.readFile(filename); var displayName; - if (themeData.Theme) { + if (themeData && themeData.Theme) { displayName = gpii.windows.getIndirectString(themeData.Theme.DisplayName); if (displayName && !dryRun) { gpii.windows.writeRegistryKey("HKEY_CURRENT_USER", "Control Panel\\Accessibility\\HighContrast", From 86b03ba660f1a205a870c8380769ea0e72a9cc0e Mon Sep 17 00:00:00 2001 From: ste Date: Mon, 30 Jul 2018 12:36:51 +0100 Subject: [PATCH 5/7] GPII-3220: Universal reference. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1f17e0a81..d938b048b 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "dependencies": { "edge-js": "8.8.1", "ffi": "2.0.0", - "gpii-universal": "0.3.0-dev.20180711T150609Z.2bb2ecf", + "gpii-universal": "stegru/universal#GPII-3220", "@pokusew/pcsclite": "0.4.18", "ref": "1.3.4", "ref-struct": "1.1.0", From ab5a17cf2c90fa94cfc33da1b40d9f912381f5a6 Mon Sep 17 00:00:00 2001 From: ste Date: Mon, 6 Aug 2018 16:40:29 +0100 Subject: [PATCH 6/7] GPII-3220: Resolving %EnvironmentVariables% in high-contrast theme path. --- .../spiSettingsHandler/src/SpiSettingsHandler.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index 77c2a64c7..45707b0e5 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -461,6 +461,13 @@ gpii.windows.spi.GPII1873_HighContrastBug = function (method, payload, results) * @return {String} The display name of the theme specified in the .theme file. */ gpii.windows.spiSettingsHandler.setHighContrastTheme = function (filename, dryRun) { + // The filename may contain environment variables, "%LikeThis%". + if (filename.indexOf("%") >= 0) { + filename = filename.replace(/%([^%]+)%/g, function (m, name) { + return process.env[name] || ""; + }); + } + var themeData = filename && gpii.iniFile.readFile(filename); var displayName; From 1be8e8d823012697b3ba663bc006663056ec5818 Mon Sep 17 00:00:00 2001 From: ste Date: Mon, 6 Aug 2018 22:20:10 +0100 Subject: [PATCH 7/7] GPII-3220: Made .theme file not found less catastrophic. --- .../spiSettingsHandler/src/SpiSettingsHandler.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js index 45707b0e5..39e984d71 100644 --- a/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js +++ b/gpii/node_modules/spiSettingsHandler/src/SpiSettingsHandler.js @@ -468,7 +468,14 @@ gpii.windows.spiSettingsHandler.setHighContrastTheme = function (filename, dryRu }); } - var themeData = filename && gpii.iniFile.readFile(filename); + var themeData; + + try { + themeData = filename && gpii.iniFile.readFile(filename); + } catch (e) { + fluid.log(e.message); + themeData = null; + } var displayName; if (themeData && themeData.Theme) {