// DVIVue.cpp : Defines the entry point for the application. // /* DVIVue: A viewer for DVI files and frontend for dvipdfmx. Copyright (c) 2020 by Peter Frane Jr. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . The author may be contacted via the e-mail address pfranejr@hotmail.com Please refer to the following URL for further instructions on how to download the packages required by WebView2: */ #include "framework.h" #include "DVIVue.h" #include #include using namespace std; #pragma comment(lib, "Shlwapi.lib") #define MAX_LOADSTRING 100 using namespace Microsoft::WRL; //using namespace std; // Global Variables: HINSTANCE hInst; // current instance WCHAR szTitle[MAX_LOADSTRING]; // The title bar text WCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name // Pointer to WebViewController static wil::com_ptr webviewController; // Pointer to WebView window static wil::com_ptr webviewWindow; static wil::com_ptr webEnvironment; char log_file[MAX_PATH + 1]{ 0 }; // Forward declarations of functions included in this code module: ATOM MyRegisterClass(HINSTANCE hInstance); BOOL InitInstance(HINSTANCE, int); LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM); void resize_window(HWND hWnd); void process_command_line(HWND hWnd); int on_create(HWND hWnd); bool create_environment(HWND hWnd); bool file_exists(const char* filename); int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { UNREFERENCED_PARAMETER(hPrevInstance); UNREFERENCED_PARAMETER(lpCmdLine); // TODO: Place code here. // Initialize global strings LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING); LoadStringW(hInstance, IDC_DVIVUE, szWindowClass, MAX_LOADSTRING); MyRegisterClass(hInstance); // Perform application initialization: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; } HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_DVIVUE)); MSG msg; // Main message loop: while (GetMessage(&msg, nullptr, 0, 0)) { if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return (int) msg.wParam; } // // FUNCTION: MyRegisterClass() // // PURPOSE: Registers the window class. // ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEXW 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, MAKEINTRESOURCE(IDI_DVIVUE)); wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_DVIVUE); wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); return RegisterClassExW(&wcex); } // // FUNCTION: InitInstance(HINSTANCE, int) // // PURPOSE: Saves instance handle and creates main window // // COMMENTS: // // In this function, we save the instance handle in a global variable and // create and display the main program window. // BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { hInst = hInstance; // Store instance handle in our global variable HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr); if (!hWnd) { return FALSE; } ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); if (!create_environment(hWnd)) { DestroyWindow(hWnd); return FALSE; } return TRUE; } bool create_environment(HWND hWnd) { HRESULT hr; wchar_t temp_path[MAX_PATH + 1]{ 0 }; if (GetTempPath(MAX_PATH, temp_path) == 0) { MessageBox(hWnd, L"Unable to obtain the TEMP path", L"Internal Error", MB_OK); return false; } // To use a fixed version of WebView2, // pass the folder name of the WebView2 runtime as the first parameter of CreateCoreWebView2EnvironmentWithOptions; // otherwise, use nullptr to use an installed version of the runtime. // For more info see the following link: https://docs.microsoft.com/en-us/microsoft-edge/webview2/reference/win32/webview2-idl?view=webview2-0.9.622#createcorewebview2environmentwithoptions hr = CreateCoreWebView2EnvironmentWithOptions(nullptr, temp_path, nullptr, Callback( [hWnd](HRESULT result, ICoreWebView2Environment* env) -> HRESULT { // Create a CoreWebView2Controller and get the associated CoreWebView2 whose parent is the main window hWnd env->CreateCoreWebView2Controller(hWnd, Callback( [hWnd](HRESULT result, ICoreWebView2Controller* controller) -> HRESULT { if (controller != nullptr) { webviewController = controller; if (webviewController->get_CoreWebView2(&webviewWindow) == S_OK) { // Resize WebView to fit the bounds of the parent window resize_window(hWnd); process_command_line(hWnd); } } return S_OK; }).Get()); return S_OK; }).Get()); return (S_OK == hr); } // read the messages of dvipdfx void read_pipe(HWND hWnd, HANDLE child_stdout_read) { HANDLE hlog; hlog = CreateFileA(log_file, GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); if (INVALID_HANDLE_VALUE == hlog) { MessageBox(hWnd, L"Unable to create the log file", L"Internal Error", MB_OK); return; } else { const size_t BUFSIZE = 4096; DWORD dw_read, dw_written; char buf[BUFSIZE]; BOOL success = FALSE; while (true) { success = ReadFile(child_stdout_read, buf, BUFSIZE, &dw_read, NULL); if (!success || dw_read == 0) break; success = WriteFile(hlog, buf, dw_read, &dw_written, NULL); if (!success) break; } CloseHandle(hlog); } } struct handle { HANDLE m_handle{ INVALID_HANDLE_VALUE }; void close() { if (m_handle != INVALID_HANDLE_VALUE) { CloseHandle(m_handle); m_handle = INVALID_HANDLE_VALUE; } } ~handle() { close(); } }; void show_pdf_file(HWND hWnd, const char *filename, const char *title) { wchar_t pdf_file[MAX_PATH + 1]{ 0 }; MultiByteToWideChar(CP_UTF8, 0, filename, lstrlenA(filename), pdf_file, MAX_PATH); if (webviewWindow->Navigate(pdf_file) == S_OK) { SetWindowTextA(hWnd, title); } } bool file_exists(const char* filename) { WIN32_FILE_ATTRIBUTE_DATA attrib{ 0 }; GetFileAttributesExA(filename, GetFileExInfoStandard, &attrib); if (attrib.nFileSizeLow > 0 || attrib.nFileSizeHigh > 0) { return true; } return false; } void create_pdf(HWND hWnd, const char* dvi_file) { PROCESS_INFORMATION pi{ 0 }; STARTUPINFOA si{ 0 }; char param[MAX_PATH * 2 + 1]{ "dvipdfmx.exe -o " }; char fname[_MAX_FNAME + 1]{ 0 }; char temp_pdf_path[MAX_PATH + 1]{ 0 }; handle child_stdout_read; handle child_stdout_write; SECURITY_ATTRIBUTES sa{ 0 }; sa.nLength = sizeof(sa); sa.bInheritHandle = TRUE; if (!CreatePipe(&child_stdout_read.m_handle, &child_stdout_write.m_handle, &sa, 0)) { MessageBox(hWnd, L"Unable to create a pipe", L"Internal Error", MB_OK); return; } else { SetHandleInformation(child_stdout_read.m_handle, HANDLE_FLAG_INHERIT, 0); } if (_splitpath_s(dvi_file, 0, 0, 0, 0, fname, _MAX_FNAME, nullptr, 0) != 0) { MessageBox(hWnd, L"Unable to split the path", L"Internal Error", MB_OK); return; } if (GetTempPathA(MAX_PATH, temp_pdf_path) == 0) { MessageBox(hWnd, L"Unable to obtain the TEMP path", L"Internal Error", MB_OK); return; } else if (!PathFileExistsA(temp_pdf_path)) { MessageBox(hWnd, L"The TEMP path folder is not available", L"Internal Error", MB_OK); return; } lstrcatA(log_file, temp_pdf_path); lstrcatA(log_file, fname); lstrcatA(log_file, ".log"); lstrcatA(temp_pdf_path, fname); lstrcatA(temp_pdf_path, ".pdf"); // enclose the parameters in quotes lstrcatA(param, "\""); lstrcatA(param, temp_pdf_path); // enclose the parameters in quotes lstrcatA(param, "\" \""); lstrcatA(param, dvi_file); // close the quote lstrcatA(param, "\""); si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW; //si.hStdInput = child_stdin_read.m_handle; si.hStdOutput = child_stdout_write.m_handle; si.hStdError = child_stdout_write.m_handle; si.wShowWindow = SW_HIDE; if (!CreateProcessA(nullptr, param, nullptr, nullptr, TRUE, CREATE_NO_WINDOW, nullptr, nullptr, &si, &pi)) { MessageBox(hWnd, L"Unable to execute 'dvipdfmx.exe", L"Error", MB_OK); return; } else { DWORD exit_code = 0; WaitForSingleObject(pi.hProcess, INFINITE); GetExitCodeProcess(pi.hProcess, &exit_code); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); child_stdout_write.close(); read_pipe(hWnd, child_stdout_read.m_handle); child_stdout_read.close(); if (exit_code != 0) { MessageBox(hWnd, L"'dvipdfmx.exe returned an error.\nPlease see the log file in the File menu", L"Error", MB_OK); return; } else { if (file_exists(temp_pdf_path)) { const char* ext = strrchr(dvi_file, '.'); // can't fail, but check anyway if (ext) { lstrcatA(fname, ext); } show_pdf_file(hWnd, temp_pdf_path, fname); } } } return; } void clear_log_file() { log_file[0] = 0; } void open_file(HWND hWnd) { OPENFILENAMEA ofn = { 0 }; char filename[MAX_PATH + 1] = { 0 }; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = hWnd; ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST; ofn.lpstrFilter = "DVI\0*.dvi\0\0"; ofn.lpstrFile = filename; ofn.nMaxFile = MAX_PATH; if (GetOpenFileNameA(&ofn) == TRUE) { create_pdf(hWnd, ofn.lpstrFile); } } void resize_window(HWND hWnd) { if (webviewController != nullptr) { RECT bounds; GetClientRect(hWnd, &bounds); webviewController->put_Bounds(bounds); } } void process_command_line(HWND hWnd) { LPWSTR* arg_list; int argc; arg_list = CommandLineToArgvW(GetCommandLineW(), &argc); if (nullptr == arg_list) { MessageBox(hWnd, L"CommandLineToArgvW failed", L"Error", MB_OK); return; } else if (argc > 1) { char dvi_file[MAX_PATH + 1]{ 0 }; int result; result = WideCharToMultiByte(CP_UTF8, 0, arg_list[1], -1, dvi_file, (int)MAX_PATH, 0, FALSE); LocalFree(arg_list); if (result > 0) { if (!file_exists(dvi_file)) { lstrcatA(dvi_file, ".dvi"); if (!file_exists(dvi_file)) { MessageBoxA(hWnd, dvi_file, "File not found", MB_OK); return; } } create_pdf(hWnd, dvi_file); } else { MessageBox(hWnd, L"Unable to convert a string from wide char to multibyte", L"Internal Error", MB_OK); return; } } } void run_application(HWND hWnd, const wchar_t *app_name, const wchar_t *input_filename) { PROCESS_INFORMATION pi{ 0 }; STARTUPINFOW si{ 0 }; wchar_t param[MAX_PATH *2 + 1]{0}; si.cb = sizeof(si); lstrcpyW(param, app_name); lstrcatW(param, L" "); if (input_filename) { lstrcatW(param, input_filename); } if (!CreateProcessW(nullptr, param, nullptr, nullptr, false, 0, nullptr, nullptr, &si, &pi)) { wchar_t msg[MAX_PATH + 1]{ L"Unable to execute " }; lstrcatW(msg, app_name); MessageBoxW(hWnd, msg, L"Error", MB_OK); return; } CloseHandle(pi.hProcess); CloseHandle(pi.hThread); } void view_log_file(HWND hWnd) { wchar_t input_filename[MAX_PATH + 1]{ 0 }; MultiByteToWideChar(CP_UTF8, 0, log_file, -1, input_filename, MAX_PATH); run_application(hWnd, L"notepad.exe", input_filename); } void new_instance(HWND hWnd, const wchar_t* input_filename) { wchar_t app_name[MAX_PATH + 1]{ 0 }; GetModuleFileNameW(nullptr, app_name, MAX_PATH); run_application(hWnd, app_name, input_filename); } void close_file(HWND hWnd) { webviewWindow->Navigate(L"about:blank"); clear_log_file(); SetWindowText(hWnd, L"DVIVue"); } // // FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM) // // PURPOSE: Processes messages for the main window. // // WM_COMMAND - process the application menu // WM_PAINT - Paint the main window // WM_DESTROY - post a quit message and return // // LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_COMMAND: { int wmId = LOWORD(wParam); // Parse the menu selections: switch (wmId) { case IDM_ABOUT: DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About); break; case IDM_EXIT: DestroyWindow(hWnd); break; case IDM_FILE_NEW: new_instance(hWnd, nullptr); break; case IDM_FILE_OPEN: open_file(hWnd); break; case IDM_FILE_CLOSE: close_file(hWnd); break; case IDM_FILE_VIEW_LOG: view_log_file(hWnd); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } } break; case WM_INITMENU: { HMENU hmenu = (HMENU)wParam; if (log_file[0] != 0) { EnableMenuItem(hmenu, IDM_FILE_VIEW_LOG, MF_ENABLED); } else { EnableMenuItem(hmenu, IDM_FILE_VIEW_LOG, MF_DISABLED | MF_GRAYED); } } break; case WM_SIZE: resize_window(hWnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } // Message handler for about box. INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { UNREFERENCED_PARAMETER(lParam); switch (message) { case WM_INITDIALOG: return (INT_PTR)TRUE; case WM_COMMAND: if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL) { EndDialog(hDlg, LOWORD(wParam)); return (INT_PTR)TRUE; } break; } return (INT_PTR)FALSE; }