Initial commit.
This commit is contained in:
parent
29be0a41ab
commit
52d5a0e042
|
|
@ -0,0 +1,3 @@
|
||||||
|
/.idea
|
||||||
|
/cmake-build-debug
|
||||||
|
/cmake-build-release
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Vanessa T. <nessa@neko-tools.de>
|
||||||
|
*
|
||||||
|
* 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 <stdexcept>
|
||||||
|
#include "CommandLine.h"
|
||||||
|
#include "Utility.h"
|
||||||
|
|
||||||
|
enum class Option {
|
||||||
|
None,
|
||||||
|
ProcessId,
|
||||||
|
ImagePath,
|
||||||
|
WindowTitle,
|
||||||
|
Text,
|
||||||
|
Order
|
||||||
|
};
|
||||||
|
|
||||||
|
void CommandLine::parse(const std::vector<std::string>& 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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Vanessa T. <nessa@neko-tools.de>
|
||||||
|
*
|
||||||
|
* 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 <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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<std::string>& args);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //FINDWINDOW_COMMANDLINE_H
|
||||||
|
|
@ -0,0 +1,190 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Vanessa T. <nessa@neko-tools.de>
|
||||||
|
*
|
||||||
|
* 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 <format>
|
||||||
|
#include "PrintHelper.h"
|
||||||
|
#include "Utility.h"
|
||||||
|
|
||||||
|
std::ostream &operator<<(std::ostream &os, const InlinePrint<Process> &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<std::string>(ramUsage.peakWorkingSetSize);
|
||||||
|
workingSetSize = formatBytes<std::string>(ramUsage.workingSetSize);
|
||||||
|
pageFileUsage = formatBytes<std::string>(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> &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<std::wstring>(ramUsage.peakWorkingSetSize);
|
||||||
|
workingSetSize = formatBytes<std::wstring>(ramUsage.workingSetSize);
|
||||||
|
pageFileUsage = formatBytes<std::wstring>(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> &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> &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<std::string>(memUsage.peakWorkingSetSize) << "\n"
|
||||||
|
<< " Working Set Size: " << formatBytes<std::string>(memUsage.workingSetSize) << "\n"
|
||||||
|
<< " Page File Usage: " << formatBytes<std::string>(memUsage.pageFileUsage) << "\n"
|
||||||
|
<< " Peak Page File Usage: " << formatBytes<std::string>(memUsage.peakPageFileUsage) << "\n"
|
||||||
|
<< " Private Usage: " << formatBytes<std::string>(memUsage.privateUsage) << "\n"
|
||||||
|
<< " Pool Usages:\n"
|
||||||
|
<< " Paged Pool Peak: " << formatBytes<std::string>(memUsage.quotaPeakPagedPoolUsage) << "\n"
|
||||||
|
<< " Paged Pool Current: " << formatBytes<std::string>(memUsage.quotaPagedPoolUsage) << "\n"
|
||||||
|
<< " Non-Paged Pool Peak: " << formatBytes<std::string>(memUsage.quotaPeakNonPagedPoolUsage) << "\n"
|
||||||
|
<< " Non-Paged Pool Current:" << formatBytes<std::string>(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<std::wstring>(memUsage.peakWorkingSetSize) << L"\n"
|
||||||
|
<< L" Working Set Size: " << formatBytes<std::wstring>(memUsage.workingSetSize) << L"\n"
|
||||||
|
<< L" Page File Usage: " << formatBytes<std::wstring>(memUsage.pageFileUsage) << L"\n"
|
||||||
|
<< L" Peak Page File Usage: " << formatBytes<std::wstring>(memUsage.peakPageFileUsage) << L"\n"
|
||||||
|
<< L" Private Usage: " << formatBytes<std::wstring>(memUsage.privateUsage) << L"\n"
|
||||||
|
<< L" Pool Usages:\n"
|
||||||
|
<< L" Paged Pool Peak: " << formatBytes<std::wstring>(memUsage.quotaPeakPagedPoolUsage) << L"\n"
|
||||||
|
<< L" Paged Pool Current: " << formatBytes<std::wstring>(memUsage.quotaPagedPoolUsage) << L"\n"
|
||||||
|
<< L" Non-Paged Pool Peak: " << formatBytes<std::wstring>(memUsage.quotaPeakNonPagedPoolUsage) << L"\n"
|
||||||
|
<< L" Non-Paged Pool Current:" << formatBytes<std::wstring>(memUsage.quotaNonPagedPoolUsage);
|
||||||
|
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Vanessa T. <nessa@neko-tools.de>
|
||||||
|
*
|
||||||
|
* 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 <ostream>
|
||||||
|
#include "Process.h"
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct InlinePrint {
|
||||||
|
int printWidth;
|
||||||
|
T value;
|
||||||
|
T const *operator->() const {
|
||||||
|
return &value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
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> &process);
|
||||||
|
std::wostream &operator<<(std::wostream &os, const InlinePrint<Process> &process);
|
||||||
|
std::ostream &operator<<(std::ostream &os, const InlineHeader<Process> &process);
|
||||||
|
std::wostream &operator<<(std::wostream &os, const InlineHeader<Process> &process);
|
||||||
|
|
||||||
|
|
||||||
|
#endif //FINDWINDOW_PRINTHELPER_H
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Vanessa T. <nessa@neko-tools.de>
|
||||||
|
*
|
||||||
|
* 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 <stdexcept>
|
||||||
|
#include <format>
|
||||||
|
|
||||||
|
#include <winternl.h>
|
||||||
|
#include <ntstatus.h>
|
||||||
|
#include <psapi.h>
|
||||||
|
|
||||||
|
#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<ULONG>(status)));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<wchar_t> 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<PROCESS_MEMORY_COUNTERS *>(&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> Process::enumerate() {
|
||||||
|
std::vector<Process> processes{};
|
||||||
|
|
||||||
|
bool foundAllProcesses = false;
|
||||||
|
std::vector<DWORD> 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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Vanessa T. <nessa@neko-tools.de>
|
||||||
|
*
|
||||||
|
* 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 <windows.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
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<Process> enumerate();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool m_limited{false};
|
||||||
|
DWORD m_processId{0};
|
||||||
|
HANDLE m_hProcess{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FINDWINDOW_PROCESS_H
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Vanessa T. <nessa@neko-tools.de>
|
||||||
|
*
|
||||||
|
* 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 <windows.h>
|
||||||
|
#include <string>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
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<double>(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<std::string> convertCommandLine(int argc, char **argv) {
|
||||||
|
std::vector<std::string> 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<LPWSTR>(&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};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Vanessa T. <nessa@neko-tools.de>
|
||||||
|
*
|
||||||
|
* 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 <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
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 <typename T>
|
||||||
|
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<std::string> 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
|
||||||
|
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Vanessa T. <nessa@neko-tools.de>
|
||||||
|
*
|
||||||
|
* 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 <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
#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<std::list<Window>*>(lParam);
|
||||||
|
|
||||||
|
windows->emplace_back(hWnd);
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::list<Window> Window::enumWindows() {
|
||||||
|
std::list<Window> windows{};
|
||||||
|
|
||||||
|
EnumWindows(enumWindowsCallback, reinterpret_cast<LPARAM>(&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<wchar_t> 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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Vanessa T. <nessa@neko-tools.de>
|
||||||
|
*
|
||||||
|
* 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 <windows.h>
|
||||||
|
#include <list>
|
||||||
|
#include <string>
|
||||||
|
#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<Window> enumWindows();
|
||||||
|
|
||||||
|
private:
|
||||||
|
HWND m_hWnd{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //FINDWINDOW_WINDOW_H
|
||||||
|
|
@ -0,0 +1,253 @@
|
||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2025 Vanessa T. <nessa@neko-tools.de>
|
||||||
|
*
|
||||||
|
* 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 <iostream>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <ranges>
|
||||||
|
#include <numeric>
|
||||||
|
#include <regex>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#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 <pid> Filter windows by process ID\n";
|
||||||
|
std::wcout << L" -I, --image-path <regex> Search processes by image file path regex\n";
|
||||||
|
std::wcout << L" -W, --window-title <regex> Search windows by title regex\n";
|
||||||
|
std::wcout << L" -T, --text <regex> 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 <option> Sort processes by:\n";
|
||||||
|
std::wcout << L" pid - Sort by Process ID\n";
|
||||||
|
std::wcout << L" path - Sort by Image Path\n";
|
||||||
|
std::wcout << L" ram - Sort by Memory Usage\n";
|
||||||
|
std::wcout << L" -r, --reverse Reverse the sorting order\n\n";
|
||||||
|
|
||||||
|
std::wcout << L"Other options:\n";
|
||||||
|
std::wcout << L" -h, --help Show this help message\n\n";
|
||||||
|
|
||||||
|
std::wcout << L"Examples:\n";
|
||||||
|
std::wcout << L" findwindow -p 1234 Find windows for specific process ID\n";
|
||||||
|
std::wcout << L" findwindow -l List all accessible processes\n";
|
||||||
|
std::wcout << L" findwindow -l -o pid -r List processes sorted by PID in reverse\n";
|
||||||
|
std::wcout << L" findwindow -p 1234 -f Find and bring windows to foreground\n";
|
||||||
|
std::wcout << L" findwindow -I \"chrome\\.exe\" Find processes with image path containing \"chrome.exe\"\n";
|
||||||
|
std::wcout << L" findwindow -W \"Notepad\" Find windows with title containing \"Notepad\"\n";
|
||||||
|
std::wcout << L" findwindow -T \"Submit\" Find windows containing text matching \"Submit\"\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
void printProcessAndWindows(const Process &process, const std::list<Window> &windows, bool bringToForeground) {
|
||||||
|
std::wcout << L"Process ID: " << process.processId() << std::endl;
|
||||||
|
std::wcout << L"Process image path: " << process.imagePath() << std::endl;
|
||||||
|
if (!process.isLimited()) std::wcout << L"Process memory usage: " << process.ramUsage() << std::endl;
|
||||||
|
|
||||||
|
auto processWindows = windows | std::views::filter([&](auto &window) {
|
||||||
|
return window.processId() == process.processId();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!processWindows.empty()) {
|
||||||
|
std::wcout << L"Found main windows for process " << process.processId() << ": " << std::endl;
|
||||||
|
for (auto window: processWindows) {
|
||||||
|
if (window.isMainWindow()) {
|
||||||
|
auto formattedHwnd = std::to_wstring(reinterpret_cast<uintptr_t>(window.handle()));
|
||||||
|
auto title = window.title();
|
||||||
|
|
||||||
|
std::wcout << " <" << formattedHwnd;
|
||||||
|
if (title.empty()) {
|
||||||
|
std::wcout << ":no title>";
|
||||||
|
} else {
|
||||||
|
std::wcout << ">";
|
||||||
|
}
|
||||||
|
std::wcout << " " << title << std::endl;
|
||||||
|
|
||||||
|
if (bringToForeground) window.bringToForeground();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
std::cout << "No window found for process " << process.processId() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void findWindows(const CommandLine &cmdLine) {
|
||||||
|
auto windows = Window::enumWindows();
|
||||||
|
std::map<DWORD, Process> processCache;
|
||||||
|
|
||||||
|
if (cmdLine.processId > 0) {
|
||||||
|
DWORD processId = cmdLine.processId;
|
||||||
|
|
||||||
|
windows.remove_if([&](auto &window) {
|
||||||
|
return window.processId() != processId;
|
||||||
|
});
|
||||||
|
} else if (!cmdLine.imagePath.empty()) {
|
||||||
|
auto regex = std::wregex(stringToWString(cmdLine.imagePath), std::regex_constants::ECMAScript | std::regex_constants::icase);
|
||||||
|
windows.remove_if([&](auto &window) {
|
||||||
|
try {
|
||||||
|
auto processId = window.processId();
|
||||||
|
auto &process = processCache.try_emplace(processId, processId).first->second;
|
||||||
|
process.open();
|
||||||
|
auto imagePath = process.imagePath();
|
||||||
|
|
||||||
|
return !std::regex_search(imagePath, regex);
|
||||||
|
} catch (std::exception &e) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (!cmdLine.windowTitle.empty()) {
|
||||||
|
auto regex = std::wregex(stringToWString(cmdLine.windowTitle), std::regex_constants::ECMAScript | std::regex_constants::icase);
|
||||||
|
windows.remove_if([&](auto &window) {
|
||||||
|
return !window.isMainWindow() || !std::regex_search(window.title(), regex);
|
||||||
|
});
|
||||||
|
} else if (!cmdLine.text.empty()) {
|
||||||
|
auto regex = std::wregex(stringToWString(cmdLine.text), std::regex_constants::ECMAScript | std::regex_constants::icase);
|
||||||
|
windows.remove_if([&](auto &window) {
|
||||||
|
return !std::regex_search(window.title(), regex);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map all windows to their process
|
||||||
|
std::map<DWORD, std::list<Window>> processWindows;
|
||||||
|
for (auto &window: windows) {
|
||||||
|
auto processId = window.processId();
|
||||||
|
auto &process = processCache.try_emplace(processId, processId).first->second;
|
||||||
|
auto &windowList = processWindows.try_emplace(window.processId(), std::list<Window>{}).first->second;
|
||||||
|
windowList.push_back(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto &[processId, windowList]: processWindows) {
|
||||||
|
auto [it, inserted] = processCache.try_emplace(processId, processId);
|
||||||
|
if (!it->second.isOpen()) {
|
||||||
|
it->second.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
printProcessAndWindows(it->second, windows, cmdLine.bringToForeground);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void listProcesses(const CommandLine &cmdLine) {
|
||||||
|
auto processes = Process::enumerate();
|
||||||
|
|
||||||
|
// Remove all processes we can't open
|
||||||
|
std::erase_if(processes, [](auto &process) {
|
||||||
|
try {
|
||||||
|
process.open();
|
||||||
|
return false;
|
||||||
|
} catch (std::exception &e) {
|
||||||
|
std::cerr << e.what() << std::endl;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
std::vector<std::size_t> indexes(processes.size());
|
||||||
|
std::iota(std::begin(indexes), std::end(indexes), std::size_t{0});
|
||||||
|
|
||||||
|
switch (cmdLine.sortOrder) {
|
||||||
|
case CommandLine::SortOrder::None:
|
||||||
|
break;
|
||||||
|
case CommandLine::SortOrder::ProcessId:
|
||||||
|
std::sort(indexes.begin(), indexes.end(), [&](auto &a, auto &b) -> bool {
|
||||||
|
return processes[a].processId() < processes[b].processId();
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case CommandLine::SortOrder::ImagePath:
|
||||||
|
std::sort(indexes.begin(), indexes.end(), [&](auto &a, auto &b) -> bool {
|
||||||
|
std::wstring pathA;
|
||||||
|
std::wstring pathB;
|
||||||
|
try {
|
||||||
|
pathA = processes[a].imagePath();
|
||||||
|
pathB = processes[b].imagePath();
|
||||||
|
} catch (std::exception &e) {}
|
||||||
|
return pathA < pathB;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case CommandLine::SortOrder::RamUsage:
|
||||||
|
std::sort(indexes.begin(), indexes.end(), [&](auto &a, auto &b) -> bool {
|
||||||
|
size_t usageA = 0;
|
||||||
|
size_t usageB = 0;
|
||||||
|
try {
|
||||||
|
usageA = processes[a].ramUsage().peakWorkingSetSize;
|
||||||
|
usageB = processes[b].ramUsage().peakWorkingSetSize;
|
||||||
|
} catch (std::exception &e) {}
|
||||||
|
return usageA < usageB;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (cmdLine.reverseOrder && cmdLine.sortOrder != CommandLine::SortOrder::None) {
|
||||||
|
std::reverse(indexes.begin(), indexes.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto consoleWidth = getConsoleDimensions().w;
|
||||||
|
std::wcout << InlineHeader<Process>{consoleWidth} << std::endl;
|
||||||
|
for (auto &i: indexes) {
|
||||||
|
auto &process = processes[i];
|
||||||
|
try {
|
||||||
|
process.open();
|
||||||
|
std::wcout << InlinePrint<Process>{consoleWidth, process} << std::endl;
|
||||||
|
} catch (std::exception &e) {
|
||||||
|
std::wcout << process.processId() << L" unable to open process: " << e.what() << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
try {
|
||||||
|
CommandLine cmdLine;
|
||||||
|
cmdLine.parse(convertCommandLine(argc, argv));
|
||||||
|
|
||||||
|
if (cmdLine.showHelp) {
|
||||||
|
usage();
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmdLine.listProcesses) {
|
||||||
|
listProcesses(cmdLine);
|
||||||
|
} else {
|
||||||
|
findWindows(cmdLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} catch (std::exception &e) {
|
||||||
|
showError(e.what());
|
||||||
|
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue