I need to control main window z-order so to say. Always on top is not my case. I want to put my window behind all others and above desktop. Is it possible? Is there analogue to C++ SetWindowPos function? Or maybe some workaround?
If it's not something you can accomplish with parent/child windows then you could call SetWindowPos()
via node-ffi
(or write a native Node module/addon). To get the HWND
for a BrowserWindow
call getNativeWindowHandle()
. I have no idea how you'd do this natively on macOS or Ubuntu.
First you should send the window to the back/bottom of the z-index stack, then disable z-index changing for the window.
I tried getting this working in my project, and eventually succeeded, as shown below.
However, note that some of my code comments might not be accurate. I don't understand node-ffi very well, and only got it working through trial and error; my code comments are just my attempt at trying to "make sense" of the behavior I observed.
import ffi from "ffi";
import ref from "ref";
import Struct from "ref-struct";
const HWND_BOTTOM = 1;
export let SetWindowPos_Flags = {
NOSIZE: 0x0001,
NOMOVE: 0x0002,
NOACTIVATE: 0x0010,
}
export var user32 = new ffi.Library("user32", {
// return, handle, handleInsertAfter, x, y, cx, cy, flags
SetWindowPos: ["bool", ["int32", "int32", "int32", "int32", "int32", "int32", "uint32"]],
});
export function SendWindowToBack(handle: number) {
user32.SetWindowPos(handle, HWND_BOTTOM, 0, 0, 0, 0, SetWindowPos_Flags.NOMOVE | SetWindowPos_Flags.NOSIZE | SetWindowPos_Flags.NOACTIVATE);
}
export const WM_WINDOWPOSCHANGING = 0x0046;
export const SWP_NOZORDER = 0x0004;
let WINDOWPOS = Struct({
hwndInsertAfter: ffi.types.int32,
hwnd: ffi.types.int32,
x: ffi.types.int32,
y: ffi.types.int32,
cx: ffi.types.int32,
cy: ffi.types.int32,
flags: ffi.types.uint32,
});
export function AddHook(window: Electron.BrowserWindow, disableZIndexChanging = false) {
window.hookWindowMessage(WM_WINDOWPOSCHANGING, (wParam: Buffer, lParam: Buffer)=> {
// The "lParam" buffer holds the address to a place in unmanaged memory, which itself holds the address to the actual (unmanaged) struct-data.
// Thus: js-pseudo-pointer (8-bytes; the lParam Buffer) -> c-pointer (8-bytes, unmanaged) -> struct-data (28-bytes, unmanaged)
// However, the lParam buffer does not realize it is/could-be a "pseudo-pointer" -- all it knows is that it's holding some random numbers.
// To access the unmanaged struct-data, we have to tell the js-side that the contents of lParam are actually the address explained above.
// Then we'll be able to use the Buffer.deref() function to correctly obtain the final struct-data.
// To do this, we:
// 1) Create a new js-pseudo-pointer (using the modified Buffer class), whose contents/what-it-points-to is marked as of type "pointer to a pointer" (ie. the c-pointer entry above)
let lParam2 = Buffer.alloc(8);
lParam2["type"] = ref.refType(WINDOWPOS);
// 2) Fill the js-pseudo-pointer with the actual address to that c-pointer (just copy this address from the unmarked lParam Buffer)
lParam.copy(lParam2);
// 3) Dereference our js-pseudo-pointer, retrieving the actual struct-data bytes
let actualStructDataBuffer = lParam2["deref"]() as Buffer;
// 4) Convert the struct-data bytes into a regular JavaScript object
let windowPos = actualStructDataBuffer["deref"]() as {hwndInsertAfter: number, hwnd: number, x: number, y: number, cx: number, cy: number, flags: number};
// 5) Modify the 7th integer (the flags field) in the (unmanaged) struct-data, to include the new SWP_NOZORDER flag
if (disableZIndexChanging) {
//lParam.flags |= SWP_NOZORDER;
let newFlags = windowPos.flags | SWP_NOZORDER;
actualStructDataBuffer.writeUInt32LE(newFlags, 6);
}
});
}
// This is the function you actually call from the rest of your program.
export function SendElectronWindowToBackAndKeepItThere(window: Electron.BrowserWindow) {
let handleAsBuffer = window.getNativeWindowHandle();
let handleAsNumber = ref.types.int64.get(handleAsBuffer, 0);
SendWindowToBack(handleAsNumber);
AddHook(window, true);
}
ffi-napi
, ref-napi
, ref-struct-napi
that support nodejs 12 (which latest electron versions use). –
Glycine ffi
(and such) repos seem to have been abandoned by the author for the last couple years (which is odd since he's still active on Github!); ffi-napi
has also worked well for me as a replacement. –
Medan If it's not something you can accomplish with parent/child windows then you could call SetWindowPos()
via node-ffi
(or write a native Node module/addon). To get the HWND
for a BrowserWindow
call getNativeWindowHandle()
. I have no idea how you'd do this natively on macOS or Ubuntu.
You can call .blur()
on the window. It will make it go below other windows.
BrowserWindow.blur()
is needed to maintain good UX in a lot of cases when launching native OS windows or other programs which for some reason open behind the Electron app window, for example when using showItemInFolder()
. –
Amoeboid © 2022 - 2024 — McMap. All rights reserved.