diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d2d8d6c --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/.idea +/cmake-build-debug +/cmake-build-release diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..fd73535 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,24 @@ +cmake_minimum_required(VERSION 3.31) +project(findwindow) + +set(CMAKE_CXX_STANDARD 20) + +# Enable static linking +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static") +set(CMAKE_FIND_LIBRARY_SUFFIXES ".a") +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libgcc -static-libstdc++") + +add_executable(findwindow main.cpp + Utility.h + CommandLine.cpp + CommandLine.h + Utility.cpp + Process.cpp + Process.h + Window.cpp + Window.h + PrintHelper.h + PrintHelper.cpp) + +# Add ntdll library for NtQueryInformationProcess +target_link_libraries(findwindow ntdll) \ No newline at end of file diff --git a/CommandLine.cpp b/CommandLine.cpp new file mode 100644 index 0000000..99c0f0f --- /dev/null +++ b/CommandLine.cpp @@ -0,0 +1,139 @@ +/* + * MIT License + * + * Copyright (c) 2025 Vanessa T. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include "CommandLine.h" +#include "Utility.h" + +enum class Option { + None, + ProcessId, + ImagePath, + WindowTitle, + Text, + Order +}; + +void CommandLine::parse(const std::vector& args) { + auto option = Option::None; + + for (auto& arg : args) { + switch (option) { + case Option::None: + if (arg == "-h" || arg == "--help") { + showHelp = true; + } else if (arg == "-f" || arg == "--foreground") { + bringToForeground = true; + } else if (arg == "-l" || arg == "--list-processes") { + listProcesses = true; + } else if (arg == "-r" || arg == "--reverse") { + reverseOrder = true; + } else if (arg == "-I" || arg == "--image-path") { + option = Option::ImagePath; + } else if (arg == "-W" || arg == "--window-title") { + option = Option::WindowTitle; + } else if (arg == "-T" || arg == "--text") { + option = Option::Text; + } else if (arg == "-p" || arg == "--process-id") { + option = Option::ProcessId; + } else if (arg == "-o" || arg == "--order") { + option = Option::Order; + } else { + throw std::runtime_error("Unknown option: " + arg); + } + break; + case Option::ImagePath: + imagePath = arg; + option = Option::None; + break; + case Option::WindowTitle: + windowTitle = arg; + option = Option::None; + break; + case Option::Text: + text = arg; + option = Option::None; + break; + case Option::ProcessId: + processId = std::stoi(arg); + option = Option::None; + break; + case Option::Order: + auto lowerArg = toLowerCase(arg); + if (arg == "pid") { + sortOrder = CommandLine::SortOrder::ProcessId; + } else if (arg == "path") { + sortOrder = CommandLine::SortOrder::ImagePath; + } else if (arg == "ram") { + sortOrder = CommandLine::SortOrder::RamUsage; + } else { + throw std::runtime_error("Unknown sort order: " + arg); + } + option = Option::None; + break; + } + } + + if (!valid()) { + throw std::runtime_error("Invalid command line"); + } +} + +bool CommandLine::valid() const { + if (showHelp) return true; + + if (listProcesses) { + if (processId > 0) return false; + if (!imagePath.empty()) return false; + if (!windowTitle.empty()) return false; + if (!text.empty()) return false; + + return true; + } + + if (processId > 0) { + if (!imagePath.empty()) return false; + if (!windowTitle.empty()) return false; + if (!text.empty()) return false; + + return true; + } + + if (!imagePath.empty()) { + if (!windowTitle.empty()) return false; + if (!text.empty()) return false; + + return true; + } + + if (!windowTitle.empty()) { + if (!text.empty()) return false; + + return true; + } + + if (text.empty()) return false; + + return true; +} diff --git a/CommandLine.h b/CommandLine.h new file mode 100644 index 0000000..8c56916 --- /dev/null +++ b/CommandLine.h @@ -0,0 +1,56 @@ +/* + * MIT License + * + * Copyright (c) 2025 Vanessa T. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef FINDWINDOW_COMMANDLINE_H +#define FINDWINDOW_COMMANDLINE_H + +#include +#include + +struct CommandLine { + enum class SortOrder { + None, + ProcessId, + ImagePath, + RamUsage, + }; + + std::string text{}; + std::string windowTitle{}; + std::string imagePath{}; + int processId{0}; + bool showHelp{false}; + bool bringToForeground{false}; + bool listProcesses{false}; + bool reverseOrder{false}; + SortOrder sortOrder{SortOrder::None}; + + [[nodiscard]] bool valid() const; + + CommandLine() = default; + void parse(const std::vector& args); +}; + + +#endif //FINDWINDOW_COMMANDLINE_H diff --git a/PrintHelper.cpp b/PrintHelper.cpp new file mode 100644 index 0000000..792eecc --- /dev/null +++ b/PrintHelper.cpp @@ -0,0 +1,190 @@ +/* + * MIT License + * + * Copyright (c) 2025 Vanessa T. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include "PrintHelper.h" +#include "Utility.h" + +std::ostream &operator<<(std::ostream &os, const InlinePrint &process) { + auto width = process.printWidth; + if (width < 80) width = 80; + auto imagePathWidth = width - 36 - 10 - 10 - 10 - 4; + auto formatString = "{:^8}|{:<" + std::to_string(imagePathWidth) + "}|{:>10}|{:>10}|{:>10}"; + + std::string peakWorkingSetSize; + std::string workingSetSize; + std::string pageFileUsage; + + try { + auto ramUsage = process->ramUsage(); + peakWorkingSetSize = formatBytes(ramUsage.peakWorkingSetSize); + workingSetSize = formatBytes(ramUsage.workingSetSize); + pageFileUsage = formatBytes(ramUsage.pageFileUsage); + + } catch (std::exception &e) { + peakWorkingSetSize = "N/A"; + workingSetSize = "N/A"; + pageFileUsage = "N/A"; + } + + std::wstring imagePath; + try { + imagePath = process->imagePath(); + + if (imagePath.length() > imagePathWidth) { + auto start = imagePath.length() - (imagePathWidth - 13); + imagePath = imagePath.substr(0, 10) + L"..." + imagePath.substr(start, imagePathWidth - 13); + } + } catch (std::exception &e) { + imagePath = L"N/A"; + } + + os << std::vformat(formatString, std::make_format_args( + process->processId(), + wstringToString(imagePath), + peakWorkingSetSize, + workingSetSize, + pageFileUsage + )); + return os; +} + +std::wostream &operator<<(std::wostream &os, const InlinePrint &process) { + auto width = process.printWidth; + if (width < 80) width = 80; + auto imagePathWidth = width - 36 - 10 - 10 - 10 - 4; + auto formatString = L"{:^8}|{:<" + std::to_wstring(imagePathWidth) + L"}|{:>10}|{:>10}|{:>10}"; + + std::wstring peakWorkingSetSize; + std::wstring workingSetSize; + std::wstring pageFileUsage; + + try { + auto ramUsage = process->ramUsage(); + peakWorkingSetSize = formatBytes(ramUsage.peakWorkingSetSize); + workingSetSize = formatBytes(ramUsage.workingSetSize); + pageFileUsage = formatBytes(ramUsage.pageFileUsage); + + } catch (std::exception &e) { + peakWorkingSetSize = L"N/A"; + workingSetSize = L"N/A"; + pageFileUsage = L"N/A"; + } + + std::wstring imagePath; + try { + imagePath = process->imagePath(); + + if (imagePath.length() > imagePathWidth) { + auto start = imagePath.length() - (imagePathWidth - 13); + imagePath = imagePath.substr(0, 10) + L"..." + imagePath.substr(start, imagePathWidth - 13); + } + } catch (std::exception &e) { + imagePath = L"N/A"; + } + + os << std::vformat(formatString, std::make_wformat_args( + process->processId(), + imagePath, + peakWorkingSetSize, + workingSetSize, + pageFileUsage + )); + return os; +} + +std::ostream &operator<<(std::ostream &os, const InlineHeader &process) { + auto width = process.printWidth; + if (width < 80) width = 80; + auto imagePathWidth = width - 36 - 10 - 10 - 10 - 4; + auto formatString = "{:^8}|{:<" + std::to_string(imagePathWidth) + "}|{:>10}|{:>10}|{:>10}"; + + os << std::vformat(formatString, std::make_format_args( + "PID", + "Image Path", + "reserved", + "use", + "swap" + )) << std::endl; + + os << "--------+" + << std::string(imagePathWidth, '-') + << "+----------+----------+----------"; + return os; +} + +std::wostream &operator<<(std::wostream &os, const InlineHeader &process) { + auto width = process.printWidth; + if (width < 80) width = 80; + auto imagePathWidth = width - 36 - 10 - 10 - 10 - 4; + auto formatString = L"{:^8}|{:<" + std::to_wstring(imagePathWidth) + L"}|{:>10}|{:>10}|{:>10}"; + + os << std::vformat(formatString, std::make_wformat_args( + L"PID", + L"Image Path", + L"reserved", + L"use", + L"swap" + )) << std::endl; + os << L"--------+" + << std::wstring(imagePathWidth, L'-') + << L"+----------+----------+----------"; + + return os; +} + + +std::ostream &operator<<(std::ostream &os, const Process::MemoryUsage &memUsage) { + os << "Memory Usage Details:\n" + << " Page Faults: " << memUsage.pageFaults << "\n" + << " Peak Working Set Size: " << formatBytes(memUsage.peakWorkingSetSize) << "\n" + << " Working Set Size: " << formatBytes(memUsage.workingSetSize) << "\n" + << " Page File Usage: " << formatBytes(memUsage.pageFileUsage) << "\n" + << " Peak Page File Usage: " << formatBytes(memUsage.peakPageFileUsage) << "\n" + << " Private Usage: " << formatBytes(memUsage.privateUsage) << "\n" + << " Pool Usages:\n" + << " Paged Pool Peak: " << formatBytes(memUsage.quotaPeakPagedPoolUsage) << "\n" + << " Paged Pool Current: " << formatBytes(memUsage.quotaPagedPoolUsage) << "\n" + << " Non-Paged Pool Peak: " << formatBytes(memUsage.quotaPeakNonPagedPoolUsage) << "\n" + << " Non-Paged Pool Current:" << formatBytes(memUsage.quotaNonPagedPoolUsage); + + return os; +} + +std::wostream &operator<<(std::wostream &os, const Process::MemoryUsage &memUsage) { + os << L"Memory Usage Details:\n" + << L" Page Faults: " << memUsage.pageFaults << L"\n" + << L" Peak Working Set Size: " << formatBytes(memUsage.peakWorkingSetSize) << L"\n" + << L" Working Set Size: " << formatBytes(memUsage.workingSetSize) << L"\n" + << L" Page File Usage: " << formatBytes(memUsage.pageFileUsage) << L"\n" + << L" Peak Page File Usage: " << formatBytes(memUsage.peakPageFileUsage) << L"\n" + << L" Private Usage: " << formatBytes(memUsage.privateUsage) << L"\n" + << L" Pool Usages:\n" + << L" Paged Pool Peak: " << formatBytes(memUsage.quotaPeakPagedPoolUsage) << L"\n" + << L" Paged Pool Current: " << formatBytes(memUsage.quotaPagedPoolUsage) << L"\n" + << L" Non-Paged Pool Peak: " << formatBytes(memUsage.quotaPeakNonPagedPoolUsage) << L"\n" + << L" Non-Paged Pool Current:" << formatBytes(memUsage.quotaNonPagedPoolUsage); + + return os; +} diff --git a/PrintHelper.h b/PrintHelper.h new file mode 100644 index 0000000..50eef53 --- /dev/null +++ b/PrintHelper.h @@ -0,0 +1,55 @@ +/* + * MIT License + * + * Copyright (c) 2025 Vanessa T. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef FINDWINDOW_PRINTHELPER_H +#define FINDWINDOW_PRINTHELPER_H + +#include +#include "Process.h" + +template +struct InlinePrint { + int printWidth; + T value; + T const *operator->() const { + return &value; + } +}; + +template +struct InlineHeader { + int printWidth; +}; + + +std::ostream& operator<<(std::ostream& os, const Process::MemoryUsage& memUsage); +std::wostream& operator<<(std::wostream& os, const Process::MemoryUsage& memUsage); + +std::ostream &operator<<(std::ostream &os, const InlinePrint &process); +std::wostream &operator<<(std::wostream &os, const InlinePrint &process); +std::ostream &operator<<(std::ostream &os, const InlineHeader &process); +std::wostream &operator<<(std::wostream &os, const InlineHeader &process); + + +#endif //FINDWINDOW_PRINTHELPER_H diff --git a/Process.cpp b/Process.cpp new file mode 100644 index 0000000..304b791 --- /dev/null +++ b/Process.cpp @@ -0,0 +1,152 @@ +/* + * MIT License + * + * Copyright (c) 2025 Vanessa T. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include + +#include +#include +#include + +#include "Process.h" +#include "Utility.h" + +Process::Process(DWORD processId) : m_processId(processId) { + +} + +Process::~Process() { + close(); +} + +DWORD Process::processId() const { + return m_processId; +} + +void Process::open() { + if (m_hProcess) return; + + m_limited = false; + m_hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, m_processId); + + if (!m_hProcess) { + m_limited = true; + m_hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, m_processId); + } + + if (!m_hProcess) { + checkAndThrowLastWindowsError("Failed to open process " + std::to_string(m_processId)); + } +} + +void Process::close() { + if (m_hProcess) { + CloseHandle(m_hProcess); + } +} + +std::wstring Process::imagePath() const { + if (!isOpen()) throw std::runtime_error("Process is not open"); + + ULONG length = 0; + NTSTATUS status = NtQueryInformationProcess(m_hProcess, ProcessImageFileName, nullptr, 0, &length); + if (status != STATUS_SUCCESS && status != STATUS_INFO_LENGTH_MISMATCH) { + throw std::runtime_error("Unable to get process image path length: NTSTATUS = " + + std::format("{:0x}", static_cast(status))); + } + + std::vector buffer(length + 1); + if (!QueryFullProcessImageNameW(m_hProcess, 0, buffer.data(), &length)) { + checkAndThrowLastWindowsError("Unable to get process image path"); + } + + return {buffer.data()}; +} + +Process::MemoryUsage Process::ramUsage() const { + if (!isOpen()) throw std::runtime_error("Process is not open"); + + MemoryUsage usage{}; + + PROCESS_MEMORY_COUNTERS_EX counters = { + .cb = sizeof counters + }; + + if (!GetProcessMemoryInfo(m_hProcess, reinterpret_cast(&counters), sizeof(counters))) { + checkAndThrowLastWindowsError("Unable to get process memory usage"); + } + + usage.pageFaults = counters.PageFaultCount; + usage.peakWorkingSetSize = counters.PeakWorkingSetSize; + usage.workingSetSize = counters.WorkingSetSize; + usage.quotaPeakPagedPoolUsage = counters.QuotaPeakPagedPoolUsage; + usage.quotaPagedPoolUsage = counters.QuotaPagedPoolUsage; + usage.quotaPeakNonPagedPoolUsage = counters.QuotaPeakNonPagedPoolUsage; + usage.quotaNonPagedPoolUsage = counters.QuotaNonPagedPoolUsage; + usage.pageFileUsage = counters.PagefileUsage; + usage.peakPageFileUsage = counters.PeakPagefileUsage; + usage.privateUsage = counters.PrivateUsage; + + return usage; +} + +bool Process::isLimited() const { + return m_limited; +} + +std::vector Process::enumerate() { + std::vector processes{}; + + bool foundAllProcesses = false; + std::vector processIds(1024); + while (!foundAllProcesses) { + auto bufferSize = processIds.size() * sizeof(DWORD); + DWORD bytesReturned; + + if (!EnumProcesses(processIds.data(), bufferSize, &bytesReturned)) { + checkAndThrowLastWindowsError("Unable to enumerate processes"); + } + + if (bytesReturned <= bufferSize) { + processIds.resize(bytesReturned / sizeof(DWORD)); + foundAllProcesses = true; + } else { + // Resize to 1.5 times the current size + processIds.resize(processes.size() + processes.size() / 2); + } + } + + processes.reserve(processIds.size() - 1); + for (auto processId: processIds) { + // Skip system idle process + if (processId == 0) continue; + processes.emplace_back(processId); + } + + return processes; +} + +bool Process::isOpen() const { + return m_hProcess != nullptr; +} diff --git a/Process.h b/Process.h new file mode 100644 index 0000000..485589b --- /dev/null +++ b/Process.h @@ -0,0 +1,73 @@ +/* + * MIT License + * + * Copyright (c) 2025 Vanessa T. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef FINDWINDOW_PROCESS_H +#define FINDWINDOW_PROCESS_H + +#include +#include +#include + +class Process { +public: + struct MemoryUsage { + uint32_t pageFaults; + size_t peakWorkingSetSize; + size_t workingSetSize; + size_t quotaPeakPagedPoolUsage; + size_t quotaPagedPoolUsage; + size_t quotaPeakNonPagedPoolUsage; + size_t quotaNonPagedPoolUsage; + size_t pageFileUsage; + size_t peakPageFileUsage; + size_t privateUsage; + }; + + explicit Process(DWORD processId); + + ~Process(); + + void open(); + + void close(); + + [[nodiscard]] bool isOpen() const; + + [[nodiscard]] bool isLimited() const; + + [[nodiscard]] DWORD processId() const; + + [[nodiscard]] std::wstring imagePath() const; + + [[nodiscard]] MemoryUsage ramUsage() const; + + static std::vector enumerate(); + +private: + bool m_limited{false}; + DWORD m_processId{0}; + HANDLE m_hProcess{nullptr}; +}; + +#endif //FINDWINDOW_PROCESS_H diff --git a/Utility.cpp b/Utility.cpp new file mode 100644 index 0000000..86348a8 --- /dev/null +++ b/Utility.cpp @@ -0,0 +1,131 @@ +/* + * MIT License + * + * Copyright (c) 2025 Vanessa T. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "Utility.h" + +#include +#include +#include + +std::wstring stringToWString(const std::string& str) { + if (str.empty()) return {}; + + // Calculate the required buffer size + int size = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0); + std::wstring result(size - 1, 0); // size-1 because MultiByteToWideChar counts null terminator + MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &result[0], size); + + return result; +} + +std::string wstringToString(const std::wstring& wstr) { + // Convert to UTF-8 + int size_needed = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr); + std::string message(size_needed - 1, 0); // -1 because WideCharToMultiByte counts null terminator + WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &message[0], size_needed, nullptr, nullptr); + + return message; +} + +std::string formatBytesString(size_t bytes) { + const char* units[] = {"B", "KB", "MB", "GB", "TB"}; + int unitIndex = 0; + auto displayBytes = static_cast(bytes); + + while (displayBytes >= 1024 && unitIndex < 4) { + displayBytes /= 1024; + unitIndex++; + } + + char buffer[64]; + std::snprintf(buffer, sizeof(buffer), "%.2f %s", displayBytes, units[unitIndex]); + return {buffer}; +} + +std::wstring formatBytesWideString(size_t bytes) { + return stringToWString(formatBytesString(bytes)); +} + +std::vector convertCommandLine(int argc, char **argv) { + std::vector args; + + for (auto i = 1; i < argc; i++) { + args.emplace_back(argv[i]); + } + + return args; +} + +std::string getErrorMessage(DWORD errorCode) { + if (errorCode == 0) return {}; + + LPWSTR messageBuffer = nullptr; + DWORD size = FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, + errorCode, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&messageBuffer), + 0, + nullptr + ); + + if (size == 0) { + return "Unknown error code: " + std::to_string(errorCode); + } + + // Convert to wstring first to handle Unicode properly + std::wstring wmessage(messageBuffer); + // Free the Windows-allocated buffer + LocalFree(messageBuffer); + + // Remove potential trailing newline and carriage return + while (!wmessage.empty() && (wmessage.back() == L'\n' || wmessage.back() == L'\r')) { + wmessage.pop_back(); + } + + return std::to_string(errorCode) + " - " + wstringToString(wmessage); +} + +void checkAndThrowLastWindowsError(const std::string& message) { + auto lastError = GetLastError(); + if (lastError != ERROR_SUCCESS) { + SetLastError(0); + + throw std::runtime_error(message + ": " + getErrorMessage(lastError)); + } +} + +ConsoleDimensions getConsoleDimensions() { + CONSOLE_SCREEN_BUFFER_INFO csbi; + int columns, rows; + + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi); + columns = csbi.srWindow.Right - csbi.srWindow.Left + 1; + rows = csbi.srWindow.Bottom - csbi.srWindow.Top + 1; + + return { columns, rows}; +} diff --git a/Utility.h b/Utility.h new file mode 100644 index 0000000..c749288 --- /dev/null +++ b/Utility.h @@ -0,0 +1,70 @@ +/* + * MIT License + * + * Copyright (c) 2025 Vanessa T. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef FINDWINDOW_UTILITY_H +#define FINDWINDOW_UTILITY_H + +#include +#include + +#include +#include + +void checkAndThrowLastWindowsError(const std::string& message); + +std::wstring stringToWString(const std::string& str); +std::string wstringToString(const std::wstring& wstr); +std::string formatBytesString(size_t bytes); + +template +T formatBytes(size_t bytes); + +template <> +inline std::string formatBytes(size_t bytes) { + return formatBytesString(bytes); +} +template <> +inline std::wstring formatBytes(size_t bytes) { + return stringToWString(formatBytesString(bytes)); +} + +inline std::string toLowerCase(std::string s) { + std::transform(s.begin(), s.end(), s.begin(), [] (auto c){ return std::tolower(c);}); + return s; +} + +std::vector convertCommandLine(int argc, char *argv[]); + +std::string getErrorMessage(DWORD errorCode); +static inline std::string getLastErrorMessage() { + return getErrorMessage(GetLastError()); +} + +struct ConsoleDimensions { + int w, h; +}; + +ConsoleDimensions getConsoleDimensions(); + +#endif //FINDWINDOW_UTILITY_H diff --git a/Window.cpp b/Window.cpp new file mode 100644 index 0000000..7bc894a --- /dev/null +++ b/Window.cpp @@ -0,0 +1,90 @@ +/* + * MIT License + * + * Copyright (c) 2025 Vanessa T. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include "Window.h" +#include "Utility.h" + +Window::Window(HWND hWnd) : m_hWnd(hWnd) { + +} + +static BOOL CALLBACK enumWindowsCallback(HWND hWnd, LPARAM lParam) { + auto windows = reinterpret_cast*>(lParam); + + windows->emplace_back(hWnd); + + return TRUE; +} + +std::list Window::enumWindows() { + std::list windows{}; + + EnumWindows(enumWindowsCallback, reinterpret_cast(&windows)); + + return windows; +} + +std::wstring Window::title() const { + if (!m_hWnd) throw std::runtime_error("Invalid window handle"); + + auto length = GetWindowTextLengthW(m_hWnd); + if (length == 0) { + checkAndThrowLastWindowsError("Unable to get window title length"); + return {}; + } + + std::vector buffer(length + 1); + if (GetWindowTextW(m_hWnd, buffer.data(), length + 1) <= 0) { + checkAndThrowLastWindowsError("Unable to get window title"); + } + + return {buffer.data()}; +} + +DWORD Window::processId() const { + if (!m_hWnd) throw std::runtime_error("Invalid window handle"); + + DWORD processId; + if (GetWindowThreadProcessId(m_hWnd, &processId)) { + checkAndThrowLastWindowsError("Unable to get window process id"); + } + + return processId; +} + +bool Window::isMainWindow() const { + if (!m_hWnd) throw std::runtime_error("Invalid window handle"); + + return GetWindow(m_hWnd, GW_OWNER) == nullptr; +} + +void Window::bringToForeground() const { + SetForegroundWindow(m_hWnd); +} + +HWND Window::handle() const { + return m_hWnd; +} diff --git a/Window.h b/Window.h new file mode 100644 index 0000000..2dbdfaa --- /dev/null +++ b/Window.h @@ -0,0 +1,52 @@ +/* + * MIT License + * + * Copyright (c) 2025 Vanessa T. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef FINDWINDOW_WINDOW_H +#define FINDWINDOW_WINDOW_H + +#include +#include +#include +#include "Process.h" + +class Window { +public: + explicit Window(HWND hWnd); + + [[nodiscard]] DWORD processId() const; + [[nodiscard]] HWND handle() const; + [[nodiscard]] std::wstring title() const; + + [[nodiscard]] bool isMainWindow() const; + + void bringToForeground() const; + + static std::list enumWindows(); + +private: + HWND m_hWnd{nullptr}; +}; + + +#endif //FINDWINDOW_WINDOW_H diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..32f7050 --- /dev/null +++ b/main.cpp @@ -0,0 +1,253 @@ +/* + * MIT License + * + * Copyright (c) 2025 Vanessa T. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include "CommandLine.h" +#include "Utility.h" +#include "Process.h" +#include "Window.h" +#include "PrintHelper.h" + +void showError(const std::string &msg) { + std::cerr << msg << std::endl; +} + +void usage() { + std::wcout << L"FindWindow - a utility for discovering and managing Windows\n\n"; + std::wcout << L"Usage: findwindow [options]\n\n"; + + std::wcout << L"Regex search and filtering options:\n"; + std::wcout << L" -p, --process-id Filter windows by process ID\n"; + std::wcout << L" -I, --image-path Search processes by image file path regex\n"; + std::wcout << L" -W, --window-title Search windows by title regex\n"; + std::wcout << L" -T, --text Search for text within windows\n"; + + std::wcout << L"Process and Window Options:\n"; + std::wcout << L" -f, --foreground Bring matching windows to foreground\n"; + + std::wcout << L"Process listing options:\n"; + std::wcout << L" -l, --list-processes List all accessible processes\n"; + std::wcout << L" -o, --order