findwindow/main.cpp

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;
}