From efa57fe37f07b92da80481fe92d0c672002acaeb Mon Sep 17 00:00:00 2001 From: fanlumaster <1730976608@qq.com> Date: Wed, 26 Mar 2025 16:36:21 +0800 Subject: [PATCH] refactor --- server/.clang-format | 2 +- server/.clangd | 5 +- server/CMakeLists.txt | 23 +- .../horizontal_candidate_window_dark.html | 55 ++ .../body/vertical_candidate_window_dark.html | 55 ++ .../other-themes/.gitkeep} | 0 server/src/MainWindow.cpp | 574 ------------------ server/src/defines/defines.h | 10 + server/src/log/fanylog.cpp | 23 + server/src/log/fanylog.h | 5 + server/src/main.cpp | 15 + server/src/utils/common_utils.cpp | 6 + server/src/utils/common_utils.h | 7 + server/src/utils/webview_utils.cpp | 19 + server/src/utils/webview_utils.h | 5 + .../webview2/candidate_window_webview2.cpp | 179 ++++++ .../src/webview2/candidate_window_webview2.h | 32 + server/src/webview2/webview2_ui.h | 0 server/src/window/candidate_window.cpp | 160 +++++ server/src/window/candidate_window.h | 12 + 20 files changed, 605 insertions(+), 582 deletions(-) create mode 100644 server/html/default-themes/body/horizontal_candidate_window_dark.html create mode 100644 server/html/default-themes/body/vertical_candidate_window_dark.html rename server/{src/webview2/webview2_ui.cpp => html/other-themes/.gitkeep} (100%) delete mode 100644 server/src/MainWindow.cpp create mode 100644 server/src/defines/defines.h create mode 100644 server/src/log/fanylog.cpp create mode 100644 server/src/log/fanylog.h create mode 100644 server/src/main.cpp create mode 100644 server/src/utils/common_utils.cpp create mode 100644 server/src/utils/common_utils.h create mode 100644 server/src/utils/webview_utils.cpp create mode 100644 server/src/utils/webview_utils.h create mode 100644 server/src/webview2/candidate_window_webview2.cpp create mode 100644 server/src/webview2/candidate_window_webview2.h delete mode 100644 server/src/webview2/webview2_ui.h create mode 100644 server/src/window/candidate_window.cpp create mode 100644 server/src/window/candidate_window.h diff --git a/server/.clang-format b/server/.clang-format index 6f12b1ec..8e3081df 100644 --- a/server/.clang-format +++ b/server/.clang-format @@ -3,4 +3,4 @@ BasedOnStyle: Microsoft IndentWidth: 4 SortIncludes: true -ColumnLimit: 80 +ColumnLimit: 120 diff --git a/server/.clangd b/server/.clangd index f4f6ed6c..76ae3e4f 100644 --- a/server/.clangd +++ b/server/.clangd @@ -4,8 +4,9 @@ CompileFlags: -D_UNICODE, -DFANY_DEBUG, "-std=c++17", - "-IC:\\Users\\SonnyCalcr\\EDisk\\CppCodes\\Win32Codes\\DirectWriteExperiment\\includes", # you should replace this to your own include path of this project - "-IC:\\Users\\SonnyCalcr\\EDisk\\CppCodes\\Win32Codes\\DirectWriteExperiment\\fmt\\include", # you should replace this to your own include path of fmt + "-IC:\\Users\\SonnyCalcr\\EDisk\\CppCodes\\IMECodes\\FanIME-UI\\includes", + "-IC:\\Users\\SonnyCalcr\\EDisk\\CppCodes\\IMECodes\\FanIME-UI\\fmt\\include", + "-IC:\\Users\\SonnyCalcr\\EDisk\\CppCodes\\IMECodes\\FanIME-UI\\src", "-IC:\\Users\\SonnyCalcr\\.nuget\\packages\\microsoft.web.webview2\\1.0.3124.44\\build\\native\\include", "-IC:\\Users\\SonnyCalcr\\.nuget\\packages\\microsoft.windows.implementationlibrary\\1.0.240803.1\\include", -Wno-microsoft-goto, diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index ecac4f9e..4073adb9 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -4,11 +4,16 @@ set(PROJECT_NAME "fanimeui") project(${PROJECT_NAME} VERSION 1.0) +set(Boost_ROOT "C:/Users/SonnyCalcr/scoop/apps/boost/1.87.0") +set (Boost_USE_STATIC_LIBS ON) +find_package(Boost REQUIRED COMPONENTS locale) + add_subdirectory(fmt) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) add_definitions( /D_UNICODE=1 @@ -20,14 +25,22 @@ add_definitions( # ) include_directories("./src/") -include_directories("./src/webview2") set(MY_EXECUTABLE_NAME "fanimeui") set(SRC_FILES - "./src/webview2/webview2_ui.cpp" - "./src/webview2/webview2_ui.h" - "src/MainWindow.cpp" + "./src/defines/defines.h" + "./src/log/fanylog.cpp" + "./src/log/fanylog.h" + "./src/utils/webview_utils.cpp" + "./src/utils/webview_utils.h" + "./src/utils/common_utils.cpp" + "./src/utils/common_utils.h" + "./src/webview2/candidate_window_webview2.cpp" + "./src/webview2/candidate_window_webview2.h" + "./src/window/candidate_window.cpp" + "./src/window/candidate_window.h" + "./src/main.cpp" ) add_executable(${MY_EXECUTABLE_NAME} WIN32 @@ -38,4 +51,4 @@ set_target_properties(${PROJECT_NAME} PROPERTIES VS_GLOBAL_WebView2LoaderPrefere set_target_properties(${PROJECT_NAME} PROPERTIES VS_USER_PROPS "${CMAKE_SOURCE_DIR}/FanIME_UI.props") set_target_properties(${PROJECT_NAME} PROPERTIES VS_PACKAGE_REFERENCES "Microsoft.Web.WebView2_1.0.3124.44;Microsoft.Windows.ImplementationLibrary_1.0.240803.1") -target_link_libraries(${PROJECT_NAME} fmt::fmt) \ No newline at end of file +target_link_libraries(${PROJECT_NAME} fmt::fmt Boost::locale) \ No newline at end of file diff --git a/server/html/default-themes/body/horizontal_candidate_window_dark.html b/server/html/default-themes/body/horizontal_candidate_window_dark.html new file mode 100644 index 00000000..1cf569a1 --- /dev/null +++ b/server/html/default-themes/body/horizontal_candidate_window_dark.html @@ -0,0 +1,55 @@ +
+ +
+
{0}
+
+ +
+
+
{1}
+
+
+ +
+
+
{2}
+
+
+ +
+
+
{3}
+
+
+ +
+
+
{4}
+
+
+ +
+
+
{5}
+
+
+ +
+
+
{6}
+
+
+ +
+
+
{7}
+
+
+ +
+
+
{8}
+
+
+ +
\ No newline at end of file diff --git a/server/html/default-themes/body/vertical_candidate_window_dark.html b/server/html/default-themes/body/vertical_candidate_window_dark.html new file mode 100644 index 00000000..1cf569a1 --- /dev/null +++ b/server/html/default-themes/body/vertical_candidate_window_dark.html @@ -0,0 +1,55 @@ +
+ +
+
{0}
+
+ +
+
+
{1}
+
+
+ +
+
+
{2}
+
+
+ +
+
+
{3}
+
+
+ +
+
+
{4}
+
+
+ +
+
+
{5}
+
+
+ +
+
+
{6}
+
+
+ +
+
+
{7}
+
+
+ +
+
+
{8}
+
+
+ +
\ No newline at end of file diff --git a/server/src/webview2/webview2_ui.cpp b/server/html/other-themes/.gitkeep similarity index 100% rename from server/src/webview2/webview2_ui.cpp rename to server/html/other-themes/.gitkeep diff --git a/server/src/MainWindow.cpp b/server/src/MainWindow.cpp deleted file mode 100644 index 759e4e9d..00000000 --- a/server/src/MainWindow.cpp +++ /dev/null @@ -1,574 +0,0 @@ -#include "WebView2.h" -#include "fmt/core.h" -#include "fmt/xchar.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// -// Message define -// -UINT WM_SHOW_MAIN_WINDOW; -UINT WM_HIDE_MAIN_WINDOW; -UINT WM_MOVE_CANDIDATE_WINDOW; - -static TCHAR szWindowClass[] = _T("global_candidate_window"); -static TCHAR szTitle[] = _T("Global Candidate Window"); - -std::wstring candStr = L""; - -static std::wstring HTMLString = LR"( - - - - - - - Vertical Candidate Window - - - - - - -
-
-
ni'uo
-
-
-
-
1. 你说
-
-
-
-
-
2. 笔画
-
-
- -
-
-
3. 量子
-
-
- -
-
-
4. 牛魔
-
-
- -
-
-
5. 仙人
-
-
- -
-
-
6. 可恨
-
-
- -
-
-
7. 木槿
-
-
- -
-
-
8. 无量
-
-
-
- - - -)"; - -static std::wstring BodyString = LR"( -
- -
-
{0}
-
- -
-
-
{1}
-
-
- -
-
-
{2}
-
-
- -
-
-
{3}
-
-
- -
-
-
{4}
-
-
- -
-
-
{5}
-
-
- -
-
-
{6}
-
-
- -
-
-
{7}
-
-
- -
-
-
{8}
-
-
- -
-)"; - -HINSTANCE hInst = 0; - -using namespace Microsoft::WRL; - -static ComPtr webviewController; -static ComPtr webview; -ComPtr webview3; -ComPtr webviewController2; - -void LogMessageW(const wchar_t *message) -{ - std::time_t now = std::time(nullptr); - std::tm *localTime = std::localtime(&now); - - wchar_t timeBuffer[80]; - wcsftime(timeBuffer, sizeof(timeBuffer) / sizeof(wchar_t), - L"%Y-%m-%d %H:%M:%S", localTime); - - std::wstring logfilePath = L"C:" - L"\\Users\\SonnyCalcr\\EDisk\\CppCodes\\Win32Cod" - L"es\\DirectWriteExperiment\\log.txt"; - std::wofstream logFile(logfilePath, std::ios_base::app); - if (logFile.is_open()) - { - logFile.imbue(std::locale("Chinese_China.65001")); - logFile << L"[" << timeBuffer << L"] " << message; - logFile << std::endl; - logFile.close(); - } -} - -void UpdateHtmlContentWithJavaScript(ComPtr webview, - const std::wstring &newContent) -{ - if (webview != nullptr) - { - std::wstring script = - L"document.body.innerHTML = `" + newContent + L"`;"; - webview->ExecuteScript(script.c_str(), nullptr); - } -} - -void MeasureDomUpdateTime(ComPtr webview) -{ - std::wstring script = - LR"(document.body.innerHTML = '
1. 原来
2. 如此
3. 竟然
4. 这样
5. 可恶
6. 棋盘
7. 磨合
8. 樱花
';)"; - - auto start = std::chrono::high_resolution_clock::now(); - - webview->ExecuteScript(script.c_str(), nullptr); - - auto end = std::chrono::high_resolution_clock::now(); - - auto duration = - std::chrono::duration_cast(end - start); - std::wstring message = - L"DOM update time: " + std::to_wstring(duration.count()) + L" μs"; - LogMessageW(message.c_str()); -} - -void inflateCandidateWindow(std::wstring &str) -{ - std::wstringstream wss(str); - std::wstring token; - std::vector words; - - while (std::getline(wss, token, L',')) - { - words.push_back(token); - } - - int size = words.size(); - - while (words.size() < 9) - { - words.push_back(L""); - } - - std::wstring result = fmt::format( // - BodyString, // - words[0], // - words[1], // - words[2], // - words[3], // - words[4], // - words[5], // - words[6], // - words[7], // - words[8] // - ); // - - if (size < 9) - { - result = result.substr( // - 0, // - result.find(fmt::format(L"", size)) // - ) // - + // - L""; // - } - - UpdateHtmlContentWithJavaScript(webview, result); -} - -static LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, - LPARAM lParam) -{ - - if (message == WM_SHOW_MAIN_WINDOW) - { - ShowWindow(hWnd, SW_SHOWNOACTIVATE); - return 0; - } - if (message == WM_HIDE_MAIN_WINDOW) - { - ShowWindow(hWnd, SW_HIDE); - if (candStr.find(L",") == 1) - { - inflateCandidateWindow(candStr); - } - else - { - UpdateHtmlContentWithJavaScript(webview, L""); - } - return 0; - } - - switch (message) - { - case WM_MOUSEACTIVATE: - // Stop the window from being activated by mouse click - return MA_NOACTIVATE; - case WM_COPYDATA: { - COPYDATASTRUCT *pcds = (COPYDATASTRUCT *)lParam; - if (pcds->dwData == 0) - { - POINT *pt = (POINT *)pcds->lpData; - MoveWindow( // - hWnd, // - pt->x, // - pt->y + 3, // - (108 + 15) * 1.5, // - (246 + 15) * 1.5, // - TRUE // - ); // - } - else if (pcds->dwData == 1) - { - WCHAR *p = (WCHAR *)pcds->lpData; - std::wstring str = p; - candStr = str; -#ifdef FANY_DEBUG - LogMessageW(str.c_str()); -#endif - inflateCandidateWindow(str); - } - return 1; - } - case WM_ACTIVATE: { - if (LOWORD(wParam) != WA_INACTIVE) - { - ShowWindow(hWnd, SW_SHOWNOACTIVATE); - } - break; - } - case WM_DESTROY: { - PostQuitMessage(0); - break; - } - default: - return DefWindowProc(hWnd, message, wParam, lParam); - } - - return 0; -} - -void InitWebview(HWND hWnd) -{ - CreateCoreWebView2EnvironmentWithOptions( - nullptr, nullptr, nullptr, - Callback( - [hWnd](HRESULT result, ICoreWebView2Environment *env) -> HRESULT { - if (result != S_OK) - { - MessageBox(hWnd, L"Failed to create WebView2 environment.", - L"Error", MB_OK); - return result; - } - - // Create a CoreWebView2Controller and get the associated - // CoreWebView2 - env->CreateCoreWebView2Controller( - hWnd, - Callback< - ICoreWebView2CreateCoreWebView2ControllerCompletedHandler>( - [hWnd](HRESULT result, - ICoreWebView2Controller *controller) -> HRESULT { - if (controller == nullptr || FAILED(result)) - { - MessageBox( - hWnd, - L"Failed to create WebView2 controller.", - L"Error", MB_OK); - return E_FAIL; - } - - webviewController = controller; - webviewController->get_CoreWebView2( - webview.GetAddressOf()); - - if (webview == nullptr) - { - MessageBox(hWnd, - L"Failed to get WebView2 instance.", - L"Error", MB_OK); - return E_FAIL; - } - - // Add a few settings for the webview - ICoreWebView2Settings *settings; - webview->get_Settings(&settings); - settings->put_IsScriptEnabled(TRUE); - settings->put_AreDefaultScriptDialogsEnabled(TRUE); - settings->put_IsWebMessageEnabled(TRUE); - settings->put_AreHostObjectsAllowed(TRUE); - - if (webview->QueryInterface( - IID_PPV_ARGS(&webview3)) == S_OK) - { - webview3->SetVirtualHostNameToFolderMapping( - L"appassets", - L"C:" - L"\\Users\\SonnyCalcr\\AppData\\Roaming\\Po" - L"tPla" - L"yerMini64\\Capture", - COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_DENY_CORS); - } - - if (controller->QueryInterface( - IID_PPV_ARGS(&webviewController2)) == S_OK) - { - COREWEBVIEW2_COLOR backgroundColor = {0, 0, 0, - 0}; - webviewController2->put_DefaultBackgroundColor( - backgroundColor); - } - - // Resize WebView to fit the bounds of the parent - // window - RECT bounds; - GetClientRect(hWnd, &bounds); - webviewController->put_Bounds(bounds); - // Navigate to a simple HTML string - auto hr = - webview->NavigateToString(HTMLString.c_str()); - if (FAILED(hr)) - { - MessageBox(hWnd, - L"Failed to navigate to string.", - L"Error", MB_OK); - } - - // webview->OpenDevToolsWindow(); - - return S_OK; - }) - .Get()); - return S_OK; - }) - .Get()); -} - -int CALLBACK WinMain(_In_ HINSTANCE hInstance, _In_ HINSTANCE hPrevInstance, - _In_ LPSTR lpCmdLine, _In_ int nCmdShow) -{ - SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); - - WM_SHOW_MAIN_WINDOW = RegisterWindowMessage(L"WM_SHOW_MAIN_WINDOW"); - WM_HIDE_MAIN_WINDOW = RegisterWindowMessage(L"WM_HIDE_MAIN_WINDOW"); - WM_MOVE_CANDIDATE_WINDOW = - RegisterWindowMessage(L"WM_MOVE_CANDIDATE_WINDOW"); - - WNDCLASSEX wcex; - - wcex.cbSize = sizeof(WNDCLASSEX); - wcex.style = CS_HREDRAW | CS_VREDRAW; - wcex.lpfnWndProc = WndProc; - wcex.cbClsExtra = 0; - wcex.cbWndExtra = 0; - wcex.hInstance = hInstance; - wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION); - wcex.hCursor = LoadCursor(NULL, IDC_ARROW); - wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); - wcex.lpszMenuName = NULL; - wcex.lpszClassName = szWindowClass; - wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION); - - if (!RegisterClassEx(&wcex)) - { - MessageBox(NULL, _T("Call to RegisterClassEx failed!"), - _T("Windows Desktop Guided Tour"), NULL); - - return 1; - } - - // Store instance handle in our global variable - hInst = hInstance; - - HWND hWnd = CreateWindowEx(WS_EX_LAYERED | WS_EX_TOOLWINDOW | // - WS_EX_NOACTIVATE | // - WS_EX_TOPMOST, // - szWindowClass, // - L"fanycandidatewindow", // - WS_POPUP, // - 100, // - 100, // - (108 + 15) * 1.5, // - (246 + 15) * 1.5, // - nullptr, nullptr, hInstance, nullptr); - - if (!hWnd) - { - MessageBox(NULL, _T("Call to CreateWindow failed!"), - _T("Windows Desktop Guided Tour"), NULL); - - return 1; - } - - MoveWindow(hWnd, -1000, 100, (108 + 15) * 1.5, (246 + 15) * 1.5, TRUE); - ShowWindow(hWnd, SW_SHOW); - UpdateWindow(hWnd); - - InitWebview(hWnd); - - MSG msg; - while (GetMessage(&msg, NULL, 0, 0)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - return (int)msg.wParam; -} \ No newline at end of file diff --git a/server/src/defines/defines.h b/server/src/defines/defines.h new file mode 100644 index 00000000..a31f8c2d --- /dev/null +++ b/server/src/defines/defines.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +// +// Message define +// +inline UINT WM_SHOW_MAIN_WINDOW; +inline UINT WM_HIDE_MAIN_WINDOW; +inline UINT WM_MOVE_CANDIDATE_WINDOW; \ No newline at end of file diff --git a/server/src/log/fanylog.cpp b/server/src/log/fanylog.cpp new file mode 100644 index 00000000..d82e5102 --- /dev/null +++ b/server/src/log/fanylog.cpp @@ -0,0 +1,23 @@ +#include "fanylog.h" + +void LogMessageW(const wchar_t *message) +{ + // std::time_t now = std::time(nullptr); + // std::tm *localTime = std::localtime(&now); + + // wchar_t timeBuffer[80]; + // wcsftime(timeBuffer, sizeof(timeBuffer) / sizeof(wchar_t), + // L"%Y-%m-%d %H:%M:%S", localTime); + + // std::wstring logfilePath = L"C:" + // L"\\Users\\SonnyCalcr\\EDisk\\CppCodes\\Win32Cod" + // L"es\\DirectWriteExperiment\\log.txt"; + // std::wofstream logFile(logfilePath, std::ios_base::app); + // if (logFile.is_open()) + // { + // logFile.imbue(std::locale("Chinese_China.65001")); + // logFile << L"[" << timeBuffer << L"] " << message; + // logFile << std::endl; + // logFile.close(); + // } +} \ No newline at end of file diff --git a/server/src/log/fanylog.h b/server/src/log/fanylog.h new file mode 100644 index 00000000..a2600104 --- /dev/null +++ b/server/src/log/fanylog.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +void LogMessageW(const wchar_t *); \ No newline at end of file diff --git a/server/src/main.cpp b/server/src/main.cpp new file mode 100644 index 00000000..58a03312 --- /dev/null +++ b/server/src/main.cpp @@ -0,0 +1,15 @@ +#include "window/candidate_window.h" + +int CALLBACK WinMain(_In_ HINSTANCE hInstance, _In_ HINSTANCE hPrevInstance, + _In_ LPSTR lpCmdLine, _In_ int nCmdShow) +{ + SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + + RegisterCandidateWindowMessage(); + + WNDCLASSEX wcex; + RegisterCandidateWindowClass(wcex, hInstance); + int ret = CreateCandidateWindow(hInstance); + + return ret; +} \ No newline at end of file diff --git a/server/src/utils/common_utils.cpp b/server/src/utils/common_utils.cpp new file mode 100644 index 00000000..6c638d94 --- /dev/null +++ b/server/src/utils/common_utils.cpp @@ -0,0 +1,6 @@ +#include "common_utils.h" + +void ShowErrorMessage(HWND hWnd, const std::wstring &message) +{ + MessageBox(hWnd, message.c_str(), L"Error", MB_OK); +} \ No newline at end of file diff --git a/server/src/utils/common_utils.h b/server/src/utils/common_utils.h new file mode 100644 index 00000000..7d9bfe56 --- /dev/null +++ b/server/src/utils/common_utils.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include + + +void ShowErrorMessage(HWND, const std::wstring &); \ No newline at end of file diff --git a/server/src/utils/webview_utils.cpp b/server/src/utils/webview_utils.cpp new file mode 100644 index 00000000..9f3d356a --- /dev/null +++ b/server/src/utils/webview_utils.cpp @@ -0,0 +1,19 @@ +#include "webview_utils.h" + +void MeasureDomUpdateTime(ComPtr webview) +{ + std::wstring script = + LR"(document.body.innerHTML = '
1. 原来
2. 如此
3. 竟然
4. 这样
5. 可恶
6. 棋盘
7. 磨合
8. 樱花
';)"; + + auto start = std::chrono::high_resolution_clock::now(); + + webview->ExecuteScript(script.c_str(), nullptr); + + auto end = std::chrono::high_resolution_clock::now(); + + auto duration = + std::chrono::duration_cast(end - start); + std::wstring message = + L"DOM update time: " + std::to_wstring(duration.count()) + L" μs"; + // LogMessageW(message.c_str()); +} \ No newline at end of file diff --git a/server/src/utils/webview_utils.h b/server/src/utils/webview_utils.h new file mode 100644 index 00000000..9f93a71e --- /dev/null +++ b/server/src/utils/webview_utils.h @@ -0,0 +1,5 @@ +#pragma once + +#include "webview2/candidate_window_webview2.h" + +void MeasureDomUpdateTime(ComPtr); \ No newline at end of file diff --git a/server/src/webview2/candidate_window_webview2.cpp b/server/src/webview2/candidate_window_webview2.cpp new file mode 100644 index 00000000..655a57a6 --- /dev/null +++ b/server/src/webview2/candidate_window_webview2.cpp @@ -0,0 +1,179 @@ +#include "candidate_window_webview2.h" +#include "utils/common_utils.h" +#include +#include + +std::wstring ReadHtmlFile(const std::wstring &filePath) +{ + std::wifstream file(filePath); + if (!file) + { + // TODO: Log + return L""; + } + // Use Boost Locale to handle UTF-8 + file.imbue(boost::locale::generator().generate("en_US.UTF-8")); + std::wstringstream buffer; + buffer << file.rdbuf(); + return buffer.str(); +} + +int PrepareCandidateWindowHtml() +{ + std::wstring htmlPath = + std::filesystem::current_path().wstring() + L"/html/default-themes/vertical_candidate_window_dark.html"; + ::HTMLString = ReadHtmlFile(htmlPath); + std::wstring bodyPath = + std::filesystem::current_path().wstring() + L"/html/default-themes/body/vertical_candidate_window_dark.html"; + ::BodyString = ReadHtmlFile(bodyPath); + return 0; +} + +void UpdateHtmlContentWithJavaScript(ComPtr webview, const std::wstring &newContent) +{ + if (webview != nullptr) + { + std::wstring script = L"document.body.innerHTML = `" + newContent + L"`;"; + webview->ExecuteScript(script.c_str(), nullptr); + } +} + +void InflateCandidateWindow(std::wstring &str) +{ + std::wstringstream wss(str); + std::wstring token; + std::vector words; + + while (std::getline(wss, token, L',')) + { + words.push_back(token); + } + + int size = words.size(); + + while (words.size() < 9) + { + words.push_back(L""); + } + + std::wstring result = fmt::format( // + BodyString, // + words[0], // + words[1], // + words[2], // + words[3], // + words[4], // + words[5], // + words[6], // + words[7], // + words[8] // + ); // + + if (size < 9) + { + size_t pos = result.find(fmt::format(L"", size)); + result = result.substr(0, pos) + L""; + } + + UpdateHtmlContentWithJavaScript(webview, result); +} + +// Handle WebView2 controller creation +HRESULT OnControllerCreated( // + HWND hWnd, // + HRESULT result, // + ICoreWebView2Controller *controller // +) +{ + if (!controller || FAILED(result)) + { + ShowErrorMessage(hWnd, L"Failed to create WebView2 controller."); + return E_FAIL; + } + + webviewController = controller; + webviewController->get_CoreWebView2(webview.GetAddressOf()); + + if (!webview) + { + ShowErrorMessage(hWnd, L"Failed to get WebView2 instance."); + return E_FAIL; + } + + // Configure WebView settings + ComPtr settings; + if (SUCCEEDED(webview->get_Settings(&settings))) + { + settings->put_IsScriptEnabled(TRUE); + settings->put_AreDefaultScriptDialogsEnabled(TRUE); + settings->put_IsWebMessageEnabled(TRUE); + settings->put_AreHostObjectsAllowed(TRUE); + } + + // Configure virtual host path + if (SUCCEEDED(webview->QueryInterface(IID_PPV_ARGS(&webview3)))) + { + webview3->SetVirtualHostNameToFolderMapping( // + L"appassets", // + LocalAssetsPath.c_str(), // + COREWEBVIEW2_HOST_RESOURCE_ACCESS_KIND_DENY_CORS // + ); // + } + + // Set transparent background + if (SUCCEEDED(controller->QueryInterface(IID_PPV_ARGS(&webviewController2)))) + { + COREWEBVIEW2_COLOR backgroundColor = {0, 0, 0, 0}; + webviewController2->put_DefaultBackgroundColor(backgroundColor); + } + + // Adjust to window size + RECT bounds; + GetClientRect(hWnd, &bounds); + webviewController->put_Bounds(bounds); + + // Navigate to HTML + HRESULT hr = webview->NavigateToString(HTMLString.c_str()); + if (FAILED(hr)) + { + ShowErrorMessage(hWnd, L"Failed to navigate to string."); + } + + return S_OK; +} + +// Handle WebView2 environment creation +HRESULT OnEnvironmentCreated(HWND hWnd, HRESULT result, ICoreWebView2Environment *env) +{ + if (FAILED(result) || !env) + { + ShowErrorMessage(hWnd, L"Failed to create WebView2 environment."); + return result; + } + + // Create WebView2 controller + return env->CreateCoreWebView2Controller( // + hWnd, // + Callback( // + [hWnd](HRESULT result, // + ICoreWebView2Controller *controller) -> HRESULT { // + return OnControllerCreated(hWnd, result, controller); // + }) // + .Get() // + ); // +} + +// Initialize WebView2 +void InitWebview(HWND hWnd) +{ + CreateCoreWebView2EnvironmentWithOptions( // + nullptr, // + nullptr, // + nullptr, // + Callback( // + [hWnd](HRESULT result, ICoreWebView2Environment *env) -> HRESULT { // + return OnEnvironmentCreated(hWnd, result, env); // + }) // + .Get() // + ); // +} \ No newline at end of file diff --git a/server/src/webview2/candidate_window_webview2.h b/server/src/webview2/candidate_window_webview2.h new file mode 100644 index 00000000..7edfd26c --- /dev/null +++ b/server/src/webview2/candidate_window_webview2.h @@ -0,0 +1,32 @@ +#pragma once + +#include "WebView2.h" +#include "fmt/core.h" +#include "fmt/xchar.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace Microsoft::WRL; + +inline ComPtr webviewController; +inline ComPtr webview; +inline ComPtr webview3; +inline ComPtr webviewController2; + +inline std::wstring HTMLString = LR"()"; +inline std::wstring BodyString = LR"()"; +inline std::wstring CandStr = L""; +const std::wstring LocalAssetsPath = L"C:\\Users\\SonnyCalcr\\AppData\\Roaming\\PotPlayerMini64\\Capture"; + +int PrepareCandidateWindowHtml(); +void UpdateHtmlContentWithJavaScript( // + ComPtr, // + const std::wstring & // +); // +void InflateCandidateWindow(std::wstring &); +void InitWebview(HWND); \ No newline at end of file diff --git a/server/src/webview2/webview2_ui.h b/server/src/webview2/webview2_ui.h deleted file mode 100644 index e69de29b..00000000 diff --git a/server/src/window/candidate_window.cpp b/server/src/window/candidate_window.cpp new file mode 100644 index 00000000..cc24fd94 --- /dev/null +++ b/server/src/window/candidate_window.cpp @@ -0,0 +1,160 @@ +#include "candidate_window.h" +#include "defines/defines.h" +#include "log/fanylog.h" +#include "webview2/candidate_window_webview2.h" +#include + +LRESULT RegisterCandidateWindowMessage() +{ + + WM_SHOW_MAIN_WINDOW = RegisterWindowMessage(L"WM_SHOW_MAIN_WINDOW"); + WM_HIDE_MAIN_WINDOW = RegisterWindowMessage(L"WM_HIDE_MAIN_WINDOW"); + WM_MOVE_CANDIDATE_WINDOW = RegisterWindowMessage(L"WM_MOVE_CANDIDATE_WINDOW"); + return 0; +} + +LRESULT RegisterCandidateWindowClass(WNDCLASSEX &wcex, HINSTANCE hInstance) +{ + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.style = CS_HREDRAW | CS_VREDRAW; + wcex.lpfnWndProc = WndProc; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.hIcon = LoadIcon(hInstance, IDI_APPLICATION); + wcex.hCursor = LoadCursor(NULL, IDC_ARROW); + wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); + wcex.lpszMenuName = NULL; + wcex.lpszClassName = szWindowClass; + wcex.hIconSm = LoadIcon(wcex.hInstance, IDI_APPLICATION); + + if (!RegisterClassEx(&wcex)) + { + MessageBox( // + NULL, // + L"Call to RegisterClassEx failed!", // + L"Windows Desktop Guided Tour", // + NULL // + ); // + return 1; + } + return 0; +} + +int CreateCandidateWindow(HINSTANCE hInstance) +{ + DWORD dwExStyle = WS_EX_LAYERED | // + WS_EX_TOOLWINDOW | // + WS_EX_NOACTIVATE | // + WS_EX_TOPMOST; // + HWND hWnd = CreateWindowEx( // + dwExStyle, // + szWindowClass, // + lpWindowName, // + WS_POPUP, // + 100, // + 100, // + (108 + 15) * 1.5, // + (246 + 15) * 1.5, // + nullptr, // + nullptr, // + hInstance, // + nullptr // + ); // + + if (!hWnd) + { + MessageBox( // + NULL, // + L"Call to CreateWindow failed!", // + L"Windows Desktop Guided Tour", // + NULL // + ); // + return 1; + } + + MoveWindow(hWnd, -1000, 100, (108 + 15) * 1.5, (246 + 15) * 1.5, TRUE); + ShowWindow(hWnd, SW_SHOW); + UpdateWindow(hWnd); + + PrepareCandidateWindowHtml(); + InitWebview(hWnd); + + MSG msg; + while (GetMessage(&msg, NULL, 0, 0)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return (int)msg.wParam; +} + +LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) +{ + if (message == WM_SHOW_MAIN_WINDOW) + { + ShowWindow(hWnd, SW_SHOWNOACTIVATE); + return 0; + } + if (message == WM_HIDE_MAIN_WINDOW) + { + ShowWindow(hWnd, SW_HIDE); + if (::CandStr.find(L",") == 1) + { + InflateCandidateWindow(::CandStr); + } + else + { + UpdateHtmlContentWithJavaScript(webview, L""); + } + return 0; + } + + switch (message) + { + case WM_MOUSEACTIVATE: + // Stop the window from being activated by mouse click + return MA_NOACTIVATE; + case WM_COPYDATA: { + COPYDATASTRUCT *pcds = (COPYDATASTRUCT *)lParam; + if (pcds->dwData == 0) + { + POINT *pt = (POINT *)pcds->lpData; + MoveWindow( // + hWnd, // + pt->x, // + pt->y + 3, // + (108 + 15) * 1.5, // + (246 + 15) * 1.5, // + TRUE // + ); // + } + else if (pcds->dwData == 1) + { + WCHAR *p = (WCHAR *)pcds->lpData; + std::wstring str = p; + CandStr = str; +#ifdef FANY_DEBUG + LogMessageW(str.c_str()); +#endif + InflateCandidateWindow(str); + } + return 1; + } + case WM_ACTIVATE: { + if (LOWORD(wParam) != WA_INACTIVE) + { + ShowWindow(hWnd, SW_SHOWNOACTIVATE); + } + break; + } + case WM_DESTROY: { + PostQuitMessage(0); + break; + } + default: + return DefWindowProc(hWnd, message, wParam, lParam); + } + + return 0; +} \ No newline at end of file diff --git a/server/src/window/candidate_window.h b/server/src/window/candidate_window.h new file mode 100644 index 00000000..4f1b83b1 --- /dev/null +++ b/server/src/window/candidate_window.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +inline WCHAR szWindowClass[] = L"global_candidate_window"; +inline WCHAR szTitle[] = L"Global Candidate Window"; +inline WCHAR lpWindowName[] = L"fanycandidatewindow"; + +LRESULT RegisterCandidateWindowMessage(); +LRESULT RegisterCandidateWindowClass(WNDCLASSEX &, HINSTANCE); +int CreateCandidateWindow(HINSTANCE); +LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); \ No newline at end of file