Release v0.6.39: isolate history UI and image clipboard memory
This commit is contained in:
1 parent
fa2f6d2ce4
commit
00c9cb0ee4
22 files changed
+747
-49
No files matched your search
+86
-14
@@ -15,6 +15,7 @@ namespace ks
|
||||
{
|
||||
#include "app_sync.inl"
|
||||
App *App::current = nullptr;
|
||||
#include "app_history.inl"
|
||||
namespace
|
||||
{
|
||||
const char *transformCommandName(int command)
|
||||
@@ -71,7 +72,7 @@ class Transaction
|
||||
App &app;
|
||||
HWND target;
|
||||
uint64_t version;
|
||||
Clip snapshot;
|
||||
ClipboardSnapshot snapshot;
|
||||
DWORD expected{};
|
||||
bool captured{};
|
||||
mutable bool invalidLogged{};
|
||||
@@ -87,7 +88,7 @@ class Transaction
|
||||
try
|
||||
{
|
||||
expected = GetClipboardSequenceNumber();
|
||||
snapshot = readClipboard(app.window, 64 * 1024 * 1024);
|
||||
snapshot.capture(app.window, expected);
|
||||
if (expected != GetClipboardSequenceNumber())
|
||||
throw std::runtime_error("Clipboard changed while taking snapshot");
|
||||
captured = true;
|
||||
@@ -103,7 +104,7 @@ class Transaction
|
||||
if (captured && GetClipboardSequenceNumber() == expected)
|
||||
try
|
||||
{
|
||||
app.ownSequence = writeClipboard(app.window, snapshot);
|
||||
app.ownSequence = snapshot.restore(app.window, expected);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
@@ -131,7 +132,7 @@ class Transaction
|
||||
if (GetClipboardSequenceNumber() != expected)
|
||||
{
|
||||
auto now = GetClipboardSequenceNumber();
|
||||
if (adopted.empty() || !ownsClipboard(target) || readClipboard(app.window).text() != adopted ||
|
||||
if (adopted.empty() || !ownsClipboard(target) || readClipboard(app.window, 32 * 1024 * 1024, ClipboardRead::Selection).text() != adopted ||
|
||||
GetClipboardSequenceNumber() != now)
|
||||
throw std::runtime_error("Operation cancelled: another application changed the clipboard");
|
||||
expected = now;
|
||||
@@ -153,7 +154,7 @@ class Transaction
|
||||
if (GetClipboardOwner() != app.window)
|
||||
throw std::runtime_error("Clipboard changed immediately after writing");
|
||||
auto text = c.text();
|
||||
if ((!text.empty() && readClipboard(app.window).text() != text) ||
|
||||
if ((!text.empty() && readClipboard(app.window, 32 * 1024 * 1024, ClipboardRead::Selection).text() != text) ||
|
||||
GetClipboardSequenceNumber() != expected)
|
||||
throw std::runtime_error("Clipboard changed while verifying our write");
|
||||
}
|
||||
@@ -227,7 +228,7 @@ class Transaction
|
||||
changed != GetClipboardSequenceNumber())
|
||||
throw std::runtime_error("Operation cancelled: copy did not belong to the target application");
|
||||
expected = changed;
|
||||
auto clip = readClipboard(app.window);
|
||||
auto clip = readClipboard(app.window, 32 * 1024 * 1024, ClipboardRead::Selection);
|
||||
auto text = clip.text();
|
||||
diagnosticEvent(L"Copy result number=" + std::to_wstring(number) + L" attempts=" +
|
||||
std::to_wstring(attempts) + L" elapsedMs=" + std::to_wstring(GetTickCount64() - started) +
|
||||
@@ -405,7 +406,8 @@ class Transaction
|
||||
}
|
||||
};
|
||||
} // namespace
|
||||
App::App(HINSTANCE h, bool test, const std::filesystem::path &dataRoot) : instance(h), smoke(test)
|
||||
App::App(HINSTANCE h, bool test, const std::filesystem::path &dataRoot, HWND service)
|
||||
: historyService(service), instance(h), smoke(test)
|
||||
{
|
||||
updateNotifier = notifyUpdateAvailable;
|
||||
current = this;
|
||||
@@ -422,7 +424,7 @@ App::App(HINSTANCE h, bool test, const std::filesystem::path &dataRoot) : instan
|
||||
registrationWarning = L"Settings were not loaded: " + wide(e.what());
|
||||
}
|
||||
setLoggingEnabled(settings.writeLog);
|
||||
if (settings.autoCorrect)
|
||||
if (settings.autoCorrect && !historyService)
|
||||
try { autoPredictor.configure(std::filesystem::path(executable()).parent_path() / L"dictionaries", settings.autoLanguages); }
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
@@ -441,6 +443,8 @@ App::App(HINSTANCE h, bool test, const std::filesystem::path &dataRoot) : instan
|
||||
}
|
||||
App::~App()
|
||||
{
|
||||
if (auto host = historyController(historyProcess)) PostMessageW(host, WM_CLOSE, 0, 0);
|
||||
if (historyProcess) CloseHandle(historyProcess);
|
||||
if (notificationRegistration) CloseHandle(notificationRegistration);
|
||||
if (syncWorker.joinable()) syncWorker.join();
|
||||
if (updateWorker.joinable())
|
||||
@@ -688,6 +692,7 @@ void App::reloadLayouts()
|
||||
}
|
||||
void App::clearHistory()
|
||||
{
|
||||
if (historyService) { remoteHistory("clear"); history->reload(); return; }
|
||||
if (busy) throw std::runtime_error("Wait for the current operation to finish.");
|
||||
if (history) history->clear();
|
||||
if (historyWindow) refreshHistory(*this);
|
||||
@@ -1523,6 +1528,7 @@ void App::transform(int command, int snippet)
|
||||
}
|
||||
void App::pasteEntry(std::wstring id, bool plain)
|
||||
{
|
||||
if (historyService) { remoteHistory("paste", id, plain); return; }
|
||||
if (installingUpdate) return;
|
||||
if (!history || busy.exchange(true))
|
||||
return;
|
||||
@@ -1544,10 +1550,20 @@ void App::pasteEntry(std::wstring id, bool plain)
|
||||
} reset{busy};
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < 20 && !modifiersReleased(); ++i)
|
||||
// Foreground activation crosses a process boundary when the
|
||||
// history UI closes. Let the destination finish activation,
|
||||
// while retaining the same input-version cancellation guard.
|
||||
for (int i = 0; i < 20 && inputVersion == version &&
|
||||
(!modifiersReleased() || GetForegroundWindow() != target); ++i)
|
||||
Sleep(10);
|
||||
if (!modifiersReleased() || GetForegroundWindow() != target || inputVersion != version)
|
||||
{
|
||||
log(L"History paste cancelled before transaction: foreground=" +
|
||||
std::to_wstring(GetForegroundWindow() == target) + L" modifiers=" +
|
||||
std::to_wstring(modifiersReleased()) + L" expectedVersion=" + std::to_wstring(version) +
|
||||
L" actualVersion=" + std::to_wstring(inputVersion.load()));
|
||||
return;
|
||||
}
|
||||
auto clip = history->load(id);
|
||||
if (plain)
|
||||
{
|
||||
@@ -1557,7 +1573,7 @@ void App::pasteEntry(std::wstring id, bool plain)
|
||||
clip = textClip(text);
|
||||
}
|
||||
Transaction tx(*this, target, version);
|
||||
tx.paste(clip);
|
||||
log(tx.paste(clip) ? L"History paste: simulated paste completed" : L"History paste cancelled before simulated paste");
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
@@ -1599,7 +1615,8 @@ void App::captureHistory()
|
||||
}
|
||||
return;
|
||||
}
|
||||
auto clip = readClipboard(window, (size_t)config.maxImageMiB * 1024 * 1024);
|
||||
auto clip = readClipboard(window, (size_t)config.maxImageMiB * 1024 * 1024,
|
||||
ClipboardRead::WithoutImages, sequence);
|
||||
if (sequence != GetClipboardSequenceNumber())
|
||||
return;
|
||||
for (auto &f : clip.formats)
|
||||
@@ -1618,6 +1635,7 @@ void App::captureHistory()
|
||||
}
|
||||
void App::recognizeEntry(std::wstring id)
|
||||
{
|
||||
if (historyService) { remoteHistory("ocr", id); return; }
|
||||
if (!history || busy.exchange(true))
|
||||
return;
|
||||
auto sequence = GetClipboardSequenceNumber();
|
||||
@@ -1788,7 +1806,37 @@ LRESULT CALLBACK App::windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
|
||||
auto *app = current;
|
||||
if (!app)
|
||||
return DefWindowProcW(hwnd, msg, wp, lp);
|
||||
if (msg == app->taskbarCreated && app->taskbarCreated)
|
||||
if (app->historyService) {
|
||||
if (msg == HistoryHostShow || msg == HistoryHostRefresh) {
|
||||
try {
|
||||
app->history->reload();
|
||||
if (msg == HistoryHostShow) showHistory(*app);
|
||||
else if (IsWindowVisible(app->historyWindow)) refreshHistory(*app);
|
||||
} catch (const std::exception &e) { app->report(wide(e.what())); }
|
||||
return 0;
|
||||
}
|
||||
if (msg == WM_TIMER) {
|
||||
if (!IsWindow(app->historyService)) PostMessageW(hwnd, WM_CLOSE, 0, 0);
|
||||
return 0;
|
||||
}
|
||||
if (msg == WM_CLOSE) {
|
||||
EnumThreadWindows(GetCurrentThreadId(), [](HWND child, LPARAM) -> BOOL {
|
||||
wchar_t name[64]{}; GetClassNameW(child, name, 64);
|
||||
if (std::wstring(name) == L"KSwitcher.Entry" || std::wstring(name) == L"KSwitcher.History")
|
||||
DestroyWindow(child);
|
||||
return TRUE;
|
||||
}, 0);
|
||||
DestroyWindow(hwnd);
|
||||
return 0;
|
||||
}
|
||||
if (msg == WM_DESTROY) { PostQuitMessage(0); return 0; }
|
||||
if (msg == ErrorMessage) {
|
||||
std::unique_ptr<std::wstring> text(reinterpret_cast<std::wstring *>(lp));
|
||||
MessageBoxW(app->historyWindow, text->c_str(), L"Clipboard history", MB_OK | MB_ICONWARNING);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
if (!app->historyService && msg == app->taskbarCreated && app->taskbarCreated)
|
||||
{
|
||||
app->addTray();
|
||||
return 0;
|
||||
@@ -1902,6 +1950,7 @@ LRESULT CALLBACK App::windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
|
||||
case WM_COPYDATA:
|
||||
{
|
||||
auto *data = (COPYDATASTRUCT *)lp;
|
||||
if (data && data->dwData == HistoryRequest) return app->historyRequest((HWND)wp, *data);
|
||||
if (data && data->dwData >= ConfigRead && data->dwData < ConfigEnd)
|
||||
{
|
||||
if (app->installingUpdate) return FALSE;
|
||||
@@ -1988,7 +2037,7 @@ int App::execute()
|
||||
WNDCLASSW cls{};
|
||||
cls.hInstance = instance;
|
||||
cls.lpfnWndProc = windowProc;
|
||||
cls.lpszClassName = L"KSwitcher.Main";
|
||||
cls.lpszClassName = historyService ? HistoryHostClass : L"KSwitcher.Main";
|
||||
cls.hIcon = LoadIconW(instance, MAKEINTRESOURCEW(IDI_KSWITCHER));
|
||||
RegisterClassW(&cls);
|
||||
window = CreateWindowExW(WS_EX_TOOLWINDOW, cls.lpszClassName, L"KSwitcher", WS_POPUP, 0, 0, 0, 0, nullptr,
|
||||
@@ -2022,6 +2071,29 @@ int App::execute()
|
||||
}
|
||||
CoUninitialize();
|
||||
});
|
||||
if (historyService) {
|
||||
try {
|
||||
auto bytes = unprotect(readFile(data / L"history-view.bin", 4 * 1024 * 1024));
|
||||
auto state = HistoryJson::parse(bytes);
|
||||
historySelection = wide(state.value("selection", std::string{}));
|
||||
historyTop = wide(state.value("top", std::string{}));
|
||||
historyQuery = wide(state.value("query", std::string{}));
|
||||
} catch (...) {}
|
||||
showHistory(*this);
|
||||
remoteHistory("ready", std::to_wstring(reinterpret_cast<uintptr_t>(historyWindow)));
|
||||
SetTimer(window, 1, 1000, nullptr);
|
||||
MSG message{};
|
||||
while (GetMessageW(&message, nullptr, 0, 0) > 0) {
|
||||
if (message.message == WM_KEYDOWN && message.wParam == VK_ESCAPE && historyWindow &&
|
||||
(message.hwnd == historyWindow || IsChild(historyWindow, message.hwnd))) {
|
||||
SendMessageW(historyWindow, WM_CLOSE, 0, 0);
|
||||
continue;
|
||||
}
|
||||
if (historyWindow && message.message == WM_SYSCHAR && IsDialogMessageW(historyWindow, &message)) continue;
|
||||
TranslateMessage(&message); DispatchMessageW(&message);
|
||||
}
|
||||
return int(message.wParam);
|
||||
}
|
||||
keyboardHook = SetWindowsHookExW(WH_KEYBOARD_LL, keyProc, instance, 0);
|
||||
mouseHook = SetWindowsHookExW(WH_MOUSE_LL, mouseProc, instance, 0);
|
||||
registerHotkeys(settings);
|
||||
@@ -2058,7 +2130,7 @@ int App::execute()
|
||||
continue;
|
||||
}
|
||||
// Preserve history-specific Enter/Shift+Enter and Ctrl+digit handling.
|
||||
if (historyWindow && msg.message == WM_SYSCHAR && IsDialogMessageW(historyWindow, &msg))
|
||||
if (!isolateHistory && historyWindow && msg.message == WM_SYSCHAR && IsDialogMessageW(historyWindow, &msg))
|
||||
continue;
|
||||
if (settingsWindow && IsDialogMessageW(settingsWindow, &msg))
|
||||
continue;
|
||||
|
||||
Reference in new issue
Block a user