gpii-windows
Version:
Components of the GPII personalization infrastructure for use on Microsoft's "Windows" ™
503 lines (424 loc) • 17 kB
JavaScript
/*
* Windows Native Settings Handler
*
* Deals with individual settings that are only accessible throught isolated
* API calls.
*
* 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
*/
;
var fluid = require("gpii-universal"),
gpii = fluid.registerNamespace("gpii"),
windows = fluid.registerNamespace("gpii.windows"),
ref = require("ref"),
child_process = require("child_process"),
path = require("path");
require("../../WindowsUtilities/WindowsUtilities.js");
fluid.registerNamespace("gpii.windows.nativeSettingsHandler");
/**
* Gets the current double click time for mouse.
*
* @return {Promise} A promise holding the current system double click time for mouse.
*/
windows.nativeSettingsHandler.GetDoubleClickTime = function () {
var result = windows.user32.GetDoubleClickTime();
return fluid.toPromise(result);
};
/**
* Mirrors primary screen into a secondary screen.
*
* Note:
* The native function 'SetDisplayConfig' returns the error 'ERROR_GEN_FAILURE' which stands to code
* 31 when a second display isn't present, this error may be misleading because no system component
* is malfunctioning.
*
* @param {Boolean} mirror A boolean representing the desired state for the 'screen mirror' feature.
* @return {Promise} A promise that resolves on success or holds a error code and message on failure.
*/
windows.nativeSettingsHandler.SetScreenMirrored = function (mirror) {
var pRes = fluid.promise();
var err = 0;
var flags = mirror ?
(windows.API_constants.SDC_TOPOLOGY_CLONE | windows.API_constants.SDC_APPLY) :
(windows.API_constants.SDC_TOPOLOGY_INTERNAL | windows.API_constants.SDC_APPLY);
err = gpii.windows.user32.SetDisplayConfig(0, ref.NULL, 0, ref.NULL, flags);
if (err === 0) {
pRes.resolve();
} else {
pRes.reject({code: err, msg: windows.translateLastError(err)});
}
return pRes;
};
/**
* Queries if the main display is currently mirrored.
*
* @return {Promise} A promise holding a bool with the result.
*/
windows.nativeSettingsHandler.GetScreenMirrored = function () {
// The size of the structs for holding the display config information.
var DISPLAYCONFIG_PATH_INFO_SIZE = 72;
var DISPLAYCONFIG_MODE_INFO_SIZE = 64;
var pModePathArrayElem = ref.alloc(ref.types.uint32, 1);
var pModeInfoArrayElem = ref.alloc(ref.types.uint32, 1);
gpii.windows.user32.GetDisplayConfigBufferSizes(4, pModePathArrayElem, pModeInfoArrayElem);
var topologyIdBuf = ref.alloc(ref.types.uint32, 0);
var pModePathArray = Buffer.alloc(DISPLAYCONFIG_PATH_INFO_SIZE * pModePathArrayElem.deref());
var pModeInfoArray = Buffer.alloc(DISPLAYCONFIG_MODE_INFO_SIZE * pModeInfoArrayElem.deref());
var pRes = fluid.promise();
var err =
gpii.windows.user32.QueryDisplayConfig(
4,
pModePathArrayElem,
pModePathArray,
pModeInfoArrayElem,
pModeInfoArray,
topologyIdBuf
);
if (err === 0) {
var screenCloned = topologyIdBuf.deref() === windows.API_constants.SDC_TOPOLOGY_CLONE;
pRes.resolve(screenCloned);
} else {
pRes.reject({code: err, msg: windows.translateLastError(err)});
}
return pRes;
};
/**
* Sets the current double click time for mouse.
*
* @param {Number} num The double click time to set in the system.
* @return {Promise} A promise that resolves on success or holds a object with the error information
* when rejected.
*/
windows.nativeSettingsHandler.SetDoubleClickTime = function (num) {
var pRes = fluid.promise();
var retVal = windows.user32.SetDoubleClickTime(num);
if (retVal !== 0) {
pRes.resolve();
} else {
var lastErr = windows.kernel32.GetLastError();
var lastErrMsg = windows.translateLastError(lastErr);
pRes.reject({code: lastErr, msg: lastErrMsg});
}
return pRes;
};
/**
* Set Windows background solid color.
*
* @param {Object} rgbColor The color in RGB format, as and object of type: { r: 0x00, g: 0x00, b: 0x00 }
* @return {Promise} Resolve or rejects if failing to set the new system color.
*/
windows.nativeSettingsHandler.SetSolidColor = function (rgbColor) {
var pRes = fluid.promise();
var color = rgbColor.r | rgbColor.g << 8 | rgbColor.b << 16;
var bufElems = ref.alloc(windows.types.INT, windows.API_constants.COLOR_DESKTOP);
var bufColors = ref.alloc(windows.types.UINT, color);
var res = windows.user32.SetSysColors(1, bufElems, bufColors);
if (res === 0) {
pRes.reject("nativeSettingsHandler: Failed to set new system color.");
} else {
pRes.resolve();
}
return pRes;
};
/**
* Returns the current Windows dekstop solid color as a RGB color object of format: { r: 0x00, g: 0x00, b: 0x00 }
*
* @return {Promise} Resolves containing the current system solid color value.
*/
windows.nativeSettingsHandler.GetSolidColor = function () {
var pRes = fluid.promise();
var decimal = windows.user32.GetSysColor(windows.API_constants.COLOR_DESKTOP);
pRes.resolve({ r: (decimal & 0x0000ff), g: (decimal & 0x00ff00) >> 8, b: (decimal & 0xff0000) >> 16 });
return pRes;
};
/**
* Sets the width of the double-click rectangle.
*
* @param {Number} num The number to be set as the width of the double-click rectangle.
* @return {Promise} A promise that resolves on success or holds a object with the error information
* when rejected.
*/
windows.nativeSettingsHandler.SetDoubleClickWidth = function (num) {
var pRes = fluid.promise();
var retVal = gpii.windows.spi.systemParametersInfo("UINT", gpii.windows.spi.actions.SPI_SETDOUBLECLKWIDTH, num, 0, 0);
if (retVal === 0) {
var lastErr = windows.kernel32.GetLastError();
var lastErrMsg = windows.translateLastError(lastErr);
pRes.reject({code: lastErr, msg: lastErrMsg});
} else {
pRes.resolve(retVal);
}
return pRes;
};
/**
* Gets the current width of the double-click rectangle.
*
* @return {Promise} A promise holding the current value of the width for the double-click rectangle.
*/
windows.nativeSettingsHandler.GetDoubleClickWidth = function () {
var result = windows.user32.GetSystemMetrics(windows.API_constants.SM_CXDOUBLECLK);
return fluid.toPromise(result);
};
/**
* Sets the height of the double-click rectangle.
*
* @param {Number} num The number to be set as the height of the double-click rectangle.
* @return {Promise} A promise that resolves on success or holds a object with the error information
* when rejected.
*/
windows.nativeSettingsHandler.SetDoubleClickHeight = function (num) {
var pRes = fluid.promise();
var retVal = gpii.windows.spi.systemParametersInfo("UINT", gpii.windows.spi.actions.SPI_SETDOUBLECLKHEIGHT, num, 0, 0);
if (retVal === 0) {
var lastErr = windows.kernel32.GetLastError();
var lastErrMsg = windows.translateLastError(lastErr);
pRes.reject({code: lastErr, msg: lastErrMsg});
} else {
pRes.resolve(retVal);
}
return pRes;
};
/**
* Function that handles calling the native executable for volume control.
*
* @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.
*/
windows.nativeSettingsHandler.VolumeHandler = function (mode, num) {
var promise = fluid.promise();
try {
var fileName = path.join(__dirname, "../nativeSolutions/VolumeControl/Release/VolumeControl.exe");
var valueBuff;
if (mode === "Get") {
valueBuff = child_process.execFileSync(fileName, ["Get"], {});
} else {
valueBuff = child_process.execFileSync(fileName, ["Set", num], {});
}
var strRes = valueBuff.toString("utf8");
var jsonRes = JSON.parse(strRes);
var value = fluid.get(jsonRes, "Value");
if (value) {
promise.resolve(parseFloat(value));
} else {
promise.reject(jsonRes);
}
} catch (err) {
promise.reject(err);
}
return promise;
};
/**
* Gets the current system volume, calling the native executable 'VolumeControl.exe'
* with the proper payload.
*
* @return {Promise} A promise that resolves on success or holds a object with the error information.
*/
windows.nativeSettingsHandler.GetVolume = function () {
return windows.nativeSettingsHandler.VolumeHandler("Get");
};
/**
* Sets the current system volume, calling the native executable 'VolumeControl.exe'
* with the proper payload.
*
* @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.
*/
windows.nativeSettingsHandler.SetVolume = function (num) {
return windows.nativeSettingsHandler.VolumeHandler("Set", num);
};
/**
* Gets the current height of the double-click rectangle.
*
* @return {Promise} A promise holding the current value of the height for the double-click rectangle.
*/
windows.nativeSettingsHandler.GetDoubleClickHeight = function () {
var result = windows.user32.GetSystemMetrics(windows.API_constants.SM_CYDOUBLECLK);
return fluid.toPromise(result);
};
windows.nativeSettingsHandler.functions = {
SolidColor: {
set: windows.nativeSettingsHandler.SetSolidColor,
get: windows.nativeSettingsHandler.GetSolidColor
},
DoubleClickTime: {
set: windows.nativeSettingsHandler.SetDoubleClickTime,
get: windows.nativeSettingsHandler.GetDoubleClickTime
},
DoubleClickWidth: {
set: windows.nativeSettingsHandler.SetDoubleClickWidth,
get: windows.nativeSettingsHandler.GetDoubleClickWidth
},
DoubleClickHeight: {
set: windows.nativeSettingsHandler.SetDoubleClickHeight,
get: windows.nativeSettingsHandler.GetDoubleClickHeight
},
ScreenMirror: {
set: windows.nativeSettingsHandler.SetScreenMirrored,
get: windows.nativeSettingsHandler.GetScreenMirrored
},
Volume: {
set: windows.nativeSettingsHandler.SetVolume,
get: windows.nativeSettingsHandler.GetVolume
}
};
/**
* Helper function that converts a error to string, so the rejection from the
* settings handler only holds a message.
*
* @param {Object} err The error to be stringified.
* @return {String} The error message.
*/
windows.nativeSettingsHandler.errorInfo = function (err) {
return "With error - '" + JSON.stringify(err) + "'";
};
/**
* Helper function for creating a custom message for error rejection.
*
* @param {Promise} promise The promise to be rejected.
* @param {String} settingKey The name of the requested setting for which an operation failed.
* @param {Object} err The error object.
* @param {String} message A message with information about the operation.
*/
windows.nativeSettingsHandler.reject = function (promise, settingKey, err, message) {
var operation = message + settingKey + "' failed. ";
var errMsg = "nativeSettingsHandler" + operation + windows.nativeSettingsHandler.errorInfo(err);
promise.reject(errMsg);
};
/**
* Setter for the nativeSettingsHandler.
*
* The payload should have the following format:
*
* {
* "functionName": "FunctionName",
* "setParam": val
* }
*
* The first two items:
* - function: Function name for the function to be used to set/get the value.
* - setParam: Parameter to be passed to the 'set' function.
*
* @param {Object} payload The payload.
* @return {Promise} Resolves with the response.
*/
windows.nativeSettingsHandler.setImpl = function (payload) {
var pRes = fluid.promise();
var fnName = fluid.get(payload, "options.functionName");
var setFn = fluid.get(windows.nativeSettingsHandler.functions, fnName + ".set");
var getFn = fluid.get(windows.nativeSettingsHandler.functions, fnName + ".get");
var fnNotDef = [": Failed due to '", " " + fnName + "' function not being defined."];
if (setFn === undefined) {
pRes.reject("nativeSettingsHandler" + fnNotDef[0] + "set" + fnNotDef[1]);
} else if (getFn === undefined) {
pRes.reject("nativeSettingsHandler" + fnNotDef[0] + "get" + fnNotDef[1]);
} else {
var results = {};
var settingsArray = fluid.makeArray(payload.settings);
if (settingsArray.length === 0) {
pRes.reject("nativeSettingsHandler: Failed due to empty payload.");
} else {
var uniqueSetting = settingsArray[0];
// Get the payload value to set.
var settingKey = fluid.keys(uniqueSetting)[0];
var valueToSet = fluid.values(uniqueSetting)[0].value;
// Create object for storing setting results.
results[settingKey] = {};
// Get the current values for the setting.
var pGetOld = getFn();
pGetOld.then(
function (oldVal) {
// Store the current value in results.
fluid.set(results[settingKey], "oldValue.value", oldVal);
var pSet = setFn(valueToSet);
pSet.then(
function () {
var pGetNew = getFn();
pGetNew.then(
function (newVal) {
fluid.set(results[settingKey], "newValue.value", newVal);
pRes.resolve(results);
},
function (err) {
windows.nativeSettingsHandler.reject(pRes, settingKey, err, ": Getting new value for setting '");
}
);
},
function (err) {
windows.nativeSettingsHandler.reject(pRes, settingKey, err, ": Setting new value for setting '");
}
);
},
function (err) {
windows.nativeSettingsHandler.reject(pRes, settingKey, err, ": Getting current value for setting '");
}
);
}
}
return pRes;
};
/**
* Getter for the nativeSettingsHandler.
*
* @param {Object} payload The payload.
* @return {Promise} Resolves with the response.
*/
windows.nativeSettingsHandler.getImpl = function (payload) {
var pRes = fluid.promise();
var fnName = fluid.get(payload, "options.functionName");
var getFn = fluid.get(windows.nativeSettingsHandler.functions, fnName + ".get");
var fnNotDef = [": Failed due to '", " " + fnName + "' function not being defined."];
if (getFn === undefined) {
pRes.reject("nativeSettingsHandler" + fnNotDef[0] + "get" + fnNotDef[1]);
} else {
var settingsArray = fluid.makeArray(payload.settings);
if (settingsArray.length === 0) {
pRes.reject("nativeSettingsHandler: Failed due to empty payload.");
} else {
var uniqueSetting = settingsArray[0];
var results = {};
// Get payload setting and prepare results for returning it
var settingKey = fluid.keys(uniqueSetting)[0];
results[settingKey] = {};
var pGetRes = getFn();
pGetRes.then(
function (curVal) {
results[settingKey].value = curVal;
pRes.resolve(results);
},
function (err) {
windows.nativeSettingsHandler.reject(pRes, settingKey, err, ": Getting current value for setting '");
}
);
}
}
return pRes;
};
/**
* Invoke the settings handler.
*
* @param {Object} payload The payload
* @return {Promise} Resolves with the response.
*/
windows.nativeSettingsHandler.get = function (payload) {
return gpii.settingsHandlers.invokeSettingsHandler(windows.nativeSettingsHandler.getImpl, payload);
};
/**
* Invoke the settings handler.
*
* @param {Object} payload The payload
* @return {Promise} Resolves with the response.
*/
windows.nativeSettingsHandler.set = function (payload) {
return gpii.settingsHandlers.invokeSettingsHandler(windows.nativeSettingsHandler.setImpl, payload);
};