UNPKG

gpii-windows

Version:

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

420 lines (366 loc) 13.8 kB
/* * 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 */ "use strict"; 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; };