Attaching a glfw window to an electron handle freezes the electron window - c++

I'm trying to attach a glfw window to an electron one.
I've succesfully retrieved the electron window handle (electronWindow.getNativeWindowHandle()) in my glfw app, and then used win32 api to try and attach them to one another :
GLFWwindow* createWindow(HWND parentWindow)
{
// Create an invisible window
fprintf(stdout, "Creating window as child of window 0x%016x\n", parentWindow);
glfwDefaultWindowHints();
glfwWindowHint(GLFW_RESIZABLE, FALSE);
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
glfwWindowHint(GLFW_VISIBLE, GLFW_TRUE);
GLFWwindow* window = glfwCreateWindow(800, 600, "", nullptr, nullptr);
// That we attach as a child to an other one
HWND hw = glfwGetWin32Window(window);
HWND previousParent = SetParent(hw, parentWindow);
if (!previousParent) {
fprintf(stderr, "Couldn't set window parent: %s\n", lastWindowsError().c_str());
glfwDestroyWindow(window);
return nullptr;
}
ShowWindow(hw, SW_SHOW);
return window;
}
The SetParent call doesn't fail so I'm assuming the connection is made.
Then I have my (very classical) main glfw loop like this :
while (!glfwWindowShouldClose(window))
{
glClearColor(0.3, 0.4, 0.8, 1.0);
glClear(GL_COLOR_BUFFER_BIT);
glfwPollEvents();
glfwSwapBuffers(window);
}
So the "faulty" behavior I've got is the following :
The electron app freezes. Nothing moves, the process is "not responding"
My glfw window doesn't show up anywhere
I haven't done an win32 event handling on glfw side, could this be the issue ? Like some specific event I have to take care of ?
Is this an issue with the electron app not passing events to the glfw window, hence blocking everything ?
Note: I'm doing this because I want to display native opengl using a pre-existing engine we've got.
I've tried naively doing glfwSetWindowPos each time the electron window moves, but it's sluggish as hell (probably Windows preventing this behavior) so I'm trying other approaches (here : attaching the opengl window as a child of electron).

So it seems it's not a very good idea haha + I'm not that good with win32 api so I can't really tell what's not working (cf. #iinspectable comments)
I delved into this issue on electron's github : https://github.com/electron/electron/issues/10547 and found a way to display an OpenGL frame on top of an Electron window.
I circumvented the issue by creating the window as a child process of the electron app with the HWND as startup argument :
const { app, BrowserWindow } = require('electron')
const { endianness } = require('os')
const { spawn } = require('child_process')
const nativeExecutablePath = 'src/overlay/build/Release/kf-overlay.exe'
const indexPath = 'src/ui/html/index.html'
const startChildProcess = (hwnd, target) => {
const data = endianness() == 'LE'
? hwnd.readInt32LE()
: hwnd.readInt32BE()
const p = spawn(target, [data], { cwd: process.cwd() })
p.stdout.on('data', data => console.log(`[native] ${data}`))
p.stderr.on('data', data => console.log(`[native] ${data}`))
}
const createWindow = () => {
const win = new BrowserWindow({
width: 800,
height: 600
})
// win.loadFile(indexPath)
win.loadURL('https://www.google.com')
win.webContents.setFrameRate(60)
console.log(`Starting child process ${nativeExecutablePath}`)
const hwnd = win.getNativeWindowHandle()
startChildProcess(hwnd, nativeExecutablePath)
}
app.whenReady().then(() => {
createWindow()
})
Then creating the window as a child from C++ side :
HWND createChildWindow(HWND parent)
{
RECT rect;
GetWindowRect(parent, &rect);
int parentWidth = rect.right - rect.left;
int parentHeight = rect.bottom - rect.top;
DWORD style = WS_VISIBLE | WS_CHILD | WS_POPUP;
auto child = CreateWindowEx(
WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TRANSPARENT | WS_EX_COMPOSITED,
WINDOW_CLASS_NAME,
nullptr,
style,
parentWidth / 2 - 512,
parentHeight / 2 - 512,
512,
512,
parent,
nullptr,
nullptr,
nullptr
);
SetParent(child, parent);
return child;
}
Finally I could create the OpenGL context through wglCreateContext calls, and render a frame with OpenGL calls :)

Related

OpenGL Viewport On Drag Update (Winapi)

