gpii-windows
Version:
Components of the GPII personalization infrastructure for use on Microsoft's "Windows" ™
420 lines (366 loc) • 13.8 kB
JavaScript
/*
* WmiSetttingsHandler
*
* Copyright 2018 Raising the Floor - US
*
* Licensed under the New BSD license. You may not use this file except in
* compliance with this License.
*
* You may obtain a copy of the License at
* https://github.com/GPII/universal/blob/master/LICENSE.txt
*/
;
var fluid = require("gpii-universal"),
path = require("path"),
edge = process.versions.electron ? require("electron-edge-js") : require("edge-js");
var gpii = fluid.registerNamespace("gpii");
var windows = fluid.registerNamespace("gpii.windows");
fluid.registerNamespace("gpii.windows.wmi");
fluid.registerNamespace("gpii.windows.wmiSettingsHandler");
/**
* Merge two objects same keys into single one.
*
* @param {Object} objA First object which keys are going to be merged.
* @param {Object} objB Second object which keys are going to be merged.
* @return {Array} Returns an array with the merged objects for the same keys.
*/
gpii.windows.wmi.mergeSameKeys = function (objA, objB) {
var aKeys = Object.keys(objA);
var bKeys = Object.keys(objB);
var joinedKeys = {};
fluid.each(aKeys, function (aKey) {
fluid.each(bKeys, function (bKey) {
if (aKey === aKey) {
joinedKeys[aKey] = Object.assign({}, objA[aKey], objB[bKey]);
}
});
});
return joinedKeys;
};
/**
* Replaces the placeholder "$value" used to mark the position of the
* desired parameter in the paramaters that are goint to be supplied to
* the function specified in the payload.
*
* @param {Array} query The queries which placeholders are going to be replaced.
* @return {Array} The queries with the placeholders replaced.
*/
gpii.windows.wmi.replacePlaceholder = function (query) {
var qKeys = Object.keys(query);
var rQuery = {};
fluid.each(qKeys, function (pKey) {
var kSetting = query[pKey];
var params = kSetting.set.params;
var pIndex = params.indexOf("$value");
if (pIndex === -1) {
fluid.fail("Error: Invalid payload format. No value placeholder supplied.");
} else {
var pQuery = fluid.copy(kSetting);
pQuery.set.params[pIndex] = kSetting.value;
delete pQuery.value;
rQuery[pKey] = pQuery;
}
});
return rQuery;
};
windows.wmi.preparePayload = function (settings, options) {
var queries = windows.wmi.mergeSameKeys(settings, options);
var rQueries = windows.wmi.replacePlaceholder(queries);
return rQueries;
};
/**
* Flattens the 'set query' form a JSON object to an array, to it can be passed to
* 'updateWMISetting.csx'.
*
* @param {Object} query The queries to be flatten into an array.
* @return {Object} Returns the set query flattened into an array.
*/
gpii.windows.wmi.flatSetQuery = function (query) {
var arrayQuery = [];
arrayQuery.push(query.namespace);
arrayQuery.push(query.set.className);
arrayQuery.push(query.set.method);
arrayQuery.push(query.set.params);
arrayQuery.push(query.set.returnVal);
return arrayQuery;
};
/**
* Flattens the 'set query' form a JSON object to an array, to it can be passed to
* 'updateWMISetting.csx'.
*
* @param {Object} query The queries to be flatten into a set array.
* @return {Object} Returns the set query flattened into an array.
*/
gpii.windows.wmi.flatGetQuery = function (query) {
var arrayQuery = [];
arrayQuery.push(query.namespace);
arrayQuery.push(query.get.query);
return arrayQuery;
};
var updateQuery = edge.func({
source: path.join(__dirname, "..\\dotNetSrc\\updateWMISetting.cs"),
references: ["System.Management.dll"]
});
windows.wmi.updateWMISetting = function (query) {
var result = null;
try {
result = updateQuery(query, true);
} catch (error) {
if (error.ErrorCode !== "NotSupported") {
fluid.fail("windows.wmi.updateWMISetting: Failed with WMI error msg - '" + error + "'");
}
}
return result;
};
/**
* Casts the setting back to the desired type, form one of the supported types
* when returned as a string from the WMI call.
*
* @param {String} value The setting value as string.
* @param {String} settingType The type to which the setting needs to be casted to.
* @return {Object} The setting in the correct type.
*/
windows.wmi.castReturnValue = function (value, settingType) {
var result = null;
if (settingType === "uint") {
result = parseInt(value);
} else if (settingType === "string") {
result = value;
} else if (settingType === "dynamic") {
result = value;
}
return result;
};
/**
* Function to handle settings which value range changes certain system conditions.
* Based on the settingId, the function modifies the results obtained from the previous
* set operation.
*
* Motivation:
* In portable devices running in battery, Windows 10 "battery saver" mode could activate
* the feature "Lower screen brightness while in battery saver". This feature decreases the
* available range for brightness to be set, from the regular scale of '0.0..1.0' to
* '0.0..0.7'. For this reason, in case of missmatch between the setted and
* getted value when changing brightness, we map the get value from the '0.0..1.0' scale
* to the '0.0..1.0' scale. So the feature is transparent for us.
*
* @param {String} settingId The current setting id being processed.
* @param {Object} payload The payload supplied to the settingsHandler.
* @param {Object} results The results of the set operation.
*/
windows.wmi.handleSpecialSettings = function (settingId, payload, results) {
if (settingId === "Brightness") {
var desiredValue = payload.settings.Brightness.value;
var oldValue = fluid.get(results, settingId + ".oldValue.value");
var newValue = fluid.get(results, settingId + ".newValue.value");
if (oldValue !== newValue) {
if (desiredValue !== newValue) {
var scaledNewValue = Math.round((newValue / 0.7));
// The reason behind using (scaleNewValue - 1) as an alternative is because system
// applies roof(Num * 0.7) when a new value is being set.
if (scaledNewValue === desiredValue || (scaledNewValue - 1) === desiredValue) {
var scaledOldValue = Math.round((oldValue / 0.7));
fluid.set(results, settingId + ".oldValue.value", scaledOldValue);
fluid.set(results, settingId + ".newValue.value", scaledNewValue);
}
}
}
}
};
/**
* Makes a query to modify a WMI setting with the payload information. The payload format
* should have the following schema:
*
* "settings": {
* "SettingName": {
* "value": X
* }
* },
* "options": {
* "SettingName": {
* "namespace": "WMINamespace",
* "get": {
* "query": "SELECT X FROM Y"
* },
* "set": {
* "className": "WMIClassName",
* "method": "WMIMethodName",
* "params": [
* param1,
* "$value",
* param2,
* etc...
* ],
* "returnType": [
* Type,
* Value
* ]
* }
* }
* }
*
* * Type should be one of the supported types, prensent in "updateWMISettings".
*
* PE:
*
* "com.microsoft.windows.brightness": [
* {
* "settings": {
* "Brightness": {
* "value": 100
* }
* },
* "options": {
* "Brightness": {
* "namespace": "root\\WMI",
* "get": {
* "query": "SELECT CurrentBrightness FROM WmiMonitorBrightness"
* },
* "set": {
* "className": "WmiMonitorBrightnessMethods",
* "method": "WmiSetBrightness",
* "params": [
* 4294967295,
* "$value"
* ],
* "returnType": [
* "uint",
* 0
* ]
* }
* }
* }
* }
* ]
*
* @param {Object} payload The payload with the information to set the desired setting.
* @return {Object} Returns an object with the settings values before and after being called.
*/
gpii.windows.wmi.setImpl = function (payload) {
var settings = payload.settings;
var options = payload.options;
var results = {};
var fPayload = windows.wmi.preparePayload(settings, options);
var getResults = gpii.windows.wmi.getImpl(payload);
fluid.each(getResults, function (result, settingId) {
if (result.value === null) {
fluid.log("WmiSetttingsHandler: Requested setting '" + settingId + "' is not available in this system.");
fluid.set(results, settingId + ".oldValue.value", null);
fluid.set(results, settingId + ".newValue.value", null);
}
});
fluid.each(fPayload, function (setting, settingId) {
if (fluid.get(results, settingId + ".oldValue.value") !== null) {
fluid.set(results, settingId + ".oldValue.value", getResults[settingId].value);
var setQuery = windows.wmi.flatSetQuery(setting);
var result = windows.wmi.updateWMISetting(setQuery);
if (result === false) {
fluid.fail("WmiSettingsHandler: Failed to set setting with WMI payload - '" + JSON.stringify(setQuery) + "'" );
}
getResults = gpii.windows.wmi.getImpl(payload);
fluid.set(results, settingId + ".newValue.value", getResults[settingId].value);
windows.wmi.handleSpecialSettings(settingId, payload, results);
}
});
return results;
};
/**
* WMISettingsHandler 'set' entry point.
*
* @param {Object} payload The payload that is going to be supplied to the settings handler.
* @return {Promise} The result of calling the settings handler with the supplied promise.
*/
gpii.windows.wmiSettingsHandler.set = function (payload) {
return gpii.settingsHandlers.invokeSettingsHandler(gpii.windows.wmi.setImpl, payload);
};
var getQuery = edge.func({
source: path.join(__dirname, "..\\dotNetSrc\\queryWMISetting.cs"),
references: ["System.Management.dll"]
});
/**
* Calls C# code for querying WMI.
*
* @param {Object} query The query that is going to be passed to 'queryWMISetting'.
* @return {Object} The result of the query.
*/
gpii.windows.wmi.getQuery = function (query) {
var result = null;
try {
result = getQuery(query, true);
} catch (error) {
if (error.ErrorCode !== "NotSupported") {
// We do not raise an error here because this code is being called by the deviceReporter,
// which at this moment doesn't handle failures in the same way the settingsHandler code does.
fluid.log("gpii.windows.wmi.getQuery: Query failed with WMI error msg - '" + error + "'");
}
}
return result;
};
/**
* Makes a query to get WMI settings with the payload information. The payload format should
* have the following schema:
*
* @param {Object} payload The payload that is going to be supplied to the settingsHandler.
* @return {Object} The values that are being queried to the WMI.
*/
gpii.windows.wmi.getImpl = function (payload) {
var settingsOptions = payload.options;
var results = {};
fluid.each(settingsOptions, function (settingOptions, settingId) {
var query = windows.wmi.flatGetQuery(settingOptions);
var queryValueStr = windows.wmi.getQuery(query);
var queryValue = windows.wmi.castReturnValue(queryValueStr, settingOptions.settingType);
fluid.set(results, settingId + ".value", queryValue);
});
return results;
};
/**
* WMISettingsHandler 'get' entry point.
*
* @param {Object} payload The payload that is going to be supplied to the settings handler.
* @return {Promise} The result of calling the settings handler with the supplied promise.
*/
gpii.windows.wmiSettingsHandler.get = function (payload) {
return gpii.settingsHandlers.invokeSettingsHandler(gpii.windows.wmi.getImpl, payload);
};
/*
* Registers the grade to be used in the solution registry to check if a particular
* particular WMI accessed setting is supported.
*/
fluid.defaults("gpii.deviceReporter.wmiSettingSupported", {
gradeNames: "fluid.function",
argumentMap: {
namespace: 0,
query: 1
}
});
/**
* Function to search for a particular setting using WMI interface in order to check if
* it's supported.
*
* @param {String} namespace The WMI namespace in which perform the query.
* @param {String} query The query to be performed in order to verify that the setting is supported.
* @return {Boolean} True if the setting is supported, false otherwise.
*/
gpii.deviceReporter.wmiSettingSupported = function (namespace, query) {
var payload = {
"options": {
"Setting": {
"namespace": namespace,
"get": {
"query": query
},
"settingType": "dynamic"
}
}
};
var payloadRes = gpii.windows.wmi.getImpl(payload);
var value = fluid.get(payloadRes, "Setting.value");
var result;
if (value === null) {
result = false;
} else {
result = true;
}
return result;
};