gpii-windows
Version:
Components of the GPII personalization infrastructure for use on Microsoft's "Windows" ™
320 lines (284 loc) • 12.5 kB
JavaScript
/*
* 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
}
});