In my current project I am working on I would like to make the opengl viewport window resize from when the user resizes the drag window ("SizeBox"), I planned on using the windows "ENTERSIZEMOVE" function from the windows.h window api to get the signal to redraw the viewport.I would then call "UpdateRender" to update my viewport.
On running my program I only seem to get one update call to resize the window (even if I stop and start dragging again),I also have a cube in the scene which is rotating it is entered (0.0f,0.0f,10.of). I wanted the cube to move along with the window "resizing" instead of it getting covered over by the window,Since the cube is set to the centre of the viewport so if I am correct I should not need to move it via a gltransform.
If anyone has any thoughts on why I am not getting a constant viewport "refresh" from the window being dragged it would be much appreciated.
Message loop handler (snippet)
{ case WM_ENTERSIZEMOVE:
render->UpdateRender();
break;
}
Update Render
void Render::UpdateRender()
{
int update_x = rect.right - rect.left;
int update_y = rect.bottom - rect.top;
glViewport(0,0 ,update_y/2,update_x/2);
}
Create Window
int window::Wincreate(HINSTANCE hInstance, char *winname, int winwidth, int winheight)
{
Hwnd = CreateWindow("Game Engine", winname, WS_VISIBLE | WS_SYSMENU | WS_SIZEBOX | WS_MAXIMIZEBOX | WS_MAXIMIZE, 0, 0, winwidth , winheight , 0, 0, hInstance, 0);
if (!Hwnd)
{
MessageBox(0, "Error: Could not Create Window", "Error", MB_OK);
}
Engine->EnableOGLAPI();
Draw->Initalize();
return 0;
}
Main application Loop
int window::Winloop(MSG msg)
{
while (running)
{
if (PeekMessage(&msg,0,0,0,PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
angle++;
Draw->MainRender();
SwapBuffers(hDC);
}
}
return 0;
}

CreateWindowEx posts WM_SIZE?

CreateWindowEx API really posts WM_SIZE message?
When I create a window via CreateWindowEx as full screen mode,
CreateWindowEx posts WM_SIZE but window mode doesn't.
My code sets the window style like this :
if(bFullscr)
{
//When the window is in full screen mode.
nStyle = WS_POPUP;
nExtraStyle = WS_EX_APPWINDOW;
}
else
{
//Otherwise.
nStyle = WS_OVERLAPPEDWINDOW;
nExtraStyle = (WS_EX_APPWINDOW | WS_EX_WINDOWEDGE);
}
And changes display settings like this (full screen mode only) :
if(bFullscr)
{
DEVMODE sScrSet;
memset(&sScrSet, 0, sizeof(DEVMODE));
sScrSet.dmSize = sizeof(DEVMODE);
sScrSet.dmPelsWidth = nWidth;
sScrSet.dmPelsHeight = nHeight;
sScrSet.dmBitsPerPel = nColorBit;
sScrSet.dmFields = (DM_BITSPERPEL | DM_PELSHEIGHT | DM_PELSWIDTH);
if(ChangeDisplaySettings(&sScrSet, CDS_FULLSCREEN) != DISP_CHANGE_SUCCESSFUL)
{
//Error routine.
}
}
I'm really wonder why CreateWindowEx posts WM_SIZE message selectively.
If you simply want to resize the window, somewhere in your code you should have ShowWindow(hWnd, nCmdShow); change it as follows:
ShowWindow(hWnd, SW_SHOWDEFAULT);//show normal
ShowWindow(hWnd, SW_SHOWMAXIMIZED);//show maximized (full screen)
SetWindowPos(hWnd, NULL, 10, 10, 300, 300, SWP_SHOWWINDOW);//show at specific position
Also you could use WS_MAXIMIZE in CreateWindow, but that could complicate things. Window usually has WS_OVERLAPPEDWINDOW or WS_POPUP|WS_CAPTION|WS_SYSMENU. You should pick one and keep it simple.
When Window size changes, it receives WM_SIZE, you can catch that and examine it.

SetLayeredWindowAttributes with disabled Aero

I have a program that puts a transparent window on top of the desktop where a user can do some free hand drawing with the mouse. The program works fine as long as Aero is enabled but when Aero is disabled (on Windows 7) it fails - I cannot draw and the mouse doesn't change to the mouse shape I set for the window.
The code looks like this (MFC):
//=============================================================================
// PreCreateWindow
//-----------------------------------------------------------------------------
// Public function to create the window based on global properties
//=============================================================================
BOOL CDrawWnd::PreCreateWindow(CREATESTRUCT& cs)
{
HBRUSH hBgBrush = ::CreateSolidBrush(m_crBackgroundColor);
HCURSOR hcursor = NULL;
if (m_uiCursor)
hcursor = AfxGetApp()->LoadCursor(m_uiCursor);
else if (m_Cursor)
hcursor = AfxGetApp()->LoadStandardCursor(m_Cursor); // By default IDC_CROSS
try
{
cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW, hcursor, hBgBrush);
}
catch(.../*CResourceException ex*/)
{
/*ex.ReportError();*/
MessageBox(_T("AfxRegisterWndClass failed"));
}
return CWnd::PreCreateWindow(cs);
}
//=============================================================================
// CreateWnd
//-----------------------------------------------------------------------------
// Public function to create the window based on global properties
//=============================================================================
bool CDrawWnd::CreateWnd()
{
CreateEx(WS_EX_LAYERED | WS_EX_TOPMOST | WS_EX_TOOLWINDOW, _T("WsmLayeredWindowClass"), _T("Layered Draw Window"), WS_POPUP, m_StartWndRect, NULL, NULL, NULL);
if (m_hWnd)
{
// Make this window transparent
if (!SetLayeredWindowAttributes(m_crBackgroundColor, (255 * m_Transparency) / 100, LWA_COLORKEY))
{
TRACE(_T("\nSetLayeredWindowAttributes failed: 0x%lX"), GetLastError());
}
ShowWindow(SW_SHOW);
GetWindowRect(&m_rDrawingSurface);
return true;
}
return false;
}
CDrawWnd is derived from CWnd. CDrawWnd::CreateWnd() is called to create the window. In the case it doesn't work
SetLayeredWindowAttributes(m_crBackgroundColor, (255 * m_Transparency) / 100, LWA_COLORKEY)
fails even though GetLastError() returns 0.

