UNPKG

gpii-windows

Version:

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

336 lines (299 loc) 10.7 kB
/* * App Bar - Makes a window an App Bar, like the taskbar. * * Copyright 2020 Raising the Floor - International * * Licensed under the New BSD license. You may not use this file except in * compliance with this License. * * The R&D leading to these results received funding from the * Department of Education - Grant H421A150005 (GPII-APCP). However, * these results do not necessarily represent the policy of the * Department of Education, and you should not assume endorsement by the * Federal Government. * * 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"); var gpii = fluid.registerNamespace("gpii"); fluid.registerNamespace("gpii.windows.appBar"); fluid.defaults("gpii.windows.appBar", { gradeNames: ["fluid.component"], listeners: { "onCreate.appBar": { funcName: "gpii.windows.appBar.created", args: ["{that}"] }, "onDestroy.appBar": { funcName: "gpii.windows.appBar.destroyed", args: ["{that}"] } }, invokers: { // Set these to the corresponding functions of the electron window. getNativeWindowHandle: "fluid.notImplemented", hookWindowMessage: "fluid.notImplemented", unhookWindowMessage: "fluid.notImplemented", listenForMessage: { funcName: "gpii.windows.appBar.listenForMessage", args: ["{that}", "{arguments}.0"] }, updateAppBar: { funcName: "gpii.windows.appBar.updateAppBar", // // message, appBarData, (out) approveRect args: [ "{that}.hwnd", "{arguments}.0", "{arguments}.1", "{arguments}.2" ] }, enable: { funcName: "gpii.windows.appBar.enable", args: ["{that}"] }, disable: { funcName: "gpii.windows.appBar.disable", args: ["{that}"] }, getTaskbarPos: { funcName: "gpii.windows.appBar.getTaskbarPos" }, getWorkAreaBottom: { funcName: "gpii.windows.appBar.getWorkAreaBottom" } }, events: { onAppBarChange: null, onPositionChange: null }, members: { hwnd: 0, hookedMessages: [], WM_GPII_APPBAR: "@expand:gpii.windows.messages.registeredMessage(WM_GPII_APPBAR)", enabled: false, bottomTaskbar: true } }); /** * @typedef {Object} AppBarData * @property {Number} callbackMessage * @property {Number} edge * @property {RECT} rc * @property {Number} lParam */ /** * Start some message hooks on the QSS window. * @param {Component} that The gpii.windows.appBar instance. */ gpii.windows.appBar.created = function (that) { that.hwnd = that.getNativeWindowHandle().readInt32LE(); that.listenForMessage(that.WM_GPII_APPBAR); that.listenForMessage(gpii.windows.API_constants.WM_WINDOWPOSCHANGING); that.listenForMessage(gpii.windows.API_constants.WM_WINDOWPOSCHANGED); that.listenForMessage(gpii.windows.API_constants.WM_ACTIVATE); that.updateAppBar(gpii.windows.API_constants.ABM_NEW, { callbackMessage: that.WM_GPII_APPBAR }); }; /** * Release the message hooks on the QSS window. * @param {Component} that The gpii.windows.appBar instance. */ gpii.windows.appBar.destroyed = function (that) { fluid.each(that.hookedMessages, function (msg) { that.unhookWindowMessage(msg); }); }; /** * Adds a message hook to the QSS window * @param {Component} that The gpii.windows.appBar instance. * @param {Number} msg The window message to listen for. */ gpii.windows.appBar.listenForMessage = function (that, msg) { if (!that.hookedMessages.includes(msg)) { that.hookedMessages.push(msg); that.hookWindowMessage(msg, function (wParam, lParam) { return gpii.windows.appBar.windowMessage(that, that.hwnd, msg, wParam, lParam); }); } }; /** * Handles messages from the window. * * @param {Component} that An instance of gpii.app * @param {Number} hwnd Window handle. * @param {Number} msg The message. * @param {Number} wParam Message parameter. * @param {Number} lParam Message parameter. */ gpii.windows.appBar.windowMessage = function (that, hwnd, msg, wParam, lParam) { switch (msg) { case that.WM_GPII_APPBAR: if (wParam.readUInt32LE() === gpii.windows.API_constants.ABN_POSCHANGED) { if (that.enabled) { gpii.windows.appBar.updatePosition(that); } } break; case gpii.windows.API_constants.WM_ACTIVATE: that.updateAppBar(gpii.windows.API_constants.ABM_ACTIVATE); break; case gpii.windows.API_constants.WM_WINDOWPOSCHANGED: that.updateAppBar(gpii.windows.API_constants.ABM_WINDOWPOSCHANGED); break; case gpii.windows.API_constants.WM_WINDOWPOSCHANGING: if (that.enabled) { // Put the memory pointed to by (WINDOWPOS)lParam into windowPos var windowPos = new gpii.windows.WINDOWPOS(); var windowPosPtr = lParam.readPointer().reinterpret(gpii.windows.WINDOWPOS.size); windowPosPtr.copy(windowPos.ref()); var SWP_NOMOVE = 0x2; var SWP_NOREDRAW = 0x8; var windowRect = gpii.windows.getWindowRect(that.hwnd); if (!windowPos.cy) { windowPos.cy = windowRect.height; } windowPos.flags |= SWP_NOREDRAW; windowPos.flags &= ~(0x100 | 0x0020); // Just after the work area change, Windows sometimes adjusts the position to inside the work area. // Remove the SWP_NOMOVE bit and set the location to prevent this. if (windowPos.flags & SWP_NOMOVE) { windowPos.flags &= ~SWP_NOMOVE; windowPos.x = windowRect.left; windowPos.y = windowRect.top; } // Keep it at the bottom var bottom = that.getWorkAreaBottom(); if (windowPos.y + windowPos.cy < bottom) { windowPos.y = bottom - windowPos.cy; } // Copy the struct back to the pointer. windowPos.ref().copy(windowPosPtr); } break; } }; /** * Update the app bar state. * * A wrapper for SHAppBarMessage: https://docs.microsoft.com/windows/win32/api/shellapi/nf-shellapi-shappbarmessage * * @param {Number} hwnd The window handle. * @param {Number} message The message (ABM_*) * @param {AppBarData} appBarData Information about the state. * @return {Number} The return of SHAppBarMessage. Message specific, normally !0 for success. */ gpii.windows.appBar.updateAppBar = function (hwnd, message, appBarData) { if (!appBarData) { appBarData = {}; } var data = new gpii.windows.APPBARDATA(); data.ref().fill(0); data.cbSize = gpii.windows.APPBARDATA.size; data.hWnd = hwnd; data.uCallbackMessage = appBarData.callbackMessage || 0; data.uEdge = appBarData.edge || 0; data.lParam = appBarData.lParam || 0; if (appBarData.rc) { data.rc.left = appBarData.rc.left; data.rc.top = appBarData.rc.top; data.rc.right = appBarData.rc.right === undefined ? appBarData.rc.left + appBarData.rc.width : appBarData.rc.right; data.rc.bottom = appBarData.rc.bottom === undefined ? appBarData.rc.top + appBarData.rc.height : appBarData.rc.bottom; } return gpii.windows.shell32.SHAppBarMessage(message, data.ref()); }; /** * Update the position of the window. * @param {Component} that The gpii.windows.appBar instance. */ gpii.windows.appBar.updatePosition = function (that) { var rect; var windowRect = gpii.windows.getWindowRect(that.hwnd); var taskbarRect = gpii.windows.getWindowRect(gpii.windows.getTasktrayWindow()); if (taskbarRect.left === 0 && taskbarRect.top > 0) { // Taskbar is at the bottom (the usual position) rect = { left: 0, top: taskbarRect.top - windowRect.height - 1, right: taskbarRect.width, bottom: taskbarRect.top }; } else { var desktopRect = gpii.windows.display.getDesktopSize(); rect = { left: 0, top: desktopRect.height - windowRect.height - 1, width: desktopRect.width, bottom: desktopRect.height }; } var destRect = {}; that.updateAppBar(gpii.windows.API_constants.ABM_SETPOS, { edge: gpii.windows.API_constants.ABE_BOTTOM, rc: rect }, destRect); that.events.onPositionChange.fire(); }; /** * Sets the window to be an app bar. * @param {Component} that The gpii.windows.appBar instance. */ gpii.windows.appBar.enable = function (that) { if (!that.enabled) { that.enabled = true; that.updateAppBar(gpii.windows.API_constants.ABM_NEW, {callbackMessage: that.WM_GPII_APPBAR}); gpii.windows.appBar.updatePosition(that); } }; /** * Stops the window from being an app bar. * @param {Component} that The gpii.windows.appBar instance. */ gpii.windows.appBar.disable = function (that) { if (that.enabled) { that.enabled = false; that.updateAppBar(gpii.windows.API_constants.ABM_REMOVE); } }; /** * Gets the position of the taskbar. * * @param {Rectangle} rect [optional] The bounding rectangle of the taskbar. * @return {String} "left", "top", "right", or "bottom". */ gpii.windows.appBar.getTaskbarPos = function (rect) { if (!rect) { rect = gpii.windows.getWindowRect(gpii.windows.getTasktrayWindow()); } var togo; if (rect.top > 0) { togo = "bottom"; } else if (rect.left > 0) { togo = "right"; } else { var desktop = gpii.windows.display.getDesktopSize(); if (rect.width < desktop.width) { togo = "left"; } else { togo = "top"; } } return togo; }; /** * Gets the lowest part of the screen. * @return {Number} The top of the taskbar (if it's at the bottom of the screen), or the bottom of the screen. */ gpii.windows.appBar.getWorkAreaBottom = function () { var bottom; var taskRect = gpii.windows.getWindowRect(gpii.windows.getTasktrayWindow()); var taskPos = gpii.windows.appBar.getTaskbarPos(taskRect); if (taskPos === "bottom") { bottom = taskRect.top; } else { var desktop = gpii.windows.display.getDesktopSize(); bottom = desktop.height; } return bottom; };