ENIGMA Forums

Contributing to ENIGMA => Function Peer Review => Topic started by: time-killer-games on May 24, 2022, 06:56:06 am

Title: Embed Windows
Post by: time-killer-games on May 24, 2022, 06:56:06 am
I wrote a function for Windows and Xlib platforms which works in ENIGMA. It should also work with SDL minus Mac and Android.

You can use execute_shell() or execute_program() to have a launcher executable launch a game executable. The launcher could serve as a "choose a game" menu or even an updater application.

You can save window_identifier() since it is already a string and make it an environment variable which gets passed down to the child. The child can then plug in the environment variable it inherited as the parentWindowId and its own window_identifier() can be the child windowId parameter passed to these two functions:

Code: [Select]
// embed window identifier into parent window identifier (called once)
void WindowIdSetParentWindowId(wid_t windowId, wid_t parentWindowId) {
  #if defined(_WIN32)
  HWND child  = (HWND)(void *)(uintptr_t)strtoull(windowId.c_str(), nullptr, 10);
  HWND parent = (HWND)(void *)(uintptr_t)strtoull(parentWindowId.c_str(), nullptr, 10);
  RECT wrect; GetWindowRect(parent, &wrect); RECT crect; GetClientRect(parent, &crect);
  POINT lefttop     = { crect.left,  crect.top    }; ClientToScreen(parent, &lefttop);
  POINT bottomright = { crect.right, crect.bottom }; ClientToScreen(parent, &bottomright);
  MoveWindow(child, -(lefttop.x - wrect.left), -(lefttop.y - wrect.top), crect.right + bottomright.x, crect.bottom + bottomright.y, true);
  SetWindowLongPtr(child, GWL_STYLE, GetWindowLongPtr(child, GWL_STYLE) & ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZE | WS_MAXIMIZE | WS_SYSMENU | WS_POPUP | WS_SIZEBOX) | WS_CHILD);
  SetWindowLongPtr(child, GWL_EXSTYLE, GetWindowLongPtr(child, GWL_EXSTYLE) | WS_EX_TOOLWINDOW);
  SetWindowLongPtr(parent, GWL_STYLE, GetWindowLongPtr(parent, GWL_STYLE) | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
  SetParent(child, parent); ShowWindow(child, SW_MAXIMIZE);
  #elif (defined(__linux__) && !defined(__ANDROID__)) || (defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__OpenBSD__))
  Window child  = (Window)(uintptr_t)strtoull(windowId.c_str(), nullptr, 10);
  Window parent = (Window)(uintptr_t)strtoull(parentWindowId.c_str(), nullptr, 10);
  Display *display = XOpenDisplay(nullptr); XMapWindow(display, child);
  Window root = 0, rparent = 0, *children = nullptr; unsigned int nchildren = 0;
  XQueryTree(display, child, &root, &rparent, &children, &nchildren);
  XReparentWindow(display, child, parent, 0, 0); if (nchildren > 0) { XFree(children); }
  XFlush(display); std::this_thread::sleep_for(std::chrono::milliseconds(5));
  while (parent != rparent) {
    XSynchronize(display, true);
    XQueryTree(display, child, &root, &rparent, &children, &nchildren);
    XReparentWindow(display, child, parent, 0, 0); if (nchildren > 0) { XFree(children); }
    XFlush(display); std::this_thread::sleep_for(std::chrono::milliseconds(5));
  }
  XCloseDisplay(display);
  #else
  DEBUG_MESSAGE("Unsupported platform for function!", MESSAGE_TYPE::M_INFO);
  #endif
}

// update embed area of window identifier to fill parent window identifier client area
// (must be called every step once a window is embedded inside another to function)
void WindowIdFillParentWindowId(wid_t windowId, wid_t parentWindowId) {
  if (strtoull(parentWindowId.c_str(), nullptr, 10) == 0) return;
  #if defined(_WIN32)
  HWND child  = (HWND)(void *)(uintptr_t)strtoull(windowId.c_str(), nullptr, 10);
  HWND parent = (HWND)(void *)(uintptr_t)strtoull(parentWindowId.c_str(), nullptr, 10);
  RECT crect; GetClientRect(parent, &crect);
  MoveWindow(child, 0, 0, crect.right, crect.bottom, true);
  SetWindowLongPtr(child, GWL_STYLE, GetWindowLongPtr(child, GWL_STYLE) & ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZE | WS_MAXIMIZE | WS_SYSMENU | WS_POPUP | WS_SIZEBOX) | WS_CHILD);
  SetWindowLongPtr(child, GWL_EXSTYLE, GetWindowLongPtr(child, GWL_EXSTYLE) | WS_EX_TOOLWINDOW);
  SetWindowLongPtr(parent, GWL_STYLE, GetWindowLongPtr(parent, GWL_STYLE) | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
  #elif (defined(__linux__) && !defined(__ANDROID__)) || (defined(__FreeBSD__) || defined(__DragonFly__) || defined(__NetBSD__) || defined(__OpenBSD__))
  Window child  = (Window)(uintptr_t)strtoull(windowId.c_str(), nullptr, 10);
  Window parent = (Window)(uintptr_t)strtoull(parentWindowId.c_str(), nullptr, 10);
  Display *display = XOpenDisplay(nullptr); XMapWindow(display, child);
  Window r = 0; int x = 0, y = 0; unsigned w = 0, h = 0, b = 0, d = 0;
  XGetGeometry(display, parent, &r, &x, &y, &w, &h, &b, &d);
  XMoveResizeWindow(display, child, 0, 0, w, h);
  XCloseDisplay(display);
  #else
  DEBUG_MESSAGE("Unsupported platform for function!", MESSAGE_TYPE::M_INFO);
  #endif
}

It's relatively seamless, especially on Windows. It will embed the game window to fill the launcher's client area. I have a means to close the child process when the launcher exits so you don't have the game continuing to run in the background. You can borrow code from my repository to have that automated in enigma internally so you don't risk people writing stuff wrong and terminating the wrong process id which can be avoided if you let enigma handle that for them. You can also make the step event code fire every step automatically too if you want, that will save the user from having to call it manually.

Please note embedding windows is a very popular extension feature that a lot people want on the GMC and have wanted in their projects ever since even pre-GM6 era to the present.  Over time various existing DLL's either lost their compatibility with the newer versions of GM, links to download them went dead, or the solution was closed source and not written with cross-platform in mind. I was paid $275 USD to write the above code, to put it into perspective, they knew it wasn't much work to write, but insisted to pay me that much because that's how much they wanted it. I think you would make a lot of your users happy for adding this.