5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-06 10:01:06 +08:00
wails/v2/internal/ffenestri/ffenestri_windows.cpp

620 lines
18 KiB
C++

// 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 <functional>
#include <atomic>
#include <Shlwapi.h>
#include <locale>
#include <codecvt>
#include "windows/WebView2.h"
#include "effectstructs_windows.h"
#include <Shlobj.h>
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<std::codecvt_utf8_utf16<wchar_t>> 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<ICoreWebView2Controller2> controller2 = app->webviewController->query<ICoreWebView2Controller2>();
// 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) {
}