Initial commit.
This commit is contained in:
parent
29be0a41ab
commit
ef06621b5e
|
|
@ -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
|
||||||
20
LICENSE
20
LICENSE
|
|
@ -1,9 +1,21 @@
|
||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2025 nessa
|
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:
|
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.
|
||||||
|
|
|
||||||
|
|
@ -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