254 lines
9.9 KiB
C++
254 lines
9.9 KiB
C++
/*
|
|
* 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;
|
|
}
|