gpii-windows
Version:
Components of the GPII personalization infrastructure for use on Microsoft's "Windows" ™
297 lines (259 loc) • 10.3 kB
JavaScript
/*
* Windows Display Settings Handler
*
* Copyright 2016 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 ref = require("ref");
var Struct = require("ref-struct");
var arrayType = require("ref-array");
var ffi = require("ffi-napi");
var fluid = require("gpii-universal");
var os = require("os");
var semver = require("semver");
var gpii = fluid.registerNamespace("gpii");
var windows = fluid.registerNamespace("gpii.windows");
fluid.registerNamespace("gpii.windows.display");
fluid.registerNamespace("gpii.windows.displaySettingsHandler");
// The OS specific files can only be loaded from within the OS is it intended for.
var ver = os.release();
if (semver.gte(ver, "10.0.0")) {
// Windows 10
require("./dpiWindows10.js");
} else {
require("./dpiWindows8.js");
}
var t = windows.types;
var c = windows.API_constants;
var CCHFORMNAME = 32;
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd183565(v=vs.85).aspx
windows.DEVMODEW = new Struct([
[arrayType(t.TCHAR, c.CCHDEVICENAME), "dmDeviceName"],
[t.WORD, "dmSpecVersion"],
[t.WORD, "dmDriverVersion"],
[t.WORD, "dmSize"],
[t.WORD, "dmDriverExtra"],
[t.DWORD, "dmFields"],
["short", "dmOrientation"],
["short", "dmPaperSize"],
["short", "dmPaperLength"],
["short", "dmPaperWidth"],
["short", "dmScale"],
["short", "dmCopies"],
["short", "dmDefaultSource"],
["short", "dmPrintQuality"],
["short", "dmColor"],
["short", "dmDuplex"],
["short", "dmYResolution"],
["short", "dmTTOption"],
["short", "dmCollate"],
[arrayType(t.TCHAR, CCHFORMNAME), "dmFormName"],
[t.WORD, "dmLogPixels"],
[t.DWORD, "dmBitsPerPel"],
[t.DWORD, "dmPelsWidth"],
[t.DWORD, "dmPelsHeight"],
[t.DWORD, "dmDisplayFlags"],
[t.DWORD, "dmDisplayFrequency"],
[t.DWORD, "dmICMMethod"],
[t.DWORD, "dmICMIntent"],
[t.DWORD, "dmMediaType"],
[t.DWORD, "dmDitherType"],
[t.DWORD, "dmReserved1"],
[t.DWORD, "dmReserved2"],
[t.DWORD, "dmPanningWidth"],
[t.DWORD, "dmPanningHeight"]
]);
windows.display.user32 = ffi.Library("user32", {
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd162611(v=vs.85).aspx
// LPCWSTR, DWORD, DEVMODE*
"EnumDisplaySettingsW": [
t.BOOL, ["pointer", t.DWORD, "pointer"]
],
// https://msdn.microsoft.com/en-us/library/windows/desktop/dd183411(v=vs.85).aspx
// DEVMODE*, DWORD
"ChangeDisplaySettingsW": [
t.LONG, ["pointer", t.DWORD]
]
});
/*
* Creates and initializes a new DEVMODEW structure
*/
windows.display.createDevModeStruct = function () {
var dm = new windows.DEVMODEW();
dm.ref().fill(0);
dm.dmSize = windows.DEVMODEW.size;
return dm;
};
/*
* Returns if a screen resolution is invalid
*
* @param (Object)
* @returns {Boolean} true if invalid
*/
windows.display.isInvalid = function (screenRes) {
var isInvalid = true;
if (typeof(screenRes.width) === "number" && typeof(screenRes.height) === "number" && screenRes.width > 0 && screenRes.height > 0) {
fluid.each(windows.display.getAvailableResolutions(), function (validScreenRes) {
if (validScreenRes.dmPelsWidth === screenRes.width && validScreenRes.dmPelsHeight === screenRes.height) {
isInvalid = false;
};
});
}
return isInvalid;
};
/*
* Gets the current display's screen resolution
*
* @return {Object) The width and height of the screen.
*/
windows.display.getScreenResolution = function () {
var dm = windows.display.createDevModeStruct();
if (windows.display.user32.EnumDisplaySettingsW(ref.NULL, c.ENUM_CURRENT_SETTINGS, dm.ref()) !== c.FALSE) {
// note for unknown reason on win 10 the returned dmSize is 188 not expected 220
var closestScreenRes = windows.display.findClosestResolution({width: dm.dmPelsWidth, height: dm.dmPelsHeight});
return { width: closestScreenRes.dmPelsWidth, height: closestScreenRes.dmPelsHeight };
} else {
fluid.fail("Couldn't retrieve the current screen resolution");
}
};
/*
* Gets available resolutions
*
* @return {list} of resolutions
*/
windows.display.getAvailableResolutions = function () {
var index = 0;
var dm = windows.display.createDevModeStruct();
var availableResolutions = [];
while (windows.display.user32.EnumDisplaySettingsW(ref.NULL, index++, dm.ref()) !== c.FALSE) {
var curr = {
dmPelsWidth: dm.dmPelsWidth,
dmPelsHeight: dm.dmPelsHeight
};
availableResolutions.push(curr);
};
fluid.log("windows.display.getAvailableResolutions got available screen resolutions ", availableResolutions);
return availableResolutions;
};
/*
* Sets the current display's screen resolution if possible
*
* @param {Object} The new screen resolution width and height
* @return {Boolean} true if successful
*/
windows.display.setScreenResolution = function (newRes) {
if (windows.display.isInvalid(newRes)) {
fluid.fail("Received an invalid screen resolution: ", newRes);
} else {
var dmCurrent = windows.display.createDevModeStruct();
if (windows.display.user32.EnumDisplaySettingsW(ref.NULL, c.ENUM_CURRENT_SETTINGS, dmCurrent.ref()) !== c.FALSE) {
fluid.log("windows.display.setScreenResolution about to set resolution ", newRes);
// Note: ChangeDisplaySettings has no effect if new is same as current
dmCurrent.dmPelsWidth = newRes.width;
dmCurrent.dmPelsHeight = newRes.height;
var lRet = windows.display.user32.ChangeDisplaySettingsW(dmCurrent.ref(), 0);
if (lRet === c.DISP_CHANGE_SUCCESSFUL) {
return true;
} else if (lRet === c.DISP_CHANGE_RESTART) {
// TODO: According to the returned code, the computer must be restarted for
// the graphics mode to work. For now we return 'true', but this special case
// is related to https://issues.gpii.net/browse/GPII-1982, and we must consider
// this as something that needs to be taken into account and even notified to
// the user.
//
return true;
} else {
// Return codes explained at https://msdn.microsoft.com/en-us/library/dd183411.aspx
fluid.fail("Couldn't set screen resolution, return code was " + lRet);
}
}
}
};
/**
* Gets the size of the desktop in logical pixels, taking the current DPI setting into consideration.
* This is different to the screen resolution, which counts the physical pixels.
*
* @return {Object} width and height of the desktop.
*/
windows.display.getDesktopSize = function () {
return {
width: windows.user32.GetSystemMetrics(windows.API_constants.SM_CXSCREEN),
height: windows.user32.GetSystemMetrics(windows.API_constants.SM_CYSCREEN)
};
};
windows.display.setImpl = function (payload) {
var results = {};
// Changing the screen resolution or DPI moves the mouse pointer to the centre of the screen, so get the current
// position and restore it after the setting change.
var cursorPos = new windows.POINT();
windows.user32.GetCursorPos(cursorPos.ref());
var origSize = windows.display.getDesktopSize();
var targetRes = payload.settings["screen-resolution"];
if (targetRes) {
var oldRes = windows.display.getScreenResolution();
var newRes = oldRes;
if (typeof(targetRes.width) !== "number" || typeof(targetRes.height) !== "number") {
fluid.fail("Incorrect payload for screen resolution: " +
JSON.stringify(payload, null, 4));
}
else if (windows.display.setScreenResolution(targetRes)) {
newRes = targetRes;
}
results["screen-resolution"] = { oldValue: oldRes, newValue: newRes };
}
var targetDpi = payload.settings["screen-dpi"];
if (Number.isInteger(targetDpi)) {
var oldDpi = windows.display.getScreenDpi();
windows.display.setScreenDpi(targetDpi);
results["screen-dpi"] = { oldValue: oldDpi.configured, newValue: targetDpi };
}
fluid.log("display settings handler SET returning results ", results);
// Restore the mouse position, taking into account the change in screen size, so it is back to the previous physical
// position.
var newSize = windows.display.getDesktopSize();
var x = cursorPos.x * (newSize.width / origSize.width);
var y = cursorPos.y * (newSize.height / origSize.height);
windows.user32.SetCursorPos(x, y);
return results;
};
windows.display.getImpl = function () {
var curRes = windows.display.getScreenResolution();
var curDpi = windows.display.getScreenDpi();
var results = {
"screen-resolution": curRes,
"screen-dpi": curDpi.configured
};
return results;
};
windows.displaySettingsHandler.get = function (payload) {
return gpii.settingsHandlers.invokeSettingsHandler(windows.display.getImpl, payload);
};
windows.displaySettingsHandler.set = function (payload) {
return gpii.settingsHandlers.invokeSettingsHandler(windows.display.setImpl, payload);
};
// Internal utilities
// TODO: Move this into gpii.settingsHandlerUtilities and add tests
windows.display.calculateDistance = function (x, y) {
var a = x[0] - y[0];
var b = x[1] - y[1];
return Math.sqrt(a * a + b * b);
};
windows.display.findClosestResolution = function (res) {
var distance = null;
var closest = null;
var availableResolutions = windows.display.getAvailableResolutions();
fluid.each(availableResolutions, function (currRes) {
var currDist = windows.display.calculateDistance([res.width, res.height], [currRes.dmPelsWidth, currRes.dmPelsHeight]);
if (distance === null || currDist < distance) {
distance = currDist;
closest = currRes;
}
});
return closest;
};