From ef06621b5e33890876e7db5fb6ca9b2ccc492f0a Mon Sep 17 00:00:00 2001 From: nessa Date: Fri, 9 May 2025 21:39:56 +0200 Subject: [PATCH] Initial commit. --- .gitignore | 3 + CMakeLists.txt | 24 +++++ CommandLine.cpp | 139 ++++++++++++++++++++++++++ CommandLine.h | 56 +++++++++++ LICENSE | 22 ++++- PrintHelper.cpp | 190 ++++++++++++++++++++++++++++++++++++ PrintHelper.h | 55 +++++++++++ Process.cpp | 152 +++++++++++++++++++++++++++++ Process.h | 73 ++++++++++++++ Utility.cpp | 131 +++++++++++++++++++++++++ Utility.h | 70 ++++++++++++++ Window.cpp | 90 +++++++++++++++++ Window.h | 52 ++++++++++ main.cpp | 253 ++++++++++++++++++++++++++++++++++++++++++++++++ 14 files changed, 1305 insertions(+), 5 deletions(-) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 CommandLine.cpp create mode 100644 CommandLine.h create mode 100644 PrintHelper.cpp create mode 100644 PrintHelper.h create mode 100644 Process.cpp create mode 100644 Process.h create mode 100644 Utility.cpp create mode 100644 Utility.h create mode 100644 Window.cpp create mode 100644 Window.h create mode 100644 main.cpp 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/LICENSE b/LICENSE index 3436328..180ae70 100644 --- a/LICENSE +++ b/LICENSE @@ -1,9 +1,21 @@ -MIT License + MIT License -Copyright (c) 2025 nessa + 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: + 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 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. + 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. 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