diff --git a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js index c1e8f872f..b5339e173 100644 --- a/gpii/node_modules/WindowsUtilities/WindowsUtilities.js +++ b/gpii/node_modules/WindowsUtilities/WindowsUtilities.js @@ -228,6 +228,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": [ @@ -1035,6 +1042,49 @@ windows.getWindowProcess = function (hwnd) { return pid; }; +/** + * 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..39e984d71 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,55 @@ 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. + * @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; + + try { + themeData = filename && gpii.iniFile.readFile(filename); + } catch (e) { + fluid.log(e.message); + themeData = null; + } + + var displayName; + if (themeData && 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.spiSettingsHandler.setHighContrastTheme", { + gradeNames: "fluid.function", + argumentMap: { + filename: 0 + } +}); 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] + 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); + + }); +}); diff --git a/package.json b/package.json index abe80c778..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.20180822T164749Z.f3aa8f54", + "gpii-universal": "stegru/universal#GPII-3220", "@pokusew/pcsclite": "0.4.18", "ref": "1.3.4", "ref-struct": "1.1.0",