Files
hrynco-kswitcher/src/app_history.inl
T

101 lines
5.0 KiB
C++

// History UI has no clipboard listener or hooks. The resident remains the only writer.
namespace {
constexpr ULONG_PTR HistoryRequest = 0x4b530200;
constexpr UINT HistoryHostShow = WM_APP + 71, HistoryHostRefresh = WM_APP + 72;
constexpr wchar_t HistoryHostClass[] = L"KSwitcher.HistoryHost";
using HistoryJson = nlohmann::json;
HWND historyController(HANDLE process)
{
if (!process || WaitForSingleObject(process, 0) == WAIT_OBJECT_0) return nullptr;
auto window = FindWindowW(HistoryHostClass, nullptr);
DWORD pid{}; GetWindowThreadProcessId(window, &pid);
return pid == GetProcessId(process) ? window : nullptr;
}
}
void App::openHistoryProcess()
{
auto foreground = GetForegroundWindow();
if (foreground != window && foreground != historyWindow) pasteTarget = foreground;
if (historyProcess && WaitForSingleObject(historyProcess, 0) == WAIT_OBJECT_0) {
CloseHandle(historyProcess); historyProcess = nullptr; historyWindow = nullptr;
}
if (historyProcess) {
if (auto host = historyController(historyProcess)) {
AllowSetForegroundWindow(GetProcessId(historyProcess));
PostMessageW(host, HistoryHostShow, 0, 0);
}
return;
}
auto path = executable();
auto command = L"\"" + path + L"\" --history-host=" + std::to_wstring(reinterpret_cast<uintptr_t>(window));
STARTUPINFOW startup{sizeof(startup)};
startup.dwFlags = STARTF_USESHOWWINDOW; startup.wShowWindow = SW_HIDE;
PROCESS_INFORMATION child{};
if (!CreateProcessW(path.c_str(), command.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW,
nullptr, std::filesystem::path(path).parent_path().c_str(), &startup, &child))
throw std::runtime_error("Cannot start clipboard history window");
CloseHandle(child.hThread);
historyProcess = child.hProcess;
AllowSetForegroundWindow(child.dwProcessId);
}
void App::refreshHistoryProcess()
{
if (auto host = historyController(historyProcess)) PostMessageW(host, HistoryHostRefresh, 0, 0);
}
void App::remoteHistory(const char *action, const std::wstring &id, bool plain)
{
if (!historyService || !configurationPeer(historyService, L"KSwitcher.exe"))
throw std::runtime_error("KSwitcher has stopped. Reopen clipboard history.");
auto text = HistoryJson{{"action", action}, {"id", utf8(id)}, {"plain", plain}}.dump();
if (std::string_view(action) == "paste") {
DWORD residentPid{}; GetWindowThreadProcessId(historyService, &residentPid);
AllowSetForegroundWindow(residentPid);
}
COPYDATASTRUCT message{HistoryRequest, DWORD(text.size()), text.data()};
DWORD_PTR result{};
if (!SendMessageTimeoutW(historyService, WM_COPYDATA, reinterpret_cast<WPARAM>(window),
reinterpret_cast<LPARAM>(&message), SMTO_ABORTIFHUNG | SMTO_ERRORONEXIT,
15000, &result) || result != 1)
throw std::runtime_error("KSwitcher could not complete the history request. Reopen history and retry.");
}
LRESULT App::historyRequest(HWND sender, const COPYDATASTRUCT &message)
{
DWORD pid{}; GetWindowThreadProcessId(sender, &pid);
if (!historyProcess || pid != GetProcessId(historyProcess) ||
!configurationPeer(sender, L"KSwitcher.exe") || message.cbData > 4096 ||
!message.cbData || !message.lpData || installingUpdate) return FALSE;
try {
const auto *first = static_cast<const char *>(message.lpData);
auto job = HistoryJson::parse(first, first + message.cbData);
auto action = job.at("action").get<std::string>();
auto id = wide(job.at("id").get<std::string>());
if (id.size() > 128) return FALSE;
if (action == "ready") {
auto ui = reinterpret_cast<HWND>(uintptr_t(std::stoull(id)));
DWORD uiPid{}; GetWindowThreadProcessId(ui, &uiPid);
wchar_t name[64]{}; GetClassNameW(ui, name, 64);
if (uiPid != pid || std::wstring(name) != L"KSwitcher.History") return FALSE;
historyWindow = ui;
} else if (action == "paste") {
if (busy || !history) return FALSE;
pasteEntry(id, job.at("plain").get<bool>());
} else if (action == "ocr") {
if (busy || !history) return FALSE;
recognizeEntry(id);
} else if (action == "clear") clearHistory();
else if (action == "pin" || action == "remove") {
if (busy || !history) return FALSE;
if (action == "pin") history->pin(id); else history->remove(id);
} else return FALSE;
return TRUE;
} catch (...) { return FALSE; }
}
void App::saveHistoryView(const std::wstring &selection, const std::wstring &top, const std::wstring &query, bool pinnedOnly)
{
if (!historyService) return;
try {
auto text = HistoryJson{{"selection", utf8(selection)}, {"top", utf8(top)}, {"query", utf8(query)}, {"pinnedOnly", pinnedOnly}}.dump();
atomicWrite(data / L"history-view.bin", protect(Bytes(text.begin(), text.end())));
} catch (...) {} // Closing the UI must still release its process if state cannot be saved.
}