// Some code may be inspired by or directly used from Webview (c) zserge. // License included in README.md #include "ffenestri_windows.h" #include "wv2ComHandler_windows.h" #include #include #include #include #include #include "windows/WebView2.h" #include "effectstructs_windows.h" #include int debug = 0; DWORD mainThread; // --- Assets extern const unsigned char runtime; extern const unsigned char *defaultDialogIcons[]; // dispatch will execute the given `func` pointer void dispatch(dispatchFunction func) { PostThreadMessage(mainThread, WM_APP, 0, (LPARAM) new dispatchFunction(func)); } LPWSTR cstrToLPWSTR(const char *cstr) { int wchars_num = MultiByteToWideChar( CP_UTF8 , 0 , cstr , -1, NULL , 0 ); wchar_t* wstr = new wchar_t[wchars_num+1]; MultiByteToWideChar( CP_UTF8 , 0 , cstr , -1, wstr , wchars_num ); return wstr; } // Credit: https://stackoverflow.com/a/9842450 char* LPWSTRToCstr(LPWSTR input) { int length = WideCharToMultiByte(CP_UTF8, 0, input, -1, 0, 0, NULL, NULL); char* output = new char[length]; WideCharToMultiByte(CP_UTF8, 0, input, -1, output , length, NULL, NULL); return output; } struct Application *NewApplication(const char *title, int width, int height, int resizable, int devtools, int fullscreen, int startHidden, int logLevel, int hideWindowOnClose) { // Create application struct Application *result = (struct Application*)malloc(sizeof(struct Application)); result->window = nullptr; result->webview = nullptr; result->webviewController = nullptr; result->title = title; result->width = width; result->height = height; result->resizable = resizable; result->devtools = devtools; result->fullscreen = fullscreen; result->startHidden = startHidden; result->logLevel = logLevel; result->hideWindowOnClose = hideWindowOnClose; result->webviewIsTranparent = false; result->windowBackgroundIsTranslucent = false; // Min/Max Width/Height result->minWidth = 0; result->minHeight = 0; result->maxWidth = 0; result->maxHeight = 0; // Default colour result->backgroundColour.R = 255; result->backgroundColour.G = 255; result->backgroundColour.B = 255; result->backgroundColour.A = 255; // Have a frame by default result->frame = 1; // Capture Main Thread mainThread = GetCurrentThreadId(); // Startup url result->startupURL = nullptr; return result; } void SetMinWindowSize(struct Application* app, int minWidth, int minHeight) { app->minWidth = (LONG)minWidth; app->minHeight = (LONG)minHeight; } void SetMaxWindowSize(struct Application* app, int maxWidth, int maxHeight) { app->maxWidth = (LONG)maxWidth; app->maxHeight = (LONG)maxHeight; } void SetBindings(struct Application *app, const char *bindings) { std::string temp = std::string("window.wailsbindings = \"") + std::string(bindings) + std::string("\";"); app->bindings = new char[temp.length()+1]; memcpy(app->bindings, temp.c_str(), temp.length()+1); } void performShutdown(struct Application *app) { if( app->startupURL != nullptr ) { delete[] app->startupURL; } messageFromWindowCallback("WC"); } void enableTranslucentBackground(struct Application *app) { HMODULE hUser = GetModuleHandleA("user32.dll"); if (hUser) { pfnSetWindowCompositionAttribute setWindowCompositionAttribute = (pfnSetWindowCompositionAttribute)GetProcAddress(hUser, "SetWindowCompositionAttribute"); if (setWindowCompositionAttribute) { ACCENT_POLICY accent = { ACCENT_ENABLE_BLURBEHIND, 0, 0, 0 }; WINDOWCOMPOSITIONATTRIBDATA data; data.Attrib = WCA_ACCENT_POLICY; data.pvData = &accent; data.cbData = sizeof(accent); setWindowCompositionAttribute(app->window, &data); } } } LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { struct Application *app = (struct Application *)GetWindowLongPtr(hwnd, GWLP_USERDATA); switch(msg) { case WM_DESTROY: { DestroyApplication(app); break; } case WM_SIZE: { if( app->webviewController != nullptr) { RECT bounds; GetClientRect(app->window, &bounds); app->webviewController->put_Bounds(bounds); } break; } case WM_GETMINMAXINFO: { // Exit early if this is called before the window is created. if ( app == NULL ) { return 0; } // get pixel density HDC hDC = GetDC(NULL); double DPIScaleX = GetDeviceCaps(hDC, 88)/96.0; double DPIScaleY = GetDeviceCaps(hDC, 90)/96.0; ReleaseDC(NULL, hDC); RECT rcClient, rcWind; POINT ptDiff; GetClientRect(hwnd, &rcClient); GetWindowRect(hwnd, &rcWind); int widthExtra = (rcWind.right - rcWind.left) - rcClient.right; int heightExtra = (rcWind.bottom - rcWind.top) - rcClient.bottom; LPMINMAXINFO mmi = (LPMINMAXINFO) lParam; if (app->minWidth > 0 && app->minHeight > 0) { mmi->ptMinTrackSize.x = app->minWidth * DPIScaleX + widthExtra; mmi->ptMinTrackSize.y = app->minHeight * DPIScaleY + heightExtra; } if (app->maxWidth > 0 && app->maxHeight > 0) { mmi->ptMaxSize.x = app->maxWidth * DPIScaleX + widthExtra; mmi->ptMaxSize.y = app->maxHeight * DPIScaleY + heightExtra; mmi->ptMaxTrackSize.x = app->maxWidth * DPIScaleX + widthExtra; mmi->ptMaxTrackSize.y = app->maxHeight * DPIScaleY + heightExtra; } return 0; } default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; } void init(struct Application *app, const char* js) { LPCWSTR wjs = cstrToLPWSTR(js); app->webview->AddScriptToExecuteOnDocumentCreated(wjs, nullptr); delete[] wjs; } void execJS(struct Application* app, const char *script) { LPWSTR s = cstrToLPWSTR(script); app->webview->ExecuteScript(s, nullptr); delete[] s; } void loadAssets(struct Application* app) { // patch window.external.invoke std::string initialCode = std::string("window.external={invoke:s=>window.chrome.webview.postMessage(s)};"); // Load bindings initialCode += std::string(app->bindings); delete[] app->bindings; // Load runtime initialCode += std::string((const char*)&runtime); int index = 1; while(1) { // Get next asset pointer const unsigned char *asset = assets[index]; // If we have no more assets, break if (asset == 0x00) { break; } initialCode += std::string((const char*)asset); index++; }; // Disable context menu if not in debug mode if( debug != 1 ) { initialCode += std::string("wails._.DisableDefaultContextMenu();"); } initialCode += std::string("window.external.invoke('completed');"); // Keep a copy of the code app->initialCode = new char[initialCode.length()+1]; memcpy(app->initialCode, initialCode.c_str(), initialCode.length()+1); execJS(app, app->initialCode); // Show app if we need to if( app->startHidden == false ) { Show(app); } } // This is called when all our assets are loaded into the DOM void completed(struct Application* app) { delete[] app->initialCode; app->initialCode = nullptr; if( app->startupURL == nullptr ) { messageFromWindowCallback("SS"); return; } std::string readyMessage = std::string("SS") + std::string(app->startupURL); messageFromWindowCallback(readyMessage.c_str()); } // bool initWebView2(struct Application *app, int debugEnabled, messageCallback cb) { debug = debugEnabled; CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); std::atomic_flag flag = ATOMIC_FLAG_INIT; flag.test_and_set(); // char currentExePath[MAX_PATH]; // GetModuleFileNameA(NULL, currentExePath, MAX_PATH); // char *currentExeName = PathFindFileNameA(currentExePath); // std::wstring_convert> wideCharConverter; // auto exeName = wideCharConverter.from_bytes(currentExeName); // // PWSTR path; // HRESULT appDataResult = SHGetFolderPathAndSubDir(app->window, CSIDL_LOCAL_APPDATA, nullptr, SHGFP_TYPE_CURRENT, exeName.c_str(), path); // if ( appDataResult == false ) { // path = nullptr; // } // ICoreWebView2Controller *controller; ICoreWebView2* webview; HRESULT res = CreateCoreWebView2EnvironmentWithOptions( nullptr, nullptr, nullptr, new wv2ComHandler(app, app->window, cb, [&](ICoreWebView2Controller *webviewController) { controller = webviewController; controller->get_CoreWebView2(&webview); webview->AddRef(); flag.clear(); })); if (res != S_OK) { CoUninitialize(); return false; } MSG msg = {}; while (flag.test_and_set() && GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } app->webviewController = controller; app->webview = webview; // Resize WebView to fit the bounds of the parent window RECT bounds; GetClientRect(app->window, &bounds); app->webviewController->put_Bounds(bounds); // Callback hack app->webview->AddScriptToExecuteOnDocumentCreated(L"window.chrome.webview.postMessage('I');", nullptr); // Load the HTML LPCWSTR html = (LPCWSTR) cstrToLPWSTR((char*)assets[0]); app->webview->Navigate(html); messageFromWindowCallback("Ej{\"name\":\"wails:launched\",\"data\":[]}"); return true; } void initialCallback(std::string message) { printf("MESSAGE=%s\n", message); } void Run(struct Application* app, int argc, char **argv) { WNDCLASSEX wc; HINSTANCE hInstance = GetModuleHandle(NULL); ZeroMemory(&wc, sizeof(WNDCLASSEX)); wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.hInstance = hInstance; wc.lpszClassName = (LPCWSTR)"ffenestri"; wc.lpfnWndProc = WndProc; // TODO: Menu // wc.lpszMenuName = nullptr; // Process window resizable DWORD windowStyle = WS_OVERLAPPEDWINDOW; if (app->resizable == 0) { windowStyle &= ~WS_MAXIMIZEBOX; windowStyle &= ~WS_THICKFRAME; } if ( app->frame == 0 ) { windowStyle = WS_POPUP; } RegisterClassEx(&wc); app->window = CreateWindow((LPCWSTR)"ffenestri", (LPCWSTR)"", windowStyle, CW_USEDEFAULT, CW_USEDEFAULT, app->width, app->height, NULL, NULL, hInstance, NULL); // Private setTitle as we're on the main thread setTitle(app, app->title); // Store application pointer in window handle SetWindowLongPtr(app->window, GWLP_USERDATA, (LONG_PTR)app); // Process whether window should show by default int startVisibility = SW_SHOWNORMAL; if ( app->startHidden == 1 ) { startVisibility = SW_HIDE; } // TODO: Make configurable // COREWEBVIEW2_COLOR wvColor; // wvColor.A = 255; // std::weak_ptr controller2 = app->webviewController->query(); // controller2->put_DefaultBackgroundColor(wvColor); if( app->windowBackgroundIsTranslucent ) { enableTranslucentBackground(app); } // private center() as we are on main thread center(app); ShowWindow(app->window, startVisibility); UpdateWindow(app->window); SetFocus(app->window); // Add webview2 initWebView2(app, 1, initialCallback); // Main event loop MSG msg; BOOL res; while ((res = GetMessage(&msg, NULL, 0, 0)) != -1) { if (msg.hwnd) { TranslateMessage(&msg); DispatchMessage(&msg); continue; } if (msg.message == WM_APP) { dispatchFunction *f = (dispatchFunction*) msg.lParam; (*f)(); delete(f); } else if (msg.message == WM_QUIT) { performShutdown(app); return; } } } void DestroyApplication(struct Application* app) { PostQuitMessage(0); } void SetDebug(struct Application* app, int flag) { debug = flag; } void ExecJS(struct Application* app, const char *script) { ON_MAIN_THREAD( execJS(app, script); ); } void hide(struct Application* app) { ShowWindow(app->window, SW_HIDE); } void Hide(struct Application* app) { ON_MAIN_THREAD( hide(app); ); } void show(struct Application* app) { ShowWindow(app->window, SW_SHOW); } void Show(struct Application* app) { ON_MAIN_THREAD( show(app); ); } void center(struct Application* app) { HMONITOR currentMonitor = MonitorFromWindow(app->window, MONITOR_DEFAULTTONEAREST); MONITORINFO info = {0}; info.cbSize = sizeof(info); GetMonitorInfoA(currentMonitor, &info); RECT workRect = info.rcWork; LONG screenMiddleW = (workRect.right - workRect.left) / 2; LONG screenMiddleH = (workRect.bottom - workRect.top) / 2; RECT winRect; if (app->frame == 1) { GetWindowRect(app->window, &winRect); } else { GetClientRect(app->window, &winRect); } LONG winWidth = winRect.right - winRect.left; LONG winHeight = winRect.bottom - winRect.top; LONG windowX = screenMiddleW - (winWidth / 2); LONG windowY = screenMiddleH - (winHeight / 2); SetWindowPos(app->window, HWND_TOP, windowX, windowY, winWidth, winHeight, SWP_NOSIZE); } void Center(struct Application* app) { ON_MAIN_THREAD( center(app); ); } UINT getWindowPlacement(struct Application* app) { WINDOWPLACEMENT lpwndpl; lpwndpl.length = sizeof(WINDOWPLACEMENT); BOOL result = GetWindowPlacement(app->window, &lpwndpl); if( result == 0 ) { // TODO: Work out what this call failing means return -1; } return lpwndpl.showCmd; } int isMaximised(struct Application* app) { return getWindowPlacement(app) == SW_SHOWMAXIMIZED; } void maximise(struct Application* app) { ShowWindow(app->window, SW_MAXIMIZE); } void Maximise(struct Application* app) { ON_MAIN_THREAD( maximise(app); ); } void unmaximise(struct Application* app) { ShowWindow(app->window, SW_RESTORE); } void Unmaximise(struct Application* app) { ON_MAIN_THREAD( unmaximise(app); ); } void ToggleMaximise(struct Application* app) { if(isMaximised(app)) { return Unmaximise(app); } return Maximise(app); } int isMinimised(struct Application* app) { return getWindowPlacement(app) == SW_SHOWMINIMIZED; } void minimise(struct Application* app) { ShowWindow(app->window, SW_MINIMIZE); } void Minimise(struct Application* app) { ON_MAIN_THREAD( minimise(app); ); } void unminimise(struct Application* app) { ShowWindow(app->window, SW_RESTORE); } void Unminimise(struct Application* app) { ON_MAIN_THREAD( unminimise(app); ); } void ToggleMinimise(struct Application* app) { if(isMinimised(app)) { return Unminimise(app); } return Minimise(app); } void SetColour(struct Application* app, int red, int green, int blue, int alpha) { // TBD } void SetSize(struct Application* app, int width, int height) { // TBD } void setPosition(struct Application* app, int x, int y) { // TBD } void SetPosition(struct Application* app, int x, int y) { // ON_MAIN_THREAD( // setPosition(app, x, y); // ); } void Quit(struct Application* app) { DestroyWindow(app->window); } // Credit: https://stackoverflow.com/a/6693107 void setTitle(struct Application* app, const char *title) { LPCTSTR text = cstrToLPWSTR(title); SetWindowText(app->window, text); delete[] text; } void SetTitle(struct Application* app, const char *title) { ON_MAIN_THREAD( setTitle(app, title); ); } void Fullscreen(struct Application* app) { } void UnFullscreen(struct Application* app) { } void ToggleFullscreen(struct Application* app) { } void DisableFrame(struct Application* app) { app->frame = 0; } // WebviewIsTransparent will make the webview transparent // revealing the window underneath void WebviewIsTransparent(struct Application *app) { app->webviewIsTranparent = true; } void WindowBackgroundIsTranslucent(struct Application *app) { app->windowBackgroundIsTranslucent = true; } void OpenDialog(struct Application* app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int allowFiles, int allowDirs, int allowMultiple, int showHiddenFiles, int canCreateDirectories, int resolvesAliases, int treatPackagesAsDirectories) { } void SaveDialog(struct Application* app, char *callbackID, char *title, char *filters, char *defaultFilename, char *defaultDir, int showHiddenFiles, int canCreateDirectories, int treatPackagesAsDirectories) { } void MessageDialog(struct Application* app, char *callbackID, char *type, char *title, char *message, char *icon, char *button1, char *button2, char *button3, char *button4, char *defaultButton, char *cancelButton) { } void DarkModeEnabled(struct Application* app, char *callbackID) { } void SetApplicationMenu(struct Application* app, const char *applicationMenuJSON) { } void AddTrayMenu(struct Application* app, const char *menuTrayJSON) { } void SetTrayMenu(struct Application* app, const char *menuTrayJSON) { } void DeleteTrayMenuByID(struct Application* app, const char *id) { } void UpdateTrayMenuLabel(struct Application* app, const char* JSON) { } void AddContextMenu(struct Application* app, char *contextMenuJSON) { } void UpdateContextMenu(struct Application* app, char *contextMenuJSON) { }