101 lines
5.0 KiB
C++
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.
|
|
}
|