#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 "wacom.h" #include #include #include "abr.h" #include "settings.h" #define WM_USER_CLOSE (WM_USER + 1) 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; std::thread hmd_renderer; std::thread ui_renderer; int vr_frames = 0; int running = -1; int vr_running = 0; std::mutex ui_render_mutex; std::condition_variable ui_render_cv; std::deque> render_tasklist; std::mutex render_task_mutex; std::condition_variable render_cv; std::thread render_thread; std::thread::id render_thread_id; bool render_running = false; int gl_count = 0; std::deque> tasklist; std::mutex task_mutex; 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; 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([=] { SendMessage(hWnd, WM_USER_CLOSE, 0, 0); }); } bool is_render_thread() { extern std::thread::id render_thread_id; extern std::thread::id gl_thread; return std::this_thread::get_id() == render_thread_id || std::this_thread::get_id() == gl_thread; } template::type> std::future render_task_async(T task) { #ifdef _WIN32 extern std::deque> render_tasklist; extern std::mutex render_task_mutex; extern std::condition_variable render_cv; std::packaged_task pt(task); std::future f = pt.get_future(); if (is_render_thread()) { pt(); } else { { std::lock_guard lock(render_task_mutex); render_tasklist.push_back(std::move(pt)); } render_cv.notify_all(); } return f; #endif // _WIN32 } template::type> R render_task(T task) { #ifdef _WIN32 extern std::deque> render_tasklist; extern std::mutex render_task_mutex; extern std::condition_variable render_cv; std::packaged_task pt(task); std::future f = pt.get_future(); if (is_render_thread()) { pt(); } else { { std::lock_guard lock(render_task_mutex); render_tasklist.push_back(std::move(pt)); } render_cv.notify_all(); } return f.get(); #endif // _WIN32 } 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 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_render_thread_notify() { ui_render_cv.notify_all(); } 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_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) { auto path = App::I.data_path + "/recovery.ppi"; Canvas::I->project_save_thread(path); 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; } 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); 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) { async_lock(); vive->Draw(); async_unlock(); } 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; if (async_lock_try()) { vive->Terminate(); async_unlock(); } 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 + 1: PostQuitMessage(0); return TRUE; } return DefWindowProc(hWndDlg, Msg, wParam, lParam);; } HWND splash_dialog; void splash_thread_loop() { 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 render_thread_main() { uint32_t count = 0; render_thread_id = std::this_thread::get_id(); render_running = true; while (render_running == 1) { std::deque> working_list; // move the task list locally to free the queue for other threads { std::unique_lock lock(render_task_mutex); render_cv.wait(lock, [] { return render_tasklist.empty() && render_running ? false : true; }); working_list = std::move(render_tasklist); } //{ // std::lock_guard lock(task_mutex); // working_list.insert(working_list.end(), // std::make_move_iterator(tasklist.begin()), // std::make_move_iterator(tasklist.end())); // tasklist.clear(); //} // execute the tasks if (!working_list.empty()) { async_lock(); while (!working_list.empty()) { LOG("render task %d", count); count++; working_list.front()(); working_list.pop_front(); } async_unlock(); } } } int main(int argc, char** argv) { WNDCLASS wc; PIXELFORMATDESCRIPTOR pfd; 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.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 (glewInit() != GLEW_OK) return 0; LOG("GL version: %s", glGetString(GL_VERSION)); LOG("GL vendor: %s", glGetString(GL_VENDOR)); LOG("GL renderer: %s", glGetString(GL_RENDERER)); static wchar_t window_title[512]; swprintf_s(window_title, L"PanoPainter %s (%s)", g_version_number_w, str2wstr((char*)glGetString(GL_RENDERER)).c_str()); // If supported create a 3.1 context if (wglewIsSupported("WGL_ARB_create_context")) { int contex_attribs[] = { WGL_CONTEXT_MAJOR_VERSION_ARB, 3, WGL_CONTEXT_MINOR_VERSION_ARB, 1, 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, 32, WGL_DEPTH_BITS_ARB, 24, //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; render_thread = std::thread(render_thread_main); LOG("init app"); App::I.init(); 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))); ui_renderer = std::thread([&] { BT_SetTerminate(); LOG("start render thread"); const float target_fps = 10; const float target_tick_rate = 10; unsigned long t0 = GetTickCount64(); unsigned long t1; bool first_frame = true; int frames = 0; float one_sec = 0; float render_timer = 0; float frame_timer = 0; while(running == 1) { t1 = GetTickCount64(); float dt = (float)(t1 - t0) / 1000.0f; one_sec += dt; render_timer += dt; frame_timer += dt; timer_stylus += dt; timer_ink_touch += dt; timer_ink_pen += dt; if (one_sec > 1.f) { 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); }); one_sec = 0; frames = 0; } { std::deque> working_list; { std::lock_guard lock(task_mutex); working_list = std::move(tasklist); } if (!working_list.empty()) { while (!working_list.empty()) { working_list.front()(); working_list.pop_front(); } //LOG("clear"); //WacomTablet::I.m_stylus = false; //WacomTablet::I.m_eraser = false; } } if (first_frame) { first_frame = false; 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; } App::I.tick(dt); std::unique_lock lock(ui_render_mutex); if (render_timer > 1.0f / target_fps) { App::I.redraw = true; render_timer = 0; } 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; } if (App::I.redraw) { App::I.update(frame_timer); render_task([frame_timer] { glBindFramebuffer(GL_FRAMEBUFFER, 0); App::I.clear(); App::I.draw(frame_timer); SwapBuffers(hDC); }); frame_timer = 0; frames++; } const int framerate = (1.f / target_tick_rate) * 1000; const int diff = framerate - (t1 - t0); //render_cv.wait_for(lock, std::chrono::milliseconds(diff)); //std::this_thread::sleep_for(std::chrono::milliseconds(30)); t0 = t1; } LOG("renderer terminated"); }); SetTimer(hWnd, 1, 500, NULL); if (start_in_vr) App::I.vr_start(); LOG("show main window"); SetForegroundWindow(hWnd); //ShowWindow(hWnd, show_cmd); SendMessage(splash_dialog, WM_USER + 1, 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(); } } } if (!tasklist.empty()) ui_render_cv.notify_all(); } // Clean up WacomTablet::I.terminate(); render_cv.notify_all(); 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; ui_render_cv.notify_all(); if (ui_renderer.joinable()) ui_renderer.join(); if (hmd_renderer.joinable()) hmd_renderer.join(); App::I.terminate(); PostQuitMessage(0); render_running = false; if (render_thread.joinable()) render_thread.join(); return 0; case WM_PAINT: App::I.redraw = true; break; case WM_CREATE: BT_SetTerminate(); break; case WM_CLOSE: { std::lock_guard lock(task_mutex); tasklist.emplace_back([=] { 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) { std::lock_guard lock(task_mutex); tasklist.emplace_back([=] { App::I.resize(w, h); App::I.redraw = true; }); } break; } case WM_ACTIVATE: { std::lock_guard lock(task_mutex); tasklist.emplace_back([=] { 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: // { // std::lock_guard lock(task_mutex); // tasklist.emplace_back([=] { // //LOG("touch"); // }); // break; // } case WT_PACKET: { App::I.set_stylus(); timer_stylus = 0; std::lock_guard lock(task_mutex); tasklist.emplace_back([=] { 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 { std::lock_guard lock(task_mutex); tasklist.emplace_back([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 { std::lock_guard lock(task_mutex); tasklist.emplace_back([wp] { App::I.key_up(convert_key((int)wp)); }); } break; case WM_CHAR: { std::lock_guard lock(task_mutex); tasklist.emplace_back([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; std::lock_guard lock(task_mutex); tasklist.emplace_back([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); std::lock_guard lock(task_mutex); auto pt = lastPoint; tasklist.emplace_back([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(); std::lock_guard lock(task_mutex); auto pt = lastPoint; tasklist.emplace_back([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); std::lock_guard lock(task_mutex); auto pt = lastPoint; tasklist.emplace_back([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(); std::lock_guard lock(task_mutex); auto pt = lastPoint; tasklist.emplace_back([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); { std::lock_guard lock(task_mutex); tasklist.emplace_back([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); }