UNPKG

gpii-windows

Version:

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

297 lines (259 loc) 10.3 kB
/* * 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 */ "use strict"; 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; };