// // Created by WolverinDEV on 21/04/2020. // #include #include #include #include #include #include #include #include #include #include #include #include #include "../ui.h" struct BlockingProcess { RM_APP_TYPE type{}; std::wstring name{}; std::wstring exe_path{}; int pid{}; }; bool blocking_processes(std::vector& result, std::wstring& error, const std::wstring& file) { DWORD dwSession{0}; WCHAR szSessionKey[CCH_RM_SESSION_KEY + 1] = { 0 }; DWORD dwError = RmStartSession(&dwSession, 0, szSessionKey); if(dwError != ERROR_SUCCESS) { error = L"Failed to start rm session (" + std::to_wstring(dwError) + L")"; goto error_exit; } PCWSTR pszFile = file.data(); dwError = RmRegisterResources(dwSession, 1, &pszFile, 0, nullptr, 0, nullptr); if(dwError != ERROR_SUCCESS) { error = L"Failed to register resource (" + std::to_wstring(dwError) + L")"; goto error_exit; } DWORD dwReason; UINT i; UINT nProcInfoNeeded; UINT nProcInfo = 10; RM_PROCESS_INFO rgpi[10]; dwError = RmGetList(dwSession, &nProcInfoNeeded, &nProcInfo, rgpi, &dwReason); if(dwError != ERROR_SUCCESS) { error = L"Failed to get list from rm (" + std::to_wstring(dwError) + L")"; goto error_exit; } result.reserve(nProcInfo); for (i = 0; i < nProcInfo; i++) { auto& info = result.emplace_back(); info.type = rgpi[i].ApplicationType; info.name = rgpi[i].strAppName; info.pid = rgpi[i].Process.dwProcessId; info.exe_path = L"unknown"; HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, rgpi[i].Process.dwProcessId); if (hProcess) { FILETIME ftCreate, ftExit, ftKernel, ftUser; if (GetProcessTimes(hProcess, &ftCreate, &ftExit, &ftKernel, &ftUser) && CompareFileTime(&rgpi[i].Process.ProcessStartTime, &ftCreate) == 0) { WCHAR sz[MAX_PATH]; DWORD cch{MAX_PATH}; if (QueryFullProcessImageNameW(hProcess, 0, sz, &cch) && cch <= MAX_PATH) { info.exe_path = sz; } } CloseHandle(hProcess); } } RmEndSession(dwSession); return true; error_exit: if(dwSession) RmEndSession(dwSession); return false; } #if 0 enum LabelDefault { LABEL_MAX }; enum BrushDefault { BRUSH_MAX }; template class Win32Window { public: template using WithLabels = Win32Window; template using WithBrush = Win32Window; Win32Window(); protected: std::array hLabel{}; std::array hBrush{}; }; static void a() { auto window = new Win32Window(); } #endif class FileInUseWindow { public: static constexpr auto ClassName = "FileInUseWindow"; static bool register_class(); explicit FileInUseWindow(HWND); virtual ~FileInUseWindow(); bool initialize(); void finalize(); void set_file(const std::wstring&); void update_blocking_info(); ui::FileBlockedResult result{ui::FileBlockedResult::UNSET}; bool deleteOnClose{true}; private: static constexpr auto kBackgroundColor = RGB(240, 240, 240); static LRESULT CALLBACK window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam); INT_PTR window_proc_color_static(HWND hElement, HDC hDC); INT_PTR window_proc_command(HWND hwnd, DWORD cID); void set_blocking_info(const std::wstring_view&, const std::vector&); enum Label { LABEL_FIRST_LINE, LABEL_FILE_NAME, LABEL_SECOND_LINE, LABEL_MAX }; enum Brush { BRUSH_BACKGROUND, BRUSH_MAX }; enum Font { FONT_TEXT, FONT_FILE_NAME, FONT_PROCESS_INFO, FONT_MAX }; enum Tooltip { TOOLTIP_FILE, TOOLTIP_MAX }; enum Button { BUTTON_CANCEL, BUTTON_CONTINUE, BUTTON_REFRESH, BUTTON_MAX }; HWND hWindow; std::array hLabels{}; std::array hTooltips{}; std::array hButton{}; HWND hListBox{}; std::array hBrush{}; std::array hFont{}; std::wstring blocking_file{}; bool window_active{false}; bool update_exit{false}; bool force_update{false}; std::thread update_thread{}; std::condition_variable update_cv{}; std::mutex update_mutex{}; }; bool FileInUseWindow::register_class() { WNDCLASS wc = {}; wc.lpfnWndProc = FileInUseWindow::window_proc; wc.hInstance = GetModuleHandle(nullptr); wc.lpszClassName = "FileInUseWindow"; return RegisterClass(&wc); } LRESULT FileInUseWindow::window_proc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { auto window = (FileInUseWindow*) GetWindowLongPtr(hwnd, GWLP_USERDATA); switch(uMsg) { case WM_CREATE: window = new FileInUseWindow(hwnd); if(!window->initialize()) assert(false); SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR) window); /* Certain window data is cached, so changes you make using SetWindowLongPtr will not take effect until you call the SetWindowPos function */ SetWindowPos(hwnd, nullptr, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER); return 0; case WM_DESTROY: window->finalize(); if(window->deleteOnClose) delete window; PostQuitMessage(0); return 0; case WM_CLOSE: if(window->result == ui::FileBlockedResult::UNSET) { if (MessageBoxW(hwnd, L"Do you really want to cancel the update?", L"Are you sure?", MB_OKCANCEL) == IDOK) { window->result = ui::FileBlockedResult::CANCELED; DestroyWindow(hwnd); } } else { DestroyWindow(hwnd); } return 0; case WM_CTLCOLORSTATIC: return window->window_proc_color_static((HWND) lParam, (HDC) wParam); case WM_COMMAND: return window->window_proc_command((HWND) lParam, LOWORD(wParam)); case WM_ACTIVATE: if(!window) return 0; if(wParam == WA_INACTIVE) { window->window_active = false; } else if(wParam == WA_ACTIVE) { window->window_active = true; window->force_update = true; window->update_cv.notify_all(); /* update the list */ } return 0; default: return DefWindowProcA(hwnd, uMsg, wParam, lParam); } } HWND CreateToolTip(HWND window, HWND hwnd, PTSTR pszText) { HWND hwndTip = CreateWindowEx(NULL, TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_ALWAYSTIP | TTS_BALLOON, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, hwnd, NULL, (HINSTANCE) GetWindowLongPtr(hwnd, GWLP_WNDPROC), NULL); if(!hwndTip) return nullptr; // Associate the tooltip with the tool. TOOLINFO toolInfo = { 0 }; toolInfo.cbSize = sizeof(toolInfo); toolInfo.uId = 22; toolInfo.hwnd = hwnd; toolInfo.uFlags = TTF_IDISHWND | TTF_SUBCLASS; toolInfo.lpszText = pszText; SendMessage(hwndTip, TTM_ADDTOOL, 0, (LPARAM)&toolInfo); SendMessage(hwndTip, TTM_ACTIVATE, TRUE, 0); return hwndTip; } HWND CreateAHorizontalScrollBar(HWND hwndParent, int sbHeight) { RECT rect; // Get the dimensions of the parent window's client area; if (!GetClientRect(hwndParent, &rect)) return NULL; // Create the scroll bar. return (CreateWindowExW( 0, // no extended styles L"SCROLLBAR", // scroll bar control class nullptr, // no window text WS_CHILD | WS_VISIBLE // window styles | SBS_HORZ, // horizontal scroll bar style rect.left, // horizontal position rect.bottom - sbHeight - 12, // vertical position rect.right, // width of the scroll bar sbHeight, // height of the scroll bar hwndParent, // handle to main window (HMENU) NULL, // no menu (HINSTANCE) GetWindowLongPtr(hwndParent, GWLP_WNDPROC), // instance owning this window (PVOID) NULL // pointer not needed )); } FileInUseWindow::FileInUseWindow(HWND hwnd) : hWindow{hwnd} {} FileInUseWindow::~FileInUseWindow() = default; bool FileInUseWindow::initialize() { ::SetWindowLong(this->hWindow, GWL_STYLE, GetWindowLong(this->hWindow, GWL_STYLE) & ~ (WS_SIZEBOX | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)); this->hBrush[Brush::BRUSH_BACKGROUND] = CreateSolidBrush(kBackgroundColor); this->hFont[Font::FONT_TEXT] = CreateFontW(16, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Arial"); this->hFont[Font::FONT_FILE_NAME] = CreateFontW(16, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Consolas"); this->hFont[Font::FONT_PROCESS_INFO] = CreateFontW(16, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"Consolas"); SetClassLongPtr(this->hWindow, GCLP_HBRBACKGROUND, (LONG_PTR) this->hBrush[Brush::BRUSH_BACKGROUND]); { this->hLabels[Label::LABEL_FIRST_LINE] = CreateWindow(WC_STATIC, "Failed to unlock the following file:", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 10, 10, 400, 20, this->hWindow, nullptr, (HINSTANCE) GetWindowLongPtr(this->hWindow, GWLP_WNDPROC), nullptr); this->hLabels[Label::LABEL_FILE_NAME] = CreateWindow(WC_STATIC, "", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 10, 30, 400, 20, this->hWindow, nullptr, (HINSTANCE) GetWindowLongPtr(this->hWindow, GWLP_WNDPROC), nullptr); this->hTooltips[Tooltip::TOOLTIP_FILE] = CreateToolTip(this->hWindow, this->hLabels[Label::LABEL_FILE_NAME], "Hello World"); //FIXME: Tooltip not working... this->hLabels[Label::LABEL_SECOND_LINE] = CreateWindow(WC_STATIC, "Please close these processes:", WS_CHILD | WS_VISIBLE | WS_TABSTOP, 10, 60, 400, 20, this->hWindow, nullptr, (HINSTANCE) GetWindowLongPtr(this->hWindow, GWLP_WNDPROC), nullptr); for(auto& hLabel : this->hLabels) SendMessage(hLabel, WM_SETFONT, (WPARAM) this->hFont[Font::FONT_TEXT], TRUE); SendMessage(this->hLabels[Label::LABEL_FILE_NAME], WM_SETFONT, (WPARAM) this->hFont[Font::FONT_FILE_NAME], TRUE); } { this->hListBox = CreateWindow("ListBox", nullptr, WS_VISIBLE | WS_CHILD | LBS_STANDARD | LBS_NOTIFY | WS_HSCROLL , 10, 80, 565, 200, this->hWindow, nullptr, (HINSTANCE) GetWindowLongPtr(this->hWindow, GWLP_WNDPROC), nullptr); SendMessage(this->hListBox, WM_SETFONT, (WPARAM) this->hFont[Font::FONT_PROCESS_INFO], TRUE); } { #if 0 this->hButton[Button::BUTTON_CANCEL] = CreateWindowW( WC_BUTTONW, // Predefined class; Unicode assumed L"Cancel", // Button text WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles 10, // x position 280, // y position 100, // Button width 30, // Button height this->hWindow, // Parent window nullptr, // No menu. (HINSTANCE)GetWindowLongPtr(this->hWindow, GWLP_HINSTANCE), nullptr); // Pointer not needed. #endif this->hButton[Button::BUTTON_REFRESH] = CreateWindowW( WC_BUTTONW, // Predefined class; Unicode assumed L"Refresh", // Button text WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles 10, // x position 280, // y position 100, // Button width 30, // Button height this->hWindow, // Parent window nullptr, // No menu. (HINSTANCE)GetWindowLongPtr(this->hWindow, GWLP_HINSTANCE), nullptr); // Pointer not needed. this->hButton[Button::BUTTON_CONTINUE] = CreateWindowW( WC_BUTTONW, // Predefined class; Unicode assumed L"Continue", // Button text WS_TABSTOP | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON, // Styles 475, // x position 280, // y position 100, // Button width 30, // Button height this->hWindow, // Parent window nullptr, // No menu. (HINSTANCE)GetWindowLongPtr(this->hWindow, GWLP_HINSTANCE), nullptr); // Pointer not needed. } ShowWindow(this->hWindow, SW_SHOWNORMAL); this->window_active = true; this->update_thread = std::thread([&]{ while(!this->update_exit) { { std::unique_lock update_lock{this->update_mutex}; this->update_cv.wait_for(update_lock, std::chrono::milliseconds{1000}); if(this->update_exit) return; } if(this->window_active && !this->force_update) continue; /* only auto update in the background */ this->force_update = false; this->update_blocking_info(); } }); return true; } INT_PTR FileInUseWindow::window_proc_color_static(HWND hElement, HDC hDC) { SetBkColor(hDC, kBackgroundColor); return (INT_PTR) this->hBrush[Brush::BRUSH_BACKGROUND]; } INT_PTR FileInUseWindow::window_proc_command(HWND hwnd, DWORD cID) { if(hwnd == this->hButton[Button::BUTTON_CANCEL]) { SendMessageA(this->hWindow, WM_CLOSE, (WPARAM) nullptr, (LPARAM) nullptr); } else if(hwnd == this->hButton[Button::BUTTON_REFRESH]) { this->force_update = true; this->update_cv.notify_all(); } else if(hwnd == this->hButton[Button::BUTTON_CONTINUE]) { this->result = ui::FileBlockedResult::PROCESSES_CLOSED; SendMessageA(this->hWindow, WM_CLOSE, (WPARAM) nullptr, (LPARAM) nullptr); } return 0; } void FileInUseWindow::finalize() { this->update_exit = true; this->update_cv.notify_all(); if(this->update_thread.joinable()) this->update_thread.join(); //TODO: Free resources? } static inline std::wstring_view app_type_prefix(RM_APP_TYPE type) { switch (type) { case RM_APP_TYPE::RmOtherWindow: return L"Child App : "; case RM_APP_TYPE::RmMainWindow: return L"Application: "; case RM_APP_TYPE::RmConsole: return L"Console App: "; case RM_APP_TYPE::RmService: return L"NT Service : "; case RM_APP_TYPE::RmExplorer: return L"Explorer : "; case RM_APP_TYPE::RmCritical: return L"System : "; default: return L" "; } } void FileInUseWindow::set_file(const std::wstring &file) { this->blocking_file = file; this->update_blocking_info(); } void FileInUseWindow::update_blocking_info() { std::wstring error{}; std::vector result_{}; if(!blocking_processes(result_, error, this->blocking_file)) { MessageBoxW(this->hWindow, (L"Failed to get file info:\n" + error).c_str(), L"Failed to query file info", MB_OK | MB_ICONERROR); SendMessageA(this->hWindow, WM_CLOSE, (WPARAM) nullptr, (LPARAM) nullptr); return; } this->set_blocking_info(this->blocking_file, result_); } void FileInUseWindow::set_blocking_info(const std::wstring_view &file, const std::vector &processes) { SetWindowTextW(this->hLabels[Label::LABEL_FILE_NAME], file.data()); std::vector output_processes{}; for(const auto& type : { RM_APP_TYPE::RmMainWindow, RM_APP_TYPE::RmConsole, RM_APP_TYPE::RmService, RM_APP_TYPE::RmCritical, RM_APP_TYPE::RmOtherWindow }) { for(const auto& proc : processes) { if(proc.type != type) continue; auto it = std::find_if(output_processes.begin(), output_processes.end(), [&](const BlockingProcess& entry) { return entry.name == proc.name && proc.exe_path == entry.exe_path; }); if(it != output_processes.end()) continue; output_processes.emplace_back(proc); } } HDC hDC = GetDC(NULL); SelectFont(hDC, this->hFont[Font::FONT_PROCESS_INFO]); size_t width{0}; SendMessage(this->hListBox, LB_RESETCONTENT, 0, 0); for(auto& process : output_processes) { auto message = L" " + std::wstring{app_type_prefix(process.type)} + process.name + L" " + std::to_wstring(process.pid) + L" (" + process.exe_path + L") "; SendMessageW(this->hListBox, LB_ADDSTRING, 0, (LPARAM) message.c_str()); RECT r = { 0, 0, 0, 0 }; DrawTextW(hDC, message.data(), message.length(), &r, DT_CALCRECT); if(r.right - r.left > width) width = r.right - r.left; } SendMessage(this->hListBox, LB_SETHORIZONTALEXTENT, (WPARAM) width, 0); ReleaseDC(NULL, hDC); EnableWindow(this->hButton[Button::BUTTON_CONTINUE], output_processes.empty()); } inline std::wstring mbtow(const std::string_view& str) { int length = MultiByteToWideChar(CP_UTF8, 0, str.data(), -1, nullptr, 0); std::wstring result{}; result.resize(length + 1); MultiByteToWideChar(CP_UTF8, 0, str.data(), -1, result.data() , length); return result; } #define IDR_APP_ICON 101 ui::FileBlockedResult ui::open_file_blocked(const std::string& file) { FileInUseWindow::register_class(); auto hInstance = GetModuleHandle(nullptr); auto hWindow = CreateWindowEx( 0, // Optional window styles. FileInUseWindow::ClassName, // Window class "One or more files are still in use", // Window text WS_OVERLAPPED | WS_CAPTION | WS_THICKFRAME | WS_SYSMENU, // Window style // Size and position CW_USEDEFAULT, CW_USEDEFAULT, 600, 360, nullptr, // Parent window nullptr, // Menu hInstance, // Instance handle nullptr // Additional application data ); if (hWindow == nullptr) return ui::FileBlockedResult::INTERNAL_ERROR; auto hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDR_APP_ICON)); SendMessage(hWindow, WM_SETICON, ICON_BIG, (LPARAM) hIcon); SendMessage(hWindow, WM_SETICON, ICON_SMALL, (LPARAM) hIcon); DestroyIcon(hIcon); auto window = (FileInUseWindow*) GetWindowLongPtr(hWindow, GWLP_USERDATA); window->deleteOnClose = false; window->set_file(mbtow(file)); MSG msg = { }; while (GetMessage(&msg, hWindow, 0, 0)) { if (msg.message == WM_NULL) break; TranslateMessage(&msg); DispatchMessage(&msg); } auto result = window->result; delete window; return result; }