UNPKG

gpii-windows

Version:

Components of the GPII personalization infrastructure for use on Microsoft's "Windows" ™

320 lines (284 loc) 12.5 kB
/* * Built-in AT. * Handles the registration and de-registration of the Windows built-in AT (magnifier, osk, and narrator). * * Copyright 2016 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 */ "use strict"; var fluid = require("gpii-universal"); var gpii = fluid.registerNamespace("gpii"); require("../registrySettingsHandler/src/RegistrySettingsHandler.js"); /* * The method of (de)registering AT is described in https://msdn.microsoft.com/library/windows/desktop/bb879984.aspx: * * "An application notifies the Ease of Access Center by setting a temporary registry key and then injecting the Windows * Logo key + U key combination into the input stream." * * "The application should create the temporary key at the following location. * HKCU\Software\Microsoft\Windows NT\CurrentVersion\AccessibilityTemp * * The temporary key should have the same name as the registered application name, such as [magnifierpane, osk, or * Narrator]. * The value of the key is a DWORD set to 0x0003 when it is starting, or 0x0002 when the application is exiting." * * Sending this key sequence is not necessary for GPII, as either the AT will do that itself when it starts, or GPII * will execute utilman.exe which performs the necessary work. */ gpii.windows.registeredAT = fluid.freezeRecursive({ /** * The registry key where the temporary values for enabling/disabling are kept. */ accessibilityTempKey: { baseKey: "HKEY_CURRENT_USER", path: "Software\\Microsoft\\Windows NT\\CurrentVersion\\AccessibilityTemp" }, /** * The registry key where the registered AT is defined. */ registeredATKey: { baseKey: "HKEY_LOCAL_MACHINE", path: "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Accessibility\\ATs" }, /** * The registry key where the settings to be transferred to the secure desktop are put. */ settingsTransfer: { baseKey: "HKEY_CURRENT_USER", path: "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Accessibility\\ATConfig" } }); /** * Enable or disable a registered AT application. * * While an application is enabled, Windows will automatically start and stop the application when the user switches * desktops. Disabling will stop this from happening. * * @param {String} name The registered name of the application. This can be "magnifierpane", "osk", "Narrator", or * some other 3rd party name. * @param {Boolean} enable true to enable the application. * @param {Object} options Options * @param {Boolean} options.configOnly true to only (de)register the application and not to start or stop it. * @param {String} options.startCommand Overrides the command used to start the AT application. * @param {String} options.processName Overrides the executable to terminate when disabling. * @param {String} options.utilman Overrides the command for utilman.exe * @return {Promise} Resolves when the AT has started or stopped. */ gpii.windows.enableRegisteredAT = function (name, enable, options) { var defaultOptions = { configOnly: false, utilman: "%SystemRoot%\\System32\\utilman.exe" }; options = fluid.extend(defaultOptions, options); var startPromise; if (!options.configOnly) { if (enable) { startPromise = gpii.windows.startRegisteredAT(name, options); } else { gpii.windows.stopRegisteredAT(name, options); } } var promise = fluid.promise(); // The AT will have enabled itself when it starts, so only update the registry when disabling. if (enable && !options.configOnly) { fluid.promise.follow(startPromise, promise); } else { // On Windows 7/8, if there's more than one action value in the registry then it will display the Ease of Access // Centre. (This could be because when the first AT is loaded, it then invokes yet another instance of utilman, // which will work on the next value pulling the rug from under the first instance). // To work around this, wait until it has been cleared. gpii.windows.whilePendingAT().then(function () { var targetValue = enable ? gpii.windows.API_constants.enableAT : gpii.windows.API_constants.disableAT; gpii.windows.writeRegistryKey(gpii.windows.registeredAT.accessibilityTempKey.baseKey, gpii.windows.registeredAT.accessibilityTempKey.path, name, targetValue, "REG_DWORD"); // Invoke utilman to trigger the de-registration. gpii.windows.nativeExec(options.utilman, null, function (error) { if (error && error.code) { promise.reject({ isError: true, message: "Error starting utilman. " + options.utilman + " returned exit code: " + error.code, error: error }); } else { promise.resolve(); } }); }); } return promise; }; gpii.windows.enableRegisteredAT.queryProcessReporter = function (entry) { return gpii.processReporter.handleIsRunning({ isRunning: entry.options.getState } ); }; gpii.windows.enableRegisteredAT.set = function (settingsArray) { var promises = []; var togo = gpii.settingsHandlers.transformPayload(settingsArray, function (entry) { var registryName = entry.options.registryName; var enable = entry.settings.running; // get current value: var oldValue = gpii.windows.enableRegisteredAT.queryProcessReporter(entry); var p = gpii.windows.enableRegisteredAT(registryName, enable, entry.options); promises.push(p); return { settings: { running: { "oldValue": oldValue, "newValue": enable } } }; }); // Resolve the return promise when the AT ones resolve. var promiseTogo = fluid.promise(); fluid.promise.sequence(promises).then(function () { promiseTogo.resolve(togo); }); return promiseTogo; }; gpii.windows.enableRegisteredAT.get = function (settingsArray) { return gpii.settingsHandlers.transformPayload(settingsArray, function (entry) { return { settings: { running: gpii.windows.enableRegisteredAT.queryProcessReporter(entry) } }; }); }; /** * Waits while there are pending actions for utilman. * * An entry in the AccessibilityTemp key with a value of either enableAT or disableAT means there is an action * for utilman. When utilman performs these actions, it will change the values to something else (so it doesn't repeat * the work). * * This registry key is polled for these values, and the returned promise will resolve when there aren't any. * * @param {Object} options [optional] Options * @param {Number} options.pollDelay How often, in milliseconds, to check for changes. * @param {Number} options.timeout Milliseconds to timeout after. * @return {promise} Returns a promise, resolving when there are no pending actions. The value is "timeout" on timeout. */ gpii.windows.whilePendingAT = function (options) { var defaultOptions = { pollDelay: 500, timeout: 5000 }; options = fluid.extend(true, defaultOptions, options); var checkActions = function () { // Scan the AccessibilityTemp key for action values. var values = gpii.windows.enumRegistryValues(gpii.windows.registeredAT.accessibilityTempKey.baseKey, gpii.windows.registeredAT.accessibilityTempKey.path); // Check for a value that's enableAT or disableAT return !fluid.find(values, function (value, name) { var data = fluid.get(values, [name, "data"]); if ((data === gpii.windows.API_constants.enableAT) || (data === gpii.windows.API_constants.disableAT)) { return true; } }); }; var waitOptions = { pollDelay: options.pollDelay, timeout: options.timeout, dontReject: true }; return gpii.windows.waitForCondition(checkActions, waitOptions); }; /** * Starts a registered AT application. * * @param {String} name The registered name of the application. This can be "magnifierpane", "osk", "Narrator", or * some other 3rd party name. * @param {Object} [options] Options * @param {String} options.startCommand Overrides the command used to start the AT application. * @param {Object} options.atInfo Overrides the AT information taken from the registry. * @return {Promise} Resolves when the AT has started. */ gpii.windows.startRegisteredAT = function (name, options) { var command; var atInfo; if (options && options.startCommand) { command = options.startCommand; } else { // StartExe + StartParams is the command to execute atInfo = gpii.windows.getATInformation(name, ["StartExe", "StartParams", "ATExe"]); if (options && options.atInfo) { atInfo = fluid.extend(atInfo, options.atInfo); } command = atInfo.StartExe; if (atInfo.StartParams !== undefined) { command += " " + atInfo.StartParams; } } var promise = fluid.promise(); // Before executing the command, make sure utilman isn't already running. gpii.windows.waitForProcessTermination("utilman.exe").then(function () { gpii.windows.nativeExec(command); fluid.promise.follow(gpii.windows.waitForProcessStart(atInfo.ATExe), promise); }); return promise; }; /** * Stops a registered AT application. * * @param {String} name The registered name of the application. This can be "magnifierpane", "osk", "Narrator", or * some other 3rd party name. * @param {Object} [options] Options * @param {String} options.processName Overrides the executable name to terminate. * @param {Object} options.atInfo Overrides the AT information taken from the registry. */ gpii.windows.stopRegisteredAT = function (name, options) { var exeName; var atInfo = gpii.windows.getATInformation(name, ["ATExe", "CopySettingsToLockedDesktop"]); if (options && options.processName) { exeName = options.processName; } else { if (options && options.atInfo) { atInfo = fluid.extend(atInfo, options.atInfo); } exeName = atInfo.ATExe; } gpii.windows.killProcessByName(exeName); // Remove the registry key that's used to transfer settings to the secure desktop, otherwise these settings // will override any future settings. Really, the AT should do this when it closes, unfortunately it doesn't get // closed cleanly so it is getting done here instead.GPII-2315. if (atInfo.CopySettingsToLockedDesktop) { var regPath = gpii.windows.registeredAT.settingsTransfer.path + "\\" + name; gpii.windows.deleteRegistryKey(gpii.windows.registeredAT.settingsTransfer.baseKey, regPath); } }; /** * Gets some pieces of information about a registered AT application. * * The values are documented in https://msdn.microsoft.com/library/windows/desktop/bb879984.aspx * * @param {String} name The registered name of the application. This can be "magnifierpane", "osk", "Narrator", or * some other 3rd party name. * @param {Array<String>} values A list of the values required. * @return {Object} An object containing the values from the registry. */ gpii.windows.getATInformation = function (name, values) { var regPath = gpii.windows.registeredAT.registeredATKey.path + "\\" + name; var togo = {}; for (var n = 0, len = values.length; n < len; n++) { var value = gpii.windows.readRegistryKey(gpii.windows.registeredAT.registeredATKey.baseKey, regPath, values[n], "REG_SZ"); togo[values[n]] = value.value; } return togo; }; fluid.defaults("gpii.windows.enableRegisteredAT", { gradeNames: "fluid.function", argumentMap: { name: 0, enable: 1 } });