/* eslint-disable camelcase */ import os from "os"; import ffi from "@inigolabs/ffi-napi"; import ref from "@inigolabs/ref-napi"; import StructType from "ref-struct-di"; import { app, screen, ipcMain } from "electron"; import _ from "lodash"; import setWindowTransparency from "./winapi/setWindowLong"; import { findExactWindow } from "./winapi/getWindowData"; const Struct = StructType(ref); const is64bit = os.arch() === "x64"; const isDev = process.env.NODE_ENV === "development"; const isTransparentWinbar = process.env.TRANSPARENT_WINBAR; let metricLock = false; const ABM_NEW = 0; const ABM_REMOVE = 1; const ABM_QUERYPOS = 2; const ABM_SETPOS = 3; const ABM_GETSTATE = 4; const ABM_GETTASKBARPOS = 5; const ABM_SETSTATE = 10; const ABS_AUTOHIDE = 1; const ABS_ALWAYSONTOP = 2; const ABEdgeLeft = 0; const ABEdgeTop = 1; const ABEdgeRight = 2; const ABEdgeBottom = 3; const ABEdgeDev = 4; const RECT_Struct = Struct({ left: ref.types.long, // LONG: 32-bit signed integer, in twos-complement format (–2147483648 - 2147483647 decimal). top: ref.types.long, // LONG right: ref.types.long, // LONG bottom: ref.types.long, // LONG }); // Total size must be 48 for x64 and 36 for x86 const APPBARDATA_Struct = Struct({ cbSize: ref.types.uint32, // DWORD: 32-bit unsigned integer (range: 0 through 4294967295 decimal) hWnd: is64bit ? ref.types.longlong : ref.types.long, // HWND: unsigned integers uCallbackMessage: ref.types.uint32, // UINT: 32-bit unsigned integer (range: 0 through 4294967295 decimal) uEdge: ref.types.uint32, // UINT rc: RECT_Struct, // RECT lParam: is64bit ? ref.types.uint64 : ref.types.uint32, // LPARAM }); const appbar = new APPBARDATA_Struct(); appbar.cbSize = APPBARDATA_Struct.size; export const shell32 = ffi.Library("shell32.dll", { SHAppBarMessage: ["bool", ["int", "pointer"]], }); export const user32 = ffi.Library("user32.dll", { RegisterWindowMessageA: ["long", ["string"]], GetWindowRect: ["bool", ["long", "pointer"]], MoveWindow: ["bool", ["long", "int", "int", "int", "int", "bool"]], }); const hwndBufferToInt = (hbuf) => { if (os.endianness() === "LE") return hbuf.readInt32LE(); return hbuf.readInt32BE(); }; const showWinAppbar = () => { const data = new APPBARDATA_Struct(); data.cbSize = APPBARDATA_Struct.size; data.lParam = ABS_ALWAYSONTOP; shell32.SHAppBarMessage(ABM_SETSTATE, data.ref()); if (isTransparentWinbar === "true") { const hwnd = findExactWindow("Shell_TrayWnd", null); setWindowTransparency(hwnd, 255); } }; const hideWinAppbar = () => { const data = new APPBARDATA_Struct(); data.cbSize = APPBARDATA_Struct.size; data.lParam = ABS_AUTOHIDE; shell32.SHAppBarMessage(ABM_SETSTATE, data.ref()); if (isTransparentWinbar === "true") { const hwnd = findExactWindow("Shell_TrayWnd", null); setWindowTransparency(hwnd, 0); } }; const isHiddenWinAppbar = () => { const data = new APPBARDATA_Struct(); data.cbSize = APPBARDATA_Struct.size; return shell32.SHAppBarMessage(ABM_GETSTATE, data.ref()); }; // Rehide winappbar used to reset appbar area when app closed without call [deregisterAppBar] // on kill/crash to prevent incorrect positioning on start. const rehideWinAppbar = () => { const isHidden = isHiddenWinAppbar(); if (isHidden) { showWinAppbar(); hideWinAppbar(); } else { hideWinAppbar(); } }; const setWinAppbar = (option = "hide") => { const isHidden = isHiddenWinAppbar(); if (option === "hide" && !isHidden) { hideWinAppbar(); } if (option === "show" && isHidden) { showWinAppbar(); } }; const setPos = (window, edge = ABEdgeBottom) => { const hwnd = hwndBufferToInt(window.getNativeWindowHandle()); let { width: screenW, height: screenH } = screen.getPrimaryDisplay().size; const dpi = screen.getPrimaryDisplay().scaleFactor; let height = 80 * dpi; if (screenH <= 1024) { height = 70 * dpi; } let width = screenW; appbar.hWnd = hwnd; appbar.uEdge = edge; const winTaskbar = new APPBARDATA_Struct(); winTaskbar.cbSize = APPBARDATA_Struct.size; shell32.SHAppBarMessage(ABM_GETTASKBARPOS, winTaskbar.ref()); const isHidden = shell32.SHAppBarMessage(ABM_GETSTATE, winTaskbar.ref()); const winTaskbarHeight = isHidden ? 0 : winTaskbar.rc.bottom - winTaskbar.rc.top; screenW *= dpi; screenH *= dpi; width *= dpi; if (edge === ABEdgeLeft || edge === ABEdgeRight) { appbar.rc.top = 0; appbar.rc.bottom = screenH; if (edge === ABEdgeLeft) { appbar.rc.left = 0; appbar.rc.right = width; } else { appbar.rc.left = screenW - width; appbar.rc.right = screenW; } } else { appbar.rc.left = 0; appbar.rc.right = screenW; if (edge === ABEdgeTop) { appbar.rc.top = 0; appbar.rc.bottom = height; } else { appbar.rc.top = screenH - height; appbar.rc.bottom = screenH; } } if (edge === ABEdgeDev) { appbar.rc.left = 0; appbar.rc.right = screenW; appbar.rc.top = screenH - height - winTaskbarHeight - 2; appbar.rc.bottom = screenH - winTaskbarHeight - 2; } else { appbar.hWnd = hwndBufferToInt(window.getNativeWindowHandle()); appbar.uCallbackMessage = user32.RegisterWindowMessageA("CustomTaskbarId"); shell32.SHAppBarMessage(ABM_NEW, appbar.ref()); shell32.SHAppBarMessage(ABM_QUERYPOS, appbar.ref()); shell32.SHAppBarMessage(ABM_SETPOS, appbar.ref()); } // This is done async, because windows will send a resize after a new appbar is added. setTimeout(() => { user32.MoveWindow( hwnd, appbar.rc.left, appbar.rc.top, appbar.rc.right - appbar.rc.left, appbar.rc.bottom - appbar.rc.top + winTaskbarHeight, false ); window.show(); }, 1000); }; export function deregisterAppBar() { shell32.SHAppBarMessage(ABM_REMOVE, appbar.ref()); setWinAppbar("show"); } export function registerAppBar(window) { ipcMain.on("dev-quit", () => app.quit()); const isDevPosition = process.env.DEV_POSITION; if (isDev && isDevPosition === "true") { setPos(window, ABEdgeDev); return; } rehideWinAppbar(); // Delay need after rehide, see [rehideWinAppbar] comments setTimeout(() => { setWinAppbar("hide"); setPos(window); }, 1000); screen.on("display-metrics-changed", (_event, _display, changedMetrics) => { // metric lock prevent infinity workArea change loop on win7 if ( !window.isDestroyed() && !metricLock && (_.includes(changedMetrics, "bounds") || _.includes(changedMetrics, "workArea")) ) { metricLock = true; shell32.SHAppBarMessage(ABM_REMOVE, appbar.ref()); setPos(window); setTimeout(() => { metricLock = false; }, 1000); } }); app.on("quit", () => deregisterAppBar()); // on [dev-quit] app.on("before-quit", () => deregisterAppBar()); // on electron-updater quit }