Clearing the screen after switching away from fullscreen mode?

So I've got an OpenGL application running that can toggle between fullscreen mode and windowed mode. It currently does this by resizing the window and changing styles.
However, it seems to not invalidate the screen when switching from fullscreen mode to windowed mode, which leaves things I've drawn lingering onscreen after the switch.
Interestingly, it only exhibits this behavior in single monitor mode. If I'm running with multiple monitors, it invalidates okay, and clears my drawing.
I don't think this is a driver bug, as it's happening on two separate computers using two separate video cards(although admittedly they are both nVidia.), I'm thinking I'm doing something wrong somewhere.
I've tried a bunch of methods for getting Windows to clear the screen of my previously fullscreen drawings, but nothing seems to work. InvalidateRect(), RedrawWindow(), ChangeDisplaySettings()...
Specifically:
InvalidateRect(m_hwnd, &rectx, true); // rect being the dimensions of either the screen or my window.
InvalidateRect(HWND_DESKTOP, NULL, TRUE); // Doesn't seem to do anything.
RedrawWindow(NULL, NULL, NULL, RDW_INVALIDATE | RDW_ALLCHILDREN | RDW_UPDATENOW);
ChangeDisplaySettings(NULL, 0);
Well, actually, one thing that does seem to work is ShowWindow(hwnd, SW_HIDE) before resizing. However that loses focus for a moment, allowing other applications to grab my application's input, and seems a bad way to go about it.
I should note that I'm not doing any actual display mode changes when I'm seeing this behavior; just staying at the current resolution for fullscreen.
I'm a bit clueless where I'm going wrong. Simplified code:
if(m_isFullscreen)
{
ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN);
}
else
{
ChangeDisplaySettings(&m_dmSavedScreenSettings, 0);
}
if(m_isFullscreen)
{
dwExStyle = WS_EX_APPWINDOW;
dwStyle = WS_POPUP;
ShowCursor(false);
}
else
{
dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
dwStyle = WS_OVERLAPPEDWINDOW;
if(m_isRunning) // Because ShowCursor uses a counter to indicate, and windowed mode defaults to cursor on, we don't need to increment the counter and double it being on.
{
ShowCursor(true);
}
}
RECT rect;
rect.left = 0;
rect.top = 0;
if(m_isFullscreen) { rect.right = 1280; } else { rect.right = 640; }
if(m_isFullscreen) { rect.bottom = 1024; } else { rect.bottom = 480; }
AdjustWindowRectEx(&rect, dwStyle, false, dwExStyle);
SetWindowLongPtr(m_hwnd, GWL_STYLE, dwStyle | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
SetWindowLongPtr(m_hwnd, GWL_EXSTYLE, dwExStyle);
if(m_isFullscreen)
{
MoveWindow(m_hwnd, 0, 0, 1280, 1024, true);
}
else
{
MoveWindow(m_hwnd, 0, 0, 640, 480, true); // windowed
}
And that's more or less it. Some other supporting code and error checking, but that's what I'm doing... dmSavedSettings is saved before m_hwnd is assigned from NULL, and not afterwards. My initial window creation works fine, and fullscreen works fine. It's just returning to Windowed after being fullscreen that's the issue.
If you set a null background brush in your window class, windows will not be cleared automatically. You must add a WM_PAINT handler that calls your OpenGL display handler, which in turn clears the viewport (glClearColor) and redraws.
As datenwolf mentions in another answer's comment, you want to use SetWindowPos() instead of MoveWindow() when making use of SetWindowLongPtr().
My dirty background problems were solved by calling ChangeDisplaySettings(NULL, 0) AFTER resizing my window. Doing it before does little, but afterwards appears to work fine.

BringWindowToTop is Not working even if I get the handle to Class Window

I am registering my Class in the following method:
BOOL CNDSClientDlg::InitInstance()
{
//Register Window Updated on 16th Nov 2010, #Subhen
// Register our unique class name that we wish to use
WNDCLASS wndcls;
memset(&wndcls, 0, sizeof(WNDCLASS));
wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndcls.lpfnWndProc = ::DefWindowProc;
wndcls.hInstance = AfxGetInstanceHandle();
wndcls.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wndcls.lpszMenuName = NULL;
//Class name for using FindWindow later
wndcls.lpszClassName = _T("CNDSClientDlg");
// Register new class and exit if it fails
if(!AfxRegisterClass(&wndcls)) // [C]
{
return FALSE;
}
}
and then calling the InitInstance method and creating the window in constructor of the Class:
CNDSClientDlg::CNDSClientDlg(CWnd* pParent /*=NULL*/)
: CDialog(CNDSClientDlg::IDD, pParent)
{
InitInstance();
HWND hWnd;
hInst = AfxGetInstanceHandle(); // Store instance handle in our global variable
hWnd = CreateWindow(_T("CNDSClientDlg"), "NDS", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL);
}
Now in my other application I am finding the window and trying to bring to top:
Edit
Able to bring newlyCreated Windows with below code
CWnd *pWndPrev = NULL;
CWnd *FirstChildhWnd = NULL;
pWndPrev = CWnd::FindWindow(_T("CNDSClientDlg"),NULL);
if(pWndPrev != NULL)
{
//pWndPrev->BringWindowToTop();
WINDOWPLACEMENT wndplacement;
pWndPrev->GetWindowPlacement(&wndplacement);
wndplacement.showCmd = SW_RESTORE;
pWndPrev->SetWindowPlacement(&wndplacement);
pWndPrev->SetForegroundWindow();
FirstChildhWnd = pWndPrev->GetLastActivePopup();
if (pWndPrev != FirstChildhWnd)
{
// a pop-up window is active, bring it to the top too
FirstChildhWnd->GetWindowPlacement(&wndplacement);
wndplacement.showCmd = SW_RESTORE;
FirstChildhWnd->SetWindowPlacement(&wndplacement);
FirstChildhWnd->SetForegroundWindow();
}
I am able to find the window as pWndPrev is not NULL , but It is not bringing up my application to front. Do I need to register any other class Instead of CNDSClientDlg. I want to bring my MFC application to top.
A few things to look at...
1) Try SetForegroundWindow() instead of BringWindowToTop(). It's been awhile since I've done Win32 programming, but I seem to recall that BringWindowToTop() has some limitations (especially when working with windows in different processes).
2) There are some rules that Microsoft put in place regarding SetForegroundWindow() starting with Windows 2000. The short version is that only the front-most application can change the foreground window. The idea is that an application that is not front-most cannot "jump in front of" the active application. If a background application calls SetForegroundWindow(), Windows will flash the taskbar button for the app, but will not actually bring the app to the front. The user must do that. I'm oversimplifying the rules, but this may be something to look at depending on your specific scenario.
BringWindowToTop() only works if the calling process is the foreground process or if it received the last input event.
Call CWnd::SetForegroundWindow() instead.
You may need to call AllowSetForegroundWindow in your "other" application before calling SetForegroundWindow.
That is assuming your other application is the foreground app and is trying to pass on its foreground status to the application with the window.
If neither app is the foreground app then you're not supposed to be able to bring a window to the front, although there are ways to do it (both accidentally and on purpose).
SetWindowPos(&wndTopMost, -1, -1, -1, -1, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
SetForegroundWindow();