#include "pch.h" #include "log.h" #include "shader.h" #include "shape.h" #include "texture.h" #include "image.h" #include "app.h" #include "canvas.h" #include "keymap.h" #include "hmd.h" #include "../resource.h" #include #include #include #include #include "wacom.h" #include "abr.h" #include "settings.h" #include #include #include #include #define WM_USER_CLOSE (WM_USER + 1) #define WM_USER_WAKEUP (WM_USER + 2) LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp); HINSTANCE hInst; HWND hWnd; HDC hDC; HGLRC hRC; wchar_t* className; bool keys[256]; std::mutex gl_mutex; std::mutex async_mutex; std::thread::id gl_thread; std::map vkey_map; static wchar_t window_title[512]; std::thread hmd_renderer; int vr_frames = 0; int running = -1; int vr_running = 0; int gl_count = 0; std::deque> main_tasklist; std::mutex main_task_mutex; float timer_stylus = 0; float timer_ink_touch = 0; float timer_ink_pen = 0; bool sandboxed = false; RENDERDOC_API_1_4_0* rdoc_api = NULL; bool win32_renderdoc_init() { // At init, on windows if (HMODULE mod = GetModuleHandleA("renderdoc.dll")) { pRENDERDOC_GetAPI RENDERDOC_GetAPI = (pRENDERDOC_GetAPI)GetProcAddress(mod, "RENDERDOC_GetAPI"); return RENDERDOC_GetAPI(eRENDERDOC_API_Version_1_1_2, (void**)&rdoc_api); } return false; } void win32_renderdoc_frame_start() { if (rdoc_api) rdoc_api->StartFrameCapture(NULL, NULL); } void win32_renderdoc_frame_end() { if (rdoc_api) rdoc_api->EndFrameCapture(NULL, NULL); } HRESULT(*GetDpiForMonitor_fn)(HMONITOR hmonitor, MONITOR_DPI_TYPE dpiType, UINT* dpiX, UINT* dpiY); HRESULT(*SetProcessDpiAwareness_fn)(PROCESS_DPI_AWARENESS value); void init_shcore_API() { HMODULE dll = LoadLibrary(L"Shcore.dll"); if (!dll) { LOG("cannot load Shcore.dll"); return; } LOG("loaded Shcore.dll"); GetDpiForMonitor_fn = (decltype(GetDpiForMonitor_fn))GetProcAddress(dll, "GetDpiForMonitor"); SetProcessDpiAwareness_fn = (decltype(SetProcessDpiAwareness_fn))GetProcAddress(dll, "SetProcessDpiAwareness"); } BOOL(*GetPointerInfo_fn)(UINT32 pointerId, POINTER_INFO* pointerInfo); BOOL(*GetPointerType_fn)(UINT32 pointerId, POINTER_INPUT_TYPE* pointerType); BOOL(*GetPointerTouchInfo_fn)(UINT32 pointerId, POINTER_TOUCH_INFO* touchInfo); BOOL(*GetPointerPenInfo_fn)(UINT32 pointerId, POINTER_PEN_INFO* penInfo); void init_ink_API() { HMODULE dll = LoadLibrary(L"User32.dll"); if (!dll) { LOG("cannot load User32.dll"); return; } LOG("loaded User32.dll"); GetPointerInfo_fn = (decltype(GetPointerInfo_fn))GetProcAddress(dll, "GetPointerInfo"); GetPointerType_fn = (decltype(GetPointerType_fn))GetProcAddress(dll, "GetPointerType"); GetPointerTouchInfo_fn = (decltype(GetPointerTouchInfo_fn))GetProcAddress(dll, "GetPointerTouchInfo"); GetPointerPenInfo_fn = (decltype(GetPointerPenInfo_fn))GetProcAddress(dll, "GetPointerPenInfo"); } //Returns the last Win32 error, in string format. Returns an empty string if there is no error. std::string GetLastErrorAsString() { //Get the error message, if any. DWORD errorMessageID = ::GetLastError(); if (errorMessageID == 0) return std::string(); //No error message has been recorded LPSTR messageBuffer = nullptr; size_t size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL); std::string message(messageBuffer, size); //Free the buffer. LocalFree(messageBuffer); return message; } void destroy_window() { std::lock_guard lock(main_task_mutex); main_tasklist.emplace_back([=] { PostMessage(hWnd, WM_USER_CLOSE, 0, 0); }); } void async_lock() { //std::lock_guard _lock(async_mutex); if (gl_count == 0 || gl_thread != std::this_thread::get_id()) { gl_mutex.lock(); bool ret = wglMakeCurrent(hDC, hRC); if (ret == false) LOG("FAILED wglMakeCurrent: %s", GetLastErrorAsString().c_str()); gl_thread = std::this_thread::get_id(); //LOG("lock"); } gl_count++; //assert(ret == true); } bool async_lock_try() { //std::lock_guard _lock(async_mutex); if (gl_count == 0 || gl_thread != std::this_thread::get_id()) { if (!gl_mutex.try_lock()) return false; bool ret = wglMakeCurrent(hDC, hRC); if (ret == false) LOG("FAILED wglMakeCurrent: %s", GetLastErrorAsString().c_str()); gl_thread = std::this_thread::get_id(); //LOG("lock"); } gl_count++; //assert(ret == true); return true; } void win32_async_swap() { SwapBuffers(hDC); //LOG("swap"); } void async_unlock() { //std::lock_guard _lock(async_mutex); gl_count--; if (gl_count == 0) { //LOG("unlock"); wglMakeCurrent(0, 0); gl_mutex.unlock(); } } void win32_update_stylus(float dt) { timer_stylus += dt; timer_ink_touch += dt; timer_ink_pen += dt; if (timer_stylus > 0.1 && (WacomTablet::I.m_stylus || WacomTablet::I.m_eraser)) { WacomTablet::I.m_stylus = false; WacomTablet::I.m_eraser = false; App::I->redraw = true; } if (timer_ink_pen > 0.1 && WacomTablet::I.m_ink_pen) { WacomTablet::I.m_ink_pen = false; App::I->redraw = true; } if (timer_ink_touch > 0.1 && WacomTablet::I.m_ink_touch) { WacomTablet::I.m_ink_touch = false; App::I->redraw = true; } } void win32_update_fps(int frames) { static wchar_t title_fps[512]; if (App::I->vr_active) swprintf_s(title_fps, L"%s - %d fps - %d vr fps", window_title, frames, vr_frames); else swprintf_s(title_fps, L"%s - %d fps", window_title, frames); { std::lock_guard lock(main_task_mutex); main_tasklist.emplace_back([] { SetWindowText(hWnd, title_fps); }); } PostMessage(hWnd, WM_USER_WAKEUP, 0, 0); } void win32_show_cursor(bool visible) { std::lock_guard lock(main_task_mutex); main_tasklist.emplace_back([=] { if (visible) while (ShowCursor(true) < 0); else while (ShowCursor(false) >= 0); }); } std::string win32_clipboard_get_text() { std::string ret; if (OpenClipboard(hWnd)) { if (HANDLE h = GetClipboardData(CF_TEXT)) { if (char* s = (char*)GlobalLock(h)) { ret = s; GlobalUnlock(h); } } CloseClipboard(); } return ret; } bool win32_clipboard_set_text(const std::string& s) { bool success = false; if (OpenClipboard(hWnd)) { // owned by SetClipboardData if (HGLOBAL h = GlobalAlloc(GMEM_MOVEABLE, s.size() + 1)) { if (char* p = (char*)GlobalLock(h)) { std::copy(s.begin(), s.end(), p); p[s.size()] = 0; // string null-termination GlobalUnlock(h); success = true; } EmptyClipboard(); SetClipboardData(CF_TEXT, h); } CloseClipboard(); } return success; } std::string win32_open_file(const char* filter) { OPENFILENAMEA ofn; char fileName[MAX_PATH] = ""; ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hWnd; ofn.lpstrFilter = filter; ofn.lpstrFile = fileName; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR; ofn.lpstrDefExt = ""; ofn.lpstrInitialDir = ""; if (GetOpenFileNameA(&ofn) != NULL) { return fileName; } return ""; } std::string win32_save_file(const char* filter) { OPENFILENAMEA ofn; char fileName[MAX_PATH] = ""; ZeroMemory(&ofn, sizeof(ofn)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hWnd; ofn.lpstrFilter = filter; ofn.lpstrFile = fileName; ofn.nMaxFile = MAX_PATH; ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_OVERWRITEPROMPT; ofn.lpstrDefExt = ""; ofn.lpstrInitialDir = ""; if (GetSaveFileNameA(&ofn) != NULL) { return fileName; } return ""; } std::string win32_open_dir() { BROWSEINFOA bi; char Buffer[MAX_PATH]; ZeroMemory(Buffer, MAX_PATH); ZeroMemory(&bi, sizeof(bi)); bi.hwndOwner = hWnd; bi.pszDisplayName = Buffer; bi.lpszTitle = "Title"; bi.ulFlags = BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_RETURNONLYFSDIRS | BIF_SHAREABLE; LPCITEMIDLIST pFolder = SHBrowseForFolderA(&bi); if (pFolder == NULL) return ""; if (!SHGetPathFromIDListA(pFolder, Buffer)) return ""; return Buffer; } int read_WMI_info() { // see: http://win32easy.blogspot.co.uk/2011/03/wmi-in-c-query-everyting-from-your-os.html HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED); if (FAILED(hRes)) { LOG("Unable to launch COM: %x", hRes); return 1; } if ((FAILED(hRes = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, 0)))) { LOG("Unable to initialize security: %x", hRes); return 1; } IWbemLocator* pLocator = NULL; if (FAILED(hRes = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pLocator)))) { LOG("Unable to create a WbemLocator: %x", hRes); return 1; } IWbemServices* pService = NULL; if (FAILED(hRes = pLocator->ConnectServer(L"root\\CIMV2", NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pService))) { pLocator->Release(); LOG("Unable to connect to \"CIMV2\": %x", hRes); return 1; } auto log_field = [](const wchar_t* section, IWbemClassObject* clsObj, const wchar_t* field) { VARIANT vRet; CIMTYPE pType; VariantInit(&vRet); if (SUCCEEDED(clsObj->Get(field, 0, &vRet, &pType, NULL))) { if (pType == CIM_STRING && pType != CIM_EMPTY && pType != CIM_ILLEGAL) { LOGW(L"%s %s: %s", section, field, vRet.bstrVal); } else if (pType == CIM_UINT32 && pType != CIM_EMPTY && pType != CIM_ILLEGAL) { LOGW(L"%s %s: %d", section, field, vRet.uintVal); } VariantClear(&vRet); } }; auto get_int = [](IWbemClassObject* clsObj, const wchar_t* field) { VARIANT vRet; CIMTYPE pType; VariantInit(&vRet); int ret = 0; if (SUCCEEDED(clsObj->Get(field, 0, &vRet, &pType, NULL))) { if (pType == CIM_UINT32 && pType != CIM_EMPTY && pType != CIM_ILLEGAL) ret = vRet.uintVal; VariantClear(&vRet); } return ret; }; // GET DEVICE INFO { IEnumWbemClassObject* pEnumerator = NULL; if (FAILED(hRes = pService->ExecQuery(L"WQL", L"SELECT * FROM Win32_ComputerSystem", WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator))) { pLocator->Release(); pService->Release(); LOG("Unable to retrive desktop monitors: %x", hRes); return 1; } IWbemClassObject* clsObj = NULL; int numElems; while ((hRes = pEnumerator->Next(WBEM_INFINITE, 1, &clsObj, (ULONG*)&numElems)) != WBEM_S_FALSE) { if (FAILED(hRes)) break; log_field(L"Machine", clsObj, L"Name"); log_field(L"Machine", clsObj, L"Model"); log_field(L"Machine", clsObj, L"Manufacturer"); clsObj->Release(); } pEnumerator->Release(); } // GET OS INFO { IEnumWbemClassObject* pEnumerator = NULL; if (FAILED(hRes = pService->ExecQuery(L"WQL", L"SELECT * FROM Win32_OperatingSystem", WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator))) { pLocator->Release(); pService->Release(); LOG("Unable to retrive desktop monitors: %x", hRes); return 1; } IWbemClassObject* clsObj = NULL; int numElems; while ((hRes = pEnumerator->Next(WBEM_INFINITE, 1, &clsObj, (ULONG*)&numElems)) != WBEM_S_FALSE) { if (FAILED(hRes)) break; log_field(L"OS", clsObj, L"Name"); log_field(L"OS", clsObj, L"Version"); log_field(L"OS", clsObj, L"Locale"); log_field(L"OS", clsObj, L"OSProductSuite"); log_field(L"OS", clsObj, L"Manufacturer"); log_field(L"OS", clsObj, L"Description"); clsObj->Release(); } pEnumerator->Release(); } pService->Release(); pService = NULL; if (FAILED(hRes = pLocator->ConnectServer(L"root\\Microsoft\\Windows\\DeviceGuard", NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &pService))) { pLocator->Release(); LOG("Unable to connect to \"DeviceGuard\": %x", hRes); return 1; } // GET DEVICE GUARD { IEnumWbemClassObject* pEnumerator = NULL; if (FAILED(hRes = pService->ExecQuery(L"WQL", L"SELECT * FROM Win32_DeviceGuard", WBEM_FLAG_FORWARD_ONLY, NULL, &pEnumerator))) { pLocator->Release(); pService->Release(); LOG("Unable to retrive desktop monitors: %x", hRes); return 1; } IWbemClassObject* clsObj = NULL; int numElems; while ((hRes = pEnumerator->Next(WBEM_INFINITE, 1, &clsObj, (ULONG*)&numElems)) != WBEM_S_FALSE) { if (FAILED(hRes)) break; if (get_int(clsObj, L"CodeIntegrityPolicyEnforcementStatus") > 0) { LOG("SANDBOX DETECTED"); sandboxed = true; } SAFEARRAY *psaNames = NULL; if (SUCCEEDED(clsObj->GetNames(0, WBEM_FLAG_ALWAYS | WBEM_FLAG_NONSYSTEM_ONLY, 0, &psaNames))) { // Get the number of properties. long lLower, lUpper; BSTR PropName = NULL; SafeArrayGetLBound(psaNames, 1, &lLower); SafeArrayGetUBound(psaNames, 1, &lUpper); for (long i = lLower; i <= lUpper; i++) { // Get this property. SafeArrayGetElement(psaNames, &i, &PropName); LOGW(L"Prop: %s", PropName); log_field(L"DeviceGuard", clsObj, PropName); SysFreeString(PropName); } SafeArrayDestroy(psaNames); } clsObj->Release(); } pEnumerator->Release(); } pLocator->Release(); CoUninitialize(); return 0; } INT_PTR g_iLogHandle = -1; static void SetupExceptionHandler() { // Setup exception handler BT_SetAppName(_T("PanoPainter")); BT_SetAppVersion(g_version_w); //BT_SetSupportEMail(_T("your@email.com")); BT_SetFlags(BTF_DETAILEDMODE | BTF_ATTACHREPORT | BTF_SCREENCAPTURE); // = BugTrapServer =========================================== //BT_SetSupportServer(_T("omigamedev.ddns.net"), 8088); BT_SetSupportEMail(_T("info@panopainter.com")); // - or - //BT_SetSupportServer(_T("127.0.0.1"), 9999); // = BugTrapWebServer ======================================== //BT_SetSupportServer(_T("http://localhost/BugTrapWebServer/RequestHandler.aspx"), BUGTRAP_HTTP_PORT); //BT_SetSupportServer(_T("http://omigamedev.ddns.net:8088/source/Server/BugTrapWebServer/RequestHandler.aspx"), BUGTRAP_HTTP_PORT); BT_SetSupportServer(_T("http://panopainter.com/bug/"), BUGTRAP_HTTP_PORT); // required for VS 2005 & 2008 BT_InstallSehFilter(); // Add custom log file using default name // g_iLogHandle = BT_OpenLogFile(NULL, BTLF_TEXT); // BT_SetLogSizeInEntries(g_iLogHandle, 100); // BT_SetLogFlags(g_iLogHandle, BTLF_SHOWTIMESTAMP); // BT_SetLogEchoMode(g_iLogHandle, BTLE_STDERR | BTLE_DBGOUT); // // PCTSTR pszLogFileName = BT_GetLogFileName(g_iLogHandle); TCHAR wpath[MAX_PATH]; //GetFullPathNameW(L"panopainter-log.txt", 1024, wpath, nullptr); auto log_file = App::I->data_path + "/panopainter-log.txt"; std::mbstowcs(wpath, log_file.c_str(), log_file.size()); BT_AddLogFile(wpath); BT_SetPreErrHandler([](INT_PTR){ if (Canvas::I && Canvas::I->m_unsaved) { auto t = std::time(nullptr); auto tm = *std::localtime(&t); std::ostringstream oss; oss << std::put_time(&tm, "%d-%m-%Y %H-%M-%S"); auto path = App::I->data_path + "/" + App::I->doc_name + "-recovery (" + oss.str() + ").ppi"; Canvas::I->project_save_thread(path, false); static char abspath[MAX_PATH]; GetFullPathNameA(path.c_str(), MAX_PATH, abspath, NULL); static char message[4096]; snprintf(message, sizeof(message), "File recovered in: %s", abspath); MessageBoxA(hWnd, message, "File Recovery", MB_OK | MB_ICONWARNING); } LogRemote::I.file_close(); }, 0); } // create a reverse map from kKey to VK_XXX void init_vk_map() { for (int k = 1; k < 256; k++) // ignore kKey::Unknown = 0 { for (int vk = 0; vk < 256; vk++) { if (k == (int)convert_key(vk)) { if (vkey_map.find((kKey)k) == vkey_map.end()) { vkey_map.insert({ (kKey)k, vk }); } else { LOG("KEY MAP COLLISION %d and %d maps to %d", (int)vk, (int)vkey_map[(kKey)k], (int)k); } } } } } std::mutex hmd_render_mutex; std::condition_variable hmd_render_cv; Vive* vive = nullptr; bool win32_vr_start() { if (sandboxed) return false; vive = new Vive; vive->on_draw = [](const glm::mat4& proj, const glm::mat4& view, const glm::mat4& pose) { App::I->vr_draw(proj, view, pose); }; if (!vive->Initialize()) { delete vive; vive = nullptr; LOG("VR: failed to initialize vive"); return false; } if (hmd_renderer.joinable()) hmd_renderer.join(); hmd_renderer = std::thread([&] { if (!vive) return; BT_SetTerminate(); LOG("start hmd render thread"); App::I->has_vr = true; vr_running = true; vive->on_analog_button = std::bind(&App::vr_analog, App::I, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); vive->on_button = std::bind(&App::vr_digital, App::I, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4); App::I->render_task([] { App::I->vr_draw_ui(); }); const float target_tick_rate = 90; auto t0 = GetTickCount64(); float one_sec_timer = 0; int frames = 0; while (vr_running && running == 1 && vive->Valid()) { std::unique_lock lock(hmd_render_mutex); auto t1 = GetTickCount64(); float dt = (float)(t1 - t0) / 1000.0f; one_sec_timer += dt; if (one_sec_timer >= 1.f) { one_sec_timer = 0; vr_frames = frames; frames = 0; } frames++; vive->Update(); App::I->vr_active = vive->m_active; App::I->vr_controllers[0] = vive->m_controllers[0]; App::I->vr_head = vive->m_pose; App::I->vr_update(dt); if (vr_running && vive->m_active) { App::I->render_task([] { vive->Draw(); }); } const int framerate = (1.f / target_tick_rate) * 1000; const int diff = framerate - (t1 - t0); hmd_render_cv.wait_for(lock, std::chrono::milliseconds(diff)); t0 = t1; } App::I->vr_active = false; App::I->has_vr = false; vr_running = false; vive->Terminate(); LOG("hmd renderer terminated"); }); return true; } void win32_vr_stop() { if (vive) { vr_running = false; if (hmd_renderer.joinable()) hmd_renderer.join(); vive->Terminate(); delete vive; vive = nullptr; } } void win32_save_window_state() { WINDOWPLACEMENT p; GetWindowPlacement(hWnd, &p); Settings::set("window-show-cmd", Serializer::Integer(p.showCmd)); Settings::set("window-rect", Serializer::IVec4({ p.rcNormalPosition.left, p.rcNormalPosition.top, p.rcNormalPosition.right, p.rcNormalPosition.bottom })); } HBITMAP image_to_hbitmap(const Image& img) { BITMAPINFOHEADER bmih; memset(&bmih, 0, sizeof(BITMAPINFOHEADER)); bmih.biWidth = img.width; bmih.biHeight = img.height; bmih.biBitCount = 32; bmih.biCompression = BI_RGB; bmih.biSize = sizeof(BITMAPINFOHEADER); bmih.biPlanes = 1; BITMAPINFO* bmi = (BITMAPINFO*)&bmih; return CreateDIBitmap(GetDC(NULL), &bmih, CBM_INIT, img.data(), bmi, DIB_RGB_COLORS); } LRESULT CALLBACK splash_proc(HWND hWndDlg, UINT Msg, WPARAM wParam, LPARAM lParam) { switch (Msg) { case WM_INITDIALOG: { auto monitor = MonitorFromWindow(0, MONITOR_DEFAULTTOPRIMARY); auto x = unsigned{ 96 }; auto y = unsigned{ 96 }; if (GetDpiForMonitor_fn) GetDpiForMonitor_fn(monitor, MDT_EFFECTIVE_DPI, &x, &y); float z = (float)x / 96.f; static char base_path[MAX_PATH]; GetCurrentDirectoryA(MAX_PATH, base_path); std::string path = std::string(base_path) + "\\data\\splash.jpg"; //auto hbitmap = (HBITMAP)LoadImageA(NULL, path.c_str(), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); Image img; img.load_file(path); img.flip(); auto hbitmap = image_to_hbitmap(img.resize(512 * z, 200 * z)); SendMessage(GetDlgItem(hWndDlg, IDC_STATIC_IMAGE), STM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)hbitmap); SetDlgItemText(hWndDlg, IDC_STATIC_VERSION, g_version_number_w); RECT r; GetClientRect(hWndDlg, &r); MoveWindow(GetDlgItem(hWndDlg, IDC_STATIC_IMAGE), 0, 0, 512 * z, 200 * z, TRUE); SetWindowPos(hWndDlg, HWND_TOP, 0, 0, 512 * z, 200 * z, SWP_NOMOVE); HWND hWndVersion = GetDlgItem(hWndDlg, IDC_STATIC_VERSION); RECT rv; GetClientRect(hWndVersion, &rv); MoveWindow(hWndVersion, 0, 200 * z - (rv.bottom - rv.top), r.right - r.left, rv.bottom - rv.top, TRUE); return TRUE; } case WM_USER_CLOSE: PostQuitMessage(0); return TRUE; } return DefWindowProc(hWndDlg, Msg, wParam, lParam);; } HWND splash_dialog; void splash_thread_loop() { BT_SetTerminate(); splash_dialog = CreateDialog(hInst, MAKEINTRESOURCE(IDD_SPLASH), NULL, reinterpret_cast(splash_proc)); MSG msg; while (GetMessage(&msg, 0, 0, 0) > 0) { if (IsDialogMessage(splash_dialog, &msg)) { DispatchMessage(&msg); TranslateMessage(&msg); } } DestroyWindow(splash_dialog); } BOOL UnadjustWindowRectEx(LPRECT prc, DWORD dwStyle, BOOL fMenu, DWORD dwExStyle) { RECT rc; SetRectEmpty(&rc); BOOL fRc = AdjustWindowRectEx(&rc, dwStyle, fMenu, dwExStyle); if (fRc) { prc->left -= rc.left; prc->top -= rc.top; prc->right -= rc.right; prc->bottom -= rc.bottom; } return fRc; } void _pre_call_callback(const char* name, void* funcptr, int len_args, ...) { assert(App::I->is_render_thread()); } void _post_call_callback(const char* name, void* funcptr, int len_args, ...) { GLenum error_code; error_code = glad_glGetError(); if (error_code != GL_NO_ERROR) { LOG("ERROR %d in %s\n", error_code, name); } } int main(int argc, char** argv) { WNDCLASS wc; PIXELFORMATDESCRIPTOR pfd; App::I = new App(); App::I->initLog(); init_shcore_API(); init_ink_API(); if(SetProcessDpiAwareness_fn) SetProcessDpiAwareness_fn(PROCESS_PER_MONITOR_DPI_AWARE); FILE* fp_check = fopen("data\\layout.xml", "rb"); if (!fp_check) { LOG("data files not found"); static char path[MAX_PATH]; GetModuleFileNameA(NULL, path, MAX_PATH); LOG("current dir %s", path); PathRemoveFileSpecA(path); SetCurrentDirectoryA(path); LOG("change dir to %s", path); } else { fclose(fp_check); LOG("data files ok"); } std::thread dialog_thread(splash_thread_loop); init_vk_map(); SetupExceptionHandler(); BT_SetTerminate(); read_WMI_info(); App::I->create(); // Inizialize data structures to zero memset(&wc, 0, sizeof(wc)); memset(&keys, 0, sizeof(keys)); memset(&pfd, 0, sizeof(pfd)); // Create the main window hInst = GetModuleHandle(NULL); className = L"EngineMain"; wc.hInstance = hInst; wc.lpfnWndProc = (WNDPROC)WndProc; wc.lpszClassName = className; wc.hbrBackground = (HBRUSH)COLOR_WINDOW; wc.hCursor = LoadCursor(NULL, IDC_ARROW); RegisterClass(&wc); auto monitor = MonitorFromWindow(0, MONITOR_DEFAULTTOPRIMARY); auto x = unsigned{ 96 }; auto y = unsigned{ 96 }; if (GetDpiForMonitor_fn) GetDpiForMonitor_fn(monitor, MDT_EFFECTIVE_DPI, &x, &y); App::I->display_density = (float)x / 96.f; if (Settings::has("ui-scale")) App::I->zoom = Settings::value("ui-scale"); else App::I->zoom = (float)x / 96.f; int show_cmd = SW_NORMAL; Settings::value("window-show-cmd", show_cmd); DWORD wnd_style = WS_OVERLAPPEDWINDOW; //if (show_cmd == SW_MAXIMIZE) // wnd_style != WS_MAXIMIZE; RECT clientRect = { 0, 0, (int)App::I->width * App::I->zoom, (int)App::I->height * App::I->zoom }; POINT clientPos = { CW_USEDEFAULT, CW_USEDEFAULT }; if (Settings::has("window-rect")) { auto wnd_rect = Settings::value("window-rect"); App::I->width = wnd_rect.z - wnd_rect.x; App::I->height = wnd_rect.w - wnd_rect.y; clientRect = { wnd_rect.x, wnd_rect.y, wnd_rect.z, wnd_rect.w }; clientPos = { wnd_rect.x, wnd_rect.y }; } else { AdjustWindowRect(&clientRect, wnd_style, false); } hWnd = CreateWindow(wc.lpszClassName, L"PanoPainter", wnd_style, clientPos.x, clientPos.y, (float)(clientRect.right - clientRect.left), (float)(clientRect.bottom - clientRect.top), 0, 0, hInst, 0); // Setup GL Rendering Context pfd.nSize = sizeof(pfd); pfd.nVersion = 1; pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 32; pfd.cDepthBits = 24; pfd.iLayerType = PFD_MAIN_PLANE; hDC = GetDC(hWnd); int pxfmt = ChoosePixelFormat(hDC, &pfd); SetPixelFormat(hDC, pxfmt, &pfd); hRC = wglCreateContext(hDC); // Create OpenGL 2.1 or less wglMakeCurrent(hDC, hRC); // Initialize extensions if (!gladLoadGL()) { LOG("gladLoadGL() failed"); return 0; } if (!gladLoadWGL(hDC)) { LOG("gladLoadWGL() failed"); return 0; } LOG("GL version: %s", glGetString(GL_VERSION)); LOG("GL vendor: %s", glGetString(GL_VENDOR)); LOG("GL renderer: %s", glGetString(GL_RENDERER)); if (!win32_renderdoc_init()) LOG("Renderdoc not started"); swprintf_s(window_title, L"PanoPainter %s (%s)", g_version_number_w, str2wstr((char*)glGetString(GL_RENDERER)).c_str()); // If supported create a 3.3 context if (GLAD_WGL_ARB_create_context) { int contex_attribs[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, 3, WGL_CONTEXT_MINOR_VERSION_ARB, 3, WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB, WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB, 0 }; int pixel_attribs[] = { WGL_DRAW_TO_WINDOW_ARB, GL_TRUE, WGL_SUPPORT_OPENGL_ARB, GL_TRUE, WGL_DOUBLE_BUFFER_ARB, GL_TRUE, WGL_ACCELERATION_ARB,WGL_FULL_ACCELERATION_ARB, WGL_PIXEL_TYPE_ARB, WGL_TYPE_RGBA_ARB, WGL_COLOR_BITS_ARB, 24, WGL_DEPTH_BITS_ARB, 16, //WGL_STENCIL_BITS_ARB, 8, //WGL_SAMPLE_BUFFERS_ARB, 1, // Number of buffers (must be 1 at time of writing) //WGL_SAMPLES_ARB, 4, // Number of samples 0 }; UINT numFormat; wglMakeCurrent(NULL, NULL); wglDeleteContext(hRC); DestroyWindow(hWnd); hWnd = CreateWindow(wc.lpszClassName, window_title, wnd_style, clientPos.x, clientPos.y, (float)(clientRect.right - clientRect.left), (float)(clientRect.bottom - clientRect.top), 0, 0, hInst, 0); hDC = GetDC(hWnd); wglChoosePixelFormatARB(hDC, pixel_attribs, nullptr, 1, &pxfmt, &numFormat); SetPixelFormat(hDC, pxfmt, &pfd); hRC = wglCreateContextAttribsARB(hDC, NULL, contex_attribs); wglMakeCurrent(hDC, hRC); } else { LOG("WGL_ARB_create_context not supported"); // If not supported, go fuck yourself we are not gonna support your shitty device return -1; // A negative number because you are a negative one } //wglSwapIntervalEXT(1); bool start_in_vr = false; if (argc > 1) { switch (const_hash(argv[1])) { case const_hash("convert"): App::I->initShaders(); App::I->cmd_convert(argv[2], argv[3]); return 0; case const_hash("-vrmode"): start_in_vr = true; break; default: break; } } // link: https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-registertouchwindow if (RegisterTouchWindow(hWnd, 0) == 0) { LOG("RegisterTouchWindow error: %s", GetLastErrorAsString().c_str()); } wglMakeCurrent(NULL, NULL); running = 1; App::I->render_thread_start(); App::I->ui_thread_start(); #ifdef _DEBUG glad_set_pre_callback(_pre_call_callback); glad_set_post_callback(_post_call_callback); #endif if (!sandboxed) { LOG("init WinTab"); WacomTablet::I.init(hWnd); } else { LOG("SKIP init WinTab"); } LOG("change icon"); SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)LoadIcon(GetModuleHandle(0), MAKEINTRESOURCE(IDI_ICON1))); App::I->ui_sync(); { WINDOWPLACEMENT wp; GetWindowPlacement(hWnd, &wp); wp.showCmd = show_cmd; SetWindowPlacement(hWnd, &wp); //GetClientRect(hWnd, &clientRect); //App::I->width = clientRect.right - clientRect.left; //App::I->height = clientRect.bottom - clientRect.top; } if (start_in_vr) App::I->vr_start(); LOG("show main window"); SetForegroundWindow(hWnd); //ShowWindow(hWnd, show_cmd); SendMessage(splash_dialog, WM_USER_CLOSE, 0, 0); if (dialog_thread.joinable()) dialog_thread.join(); MSG msg; LOG("start main loop"); while (running == 1) { // If there any message in the queue process it auto present = App::I->animate || App::I->redraw ? PeekMessage(&msg, 0, 0, 0, PM_REMOVE) : GetMessage(&msg, 0, 0, 0); if (msg.message == WM_QUIT) running = 0; if (present) { DispatchMessage(&msg); TranslateMessage(&msg); } // list of tasks for the main thread { std::deque> working_list; { std::lock_guard lock(main_task_mutex); working_list = std::move(main_tasklist); } if (!working_list.empty()) { while (!working_list.empty()) { working_list.front()(); working_list.pop_front(); } } } } // Clean up WacomTablet::I.terminate(); UnregisterClass(className, hInst); LogRemote::I.stop(); } LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { static bool leftDown = false; static DWORD lastTime; static glm::vec2 lastPoint; auto extra = GetMessageExtraInfo(); // if ((extra & 0xFFFFFF00) == 0xFF515700) // LOG("source %s", extra & 0x80 ? "touch" : "pen"); switch (msg) { case WM_USER_CLOSE: running = 0; if (hmd_renderer.joinable()) hmd_renderer.join(); App::I->ui_thread_stop(); App::I->render_thread_stop(); App::I->terminate(); delete App::I; PostQuitMessage(0); return 0; case WM_PAINT: App::I->redraw = true; break; case WM_CREATE: BT_SetTerminate(); break; case WM_CLOSE: { App::I->ui_task_async([] { if (App::I->request_close()) { destroy_window(); } }); return 1; break; } case WM_SIZE: { auto w = (float)LOWORD(lp); auto h = (float)HIWORD(lp); if (h != 0 && running == 1) { App::I->ui_task_async([=] { App::I->resize(w, h); App::I->redraw = true; }, true); } break; } case WM_ACTIVATE: { win32_show_cursor(true); App::I->ui_task_async([=] { int active = GET_WM_ACTIVATE_STATE(wp, lp); WacomTablet::I.set_focus(active); static BYTE keys[256]; if (GetKeyboardState(keys)) { bool alt = keys[VK_MENU] & 0x80; for (auto k : vkey_map) { // ignore alt + tab if (alt && k.first == kKey::KeyTab) continue; bool down = keys[k.second] & 0x80; if (App::I->keys[(int)k.first] && !down) App::I->key_up(k.first); else if(!App::I->keys[(int)k.first] && down) App::I->key_down(k.first); } } }); break; } // case WM_TOUCH: // { // App::I->ui_task_async([=] { // //LOG("touch"); // }); // break; // } case WT_PACKET: { App::I->set_stylus(); timer_stylus = 0; App::I->ui_task_async([=] { WacomTablet::I.handle_message(hWnd, msg, wp, lp); }); break; } case WM_SYSKEYDOWN: case WM_KEYDOWN: if ((lp >> 30 & 1) == 0 && // ignore repeated !(wp == VK_TAB && App::I->keys[(int)kKey::KeyAlt])) // ignore alt+tab { App::I->ui_task_async([wp] { App::I->key_down(convert_key((int)wp)); }); } break; case WM_SYSKEYUP: case WM_KEYUP: if (!(wp == VK_TAB && App::I->keys[(int)kKey::KeyAlt])) // ignore alt+tab { App::I->ui_task_async([wp] { App::I->key_up(convert_key((int)wp)); }); } break; case WM_CHAR: { App::I->ui_task_async([wp]{ App::I->key_char((int)wp); }); } break; case WM_MOUSEMOVE: { /* RECT r; POINT curpos; GetWindowRect(hWnd, &r); glm::vec2 center = { r.left + (r.right - r.left) / 2, r.top + (r.bottom - r.top) / 2 }; GetCursorPos(&curpos); SetCursorPos(center.x, center.y); //glm::vec2 cur = { GET_X_LPARAM(lp), GET_Y_LPARAM(lp) }; glm::vec2 sz = { App::I->width, App::I->height }; glm::vec2 diff = { curpos.x - center.x, curpos.y - center.y }; lastPoint = glm::clamp(lastPoint + diff, { 0, 0 }, sz); */ lastPoint = { GET_X_LPARAM(lp), GET_Y_LPARAM(lp) }; auto pt = lastPoint; App::I->ui_task_async([pt, extra, p = WacomTablet::I.get_pressure()]{ kEventSource pointer_source; if (WacomTablet::I.m_ink_pen || WacomTablet::I.m_ink_touch) { pointer_source = WacomTablet::I.m_ink_pen ? kEventSource::Stylus : kEventSource::Touch; } else { pointer_source = WacomTablet::I.m_stylus ? kEventSource::Stylus : kEventSource::Mouse; if ((extra & 0xFFFFFF00) == 0xFF515700) pointer_source = kEventSource::Touch; } App::I->mouse_move((float)pt.x, (float)pt.y, p, pointer_source, WacomTablet::I.m_eraser); }); } break; case WM_LBUTTONDOWN: { SetCapture(hWnd); auto pt = lastPoint; App::I->ui_task_async([pt, extra, hWnd, p = WacomTablet::I.get_pressure()]{ kEventSource pointer_source; if (WacomTablet::I.m_ink_pen || WacomTablet::I.m_ink_touch) { pointer_source = WacomTablet::I.m_ink_pen ? kEventSource::Stylus : kEventSource::Touch; } else { pointer_source = WacomTablet::I.m_stylus ? kEventSource::Stylus : kEventSource::Mouse; if ((extra & 0xFFFFFF00) == 0xFF515700) pointer_source = kEventSource::Touch; } App::I->mouse_down(0, (float)pt.x, (float)pt.y, p, pointer_source, WacomTablet::I.m_eraser); }); } break; case WM_LBUTTONUP: { ReleaseCapture(); auto pt = lastPoint; App::I->ui_task_async([pt, extra] { WacomTablet::I.reset_pressure(); kEventSource pointer_source; if (WacomTablet::I.m_ink_pen || WacomTablet::I.m_ink_touch) { pointer_source = WacomTablet::I.m_ink_pen ? kEventSource::Stylus : kEventSource::Touch; } else { pointer_source = WacomTablet::I.m_stylus ? kEventSource::Stylus : kEventSource::Mouse; if ((extra & 0xFFFFFF00) == 0xFF515700) pointer_source = kEventSource::Touch; } App::I->mouse_up(0, (float)pt.x, (float)pt.y, pointer_source, WacomTablet::I.m_eraser); }); } break; case WM_RBUTTONDOWN: { SetCapture(hWnd); auto pt = lastPoint; App::I->ui_task_async([pt, extra, hWnd] { kEventSource pointer_source; if (WacomTablet::I.m_ink_pen || WacomTablet::I.m_ink_touch) { pointer_source = WacomTablet::I.m_ink_pen ? kEventSource::Stylus : kEventSource::Touch; } else { pointer_source = WacomTablet::I.m_stylus ? kEventSource::Stylus : kEventSource::Mouse; if ((extra & 0xFFFFFF00) == 0xFF515700) pointer_source = kEventSource::Touch; } App::I->mouse_down(1, (float)pt.x, (float)pt.y, 1.f, pointer_source, 0); }); } break; case WM_RBUTTONUP: { ReleaseCapture(); auto pt = lastPoint; App::I->ui_task_async([pt, extra] { kEventSource pointer_source; if (WacomTablet::I.m_ink_pen || WacomTablet::I.m_ink_touch) { pointer_source = WacomTablet::I.m_ink_pen ? kEventSource::Stylus : kEventSource::Touch; } else { pointer_source = WacomTablet::I.m_stylus ? kEventSource::Stylus : kEventSource::Mouse; if ((extra & 0xFFFFFF00) == 0xFF515700) pointer_source = kEventSource::Touch; } App::I->mouse_up(1, (float)pt.x, (float)pt.y, pointer_source, 0); }); } break; case WM_MOUSEWHEEL: { POINT pt; pt.x = GET_X_LPARAM(lp); pt.y = GET_Y_LPARAM(lp); ScreenToClient(hWnd, &pt); { App::I->ui_task_async([pt, wp] { App::I->mouse_scroll((float)pt.x, (float)pt.y, (float)GET_WHEEL_DELTA_WPARAM(wp) / (float)WHEEL_DELTA); }); } break; } case WM_POINTERUPDATE: { POINTER_TOUCH_INFO touchInfo; POINTER_PEN_INFO penInfo; POINTER_INFO pointerInfo; UINT32 pointerId = GET_POINTERID_WPARAM(wp); POINTER_INPUT_TYPE pointerType = PT_POINTER; if(!GetPointerInfo_fn) break; // Retrieve common pointer information if (!GetPointerInfo_fn(pointerId, &pointerInfo)) { // failure, call GetLastError() } else { // success, process pointerInfo if (!GetPointerType_fn(pointerId, &pointerType)) { // failure, call GetLastError() // set PT_POINTER to fall to default case below pointerType = PT_POINTER; } switch (pointerType) { case PT_TOUCH: // Retrieve touch information if (!GetPointerTouchInfo_fn(pointerId, &touchInfo)) { // failure, call GetLastError() } else { // success, process touchInfo // mark as handled to skip call to DefWindowProc //fHandled = TRUE; timer_ink_touch = 0; WacomTablet::I.m_ink_touch = true; WacomTablet::I.m_pen_pres = 1; } break; case PT_PEN: // Retrieve pen information if (!GetPointerPenInfo_fn(pointerId, &penInfo)) { // failure, call GetLastError() } else { // success, process penInfo // mark as handled to skip call to DefWindowProc //fHandled = TRUE; //penInfo.pressure timer_ink_pen = 0; WacomTablet::I.m_ink_pen = true; WacomTablet::I.m_pen_pres = (float)penInfo.pressure / 1024.f; App::I->set_stylus(); } break; default: if (!GetPointerInfo_fn(pointerId, &pointerInfo)) { // failure. } else { // success, proceed with pointerInfo. //fHandled = HandleGenericPointerInfo(&pointerInfo); } break; } } break; } } // avoid annoying alt system menu if (wp == SC_KEYMENU && (lp >> 16) <= 0) return 0; return DefWindowProc(hWnd, msg, wp, lp); } int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int) { int argc = 0; // convert args from char to wchar auto wargs = CommandLineToArgvW(GetCommandLine(), &argc); auto argv = new char*[argc + 1]; for (int i = 0; i < argc; i++) { auto len = wcslen(wargs[i]) + 1; argv[i] = new char[len]; wcstombs_s(nullptr, argv[i], len, wargs[i], len); } LocalFree(wargs); return main(argc, argv); }