Release v0.6.57: multi-PC input, shared clipboard and resilient Settings Save
This commit is contained in:
1 parent
82292c8a7e
commit
5de010ee44
39 files changed
+1707
-312
No files matched your search
+84
-10
@@ -3,6 +3,7 @@
|
||||
#include "../vendor/json.hpp"
|
||||
#include "../resources/resource.h"
|
||||
#include "ocr.h"
|
||||
#include "lan_clipboard_image.h"
|
||||
#include "configuration.h"
|
||||
#include "diagnostics.h"
|
||||
#include "paste_probe.h"
|
||||
@@ -724,11 +725,11 @@ bool App::registerHotkeys(const Settings &s, bool rollback)
|
||||
|
||||
Bytes App::lanCommand(const Bytes &command)
|
||||
{
|
||||
if (!lanService) lanService = std::make_unique<LanService>(data);
|
||||
if (!lanService) lanService = std::make_unique<LanService>(data,window);
|
||||
auto reply = lanService->command(command);
|
||||
if (lanService->inputEnabled()) SetTimer(window, 7, 10, nullptr);
|
||||
else KillTimer(window, 7);
|
||||
if (lanService->takeInputChange()) resetInput();
|
||||
if (lanService->takeInputChange()) resetLanInput();
|
||||
return reply;
|
||||
}
|
||||
void App::applySettings(const Settings &requested)
|
||||
@@ -840,6 +841,14 @@ bool App::isHotkey(DWORD key, UINT mods)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void App::resetLanInput()
|
||||
{
|
||||
// Releases may have been consumed on another desktop. Do not retain local
|
||||
// shortcut/chat latches across an ownership handoff.
|
||||
hotkeyHook = {};
|
||||
chatSwitchHook = {};
|
||||
resetInput();
|
||||
}
|
||||
void App::resetInput()
|
||||
{
|
||||
std::lock_guard lock(stateMutex);
|
||||
@@ -1132,7 +1141,7 @@ LRESULT CALLBACK App::keyProc(int code, WPARAM wp, LPARAM lp)
|
||||
(wp == WM_KEYDOWN || wp == WM_SYSKEYDOWN || wp == WM_KEYUP || wp == WM_SYSKEYUP))
|
||||
{
|
||||
bool consumed = current->lanService->keyboard(*(KBDLLHOOKSTRUCT *)lp, wp == WM_KEYDOWN || wp == WM_SYSKEYDOWN);
|
||||
if (current->lanService->takeInputChange()) current->resetInput();
|
||||
if (current->lanService->takeInputChange()) current->resetLanInput();
|
||||
if (consumed) return 1; // Forwarded keys must not reach local hotkeys/snippets or other routers.
|
||||
}
|
||||
// MWB suppresses source-machine events that it forwards elsewhere. Let
|
||||
@@ -1168,7 +1177,7 @@ LRESULT CALLBACK App::mouseProc(int code, WPARAM wp, LPARAM lp)
|
||||
{
|
||||
bool consumed = current->lanService->mouse(UINT(wp), *(MSLLHOOKSTRUCT *)lp,
|
||||
!current->busy && !current->suspended && !current->hotkeyCapture);
|
||||
if (current->lanService->takeInputChange()) current->resetInput();
|
||||
if (current->lanService->takeInputChange()) current->resetLanInput();
|
||||
if (consumed) return 1;
|
||||
}
|
||||
// KSwitcher generates no mouse input. Remote clicks invalidate a typing
|
||||
@@ -1805,15 +1814,56 @@ void App::pasteEntry(std::wstring id, bool plain)
|
||||
}
|
||||
});
|
||||
}
|
||||
namespace {
|
||||
Clip sharedClipboard(Clip clip)
|
||||
{
|
||||
// File handles/paths and application-private formats are local to their PC.
|
||||
std::erase_if(clip.formats,[](const Format &f){return !(
|
||||
(f.name.empty()&&(f.id==CF_UNICODETEXT||f.id==CF_TEXT||f.id==CF_LOCALE||f.id==CF_DIB||f.id==CF_DIBV5))||
|
||||
f.name==L"PNG"||f.name==L"HTML Format"||f.name==L"Rich Text Format");});
|
||||
return clip;
|
||||
}
|
||||
}
|
||||
void App::publishClipboard(Clip clip,const std::wstring &app)
|
||||
{
|
||||
clip=sharedClipboard(std::move(clip));if(clip.formats.empty())return;
|
||||
auto originalSize=clip.size();
|
||||
clip=lan::compactClipboardImage(std::move(clip));
|
||||
auto packet=new LanClipboard{{},{},utf8(app),encodeClip(clip)};
|
||||
log(L"LAN clipboard queued: original="+std::to_wstring(originalSize)+L" bytes, wire="+std::to_wstring(packet->data.size())+L" bytes");
|
||||
if(!PostMessageW(window,LanClipboardPublishMessage,0,reinterpret_cast<LPARAM>(packet)))delete packet;
|
||||
}
|
||||
void App::receiveClipboard()
|
||||
{
|
||||
if(!lanService)return;
|
||||
auto packets=lanService->takeClipboard();auto config=settings;
|
||||
for(auto &packet:packets)enqueue([this,packet=std::move(packet),config]{
|
||||
try {
|
||||
auto clip=sharedClipboard(decodeClip(packet.data));if(clip.formats.empty())return;
|
||||
auto sequence=GetClipboardSequenceNumber();
|
||||
if(!suspended&&(sequence==packet.sequence||sequence==lanSequence.load())){
|
||||
const std::wstring marker=L"KSwitcher.LAN.Clipboard";
|
||||
auto active=lan::clipboardImageForPaste(clip);
|
||||
active.formats.push_back({RegisterClipboardFormatW(marker.c_str()),marker,Bytes{1}});
|
||||
ownSequence=lanSequence=writeClipboardGuarded(window,active,sequence);
|
||||
log(L"LAN clipboard activated: "+std::to_wstring(packet.data.size())+L" bytes");
|
||||
}
|
||||
if(config.history&&history){history->add(clip,wide(packet.app),config,packet.peer,wide(packet.name));
|
||||
scheduleHistoryOcr(config);PostMessageW(window,HistoryMessage,0,0);}
|
||||
}catch(const std::exception &e){log(L"LAN clipboard receive: "+wide(e.what()));}
|
||||
});
|
||||
}
|
||||
void App::captureHistory()
|
||||
{
|
||||
DWORD sequence = GetClipboardSequenceNumber();
|
||||
if (suspended || sequence == ownSequence || !settings.history || !history ||
|
||||
if (suspended || sequence == ownSequence || !history ||
|
||||
GetTickCount64() < pauseUntil)
|
||||
return;
|
||||
const bool share=lanService&&lanService->clipboardEnabled();
|
||||
if(!settings.history&&!share)return;
|
||||
Settings config = settings;
|
||||
enqueue(
|
||||
[this, sequence, config]
|
||||
[this, sequence, config, share]
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -1821,19 +1871,28 @@ void App::captureHistory()
|
||||
return;
|
||||
UINT exclude = RegisterClipboardFormatW(L"ExcludeClipboardContentFromMonitorProcessing"),
|
||||
ignore = RegisterClipboardFormatW(L"Clipboard Viewer Ignore");
|
||||
if (IsClipboardFormatAvailable(exclude) || IsClipboardFormatAvailable(ignore))
|
||||
if (IsClipboardFormatAvailable(exclude) || IsClipboardFormatAvailable(ignore) ||
|
||||
IsClipboardFormatAvailable(RegisterClipboardFormatW(L"KSwitcher.LAN.Clipboard")))
|
||||
return;
|
||||
auto owner = GetClipboardOwner();
|
||||
auto app = processName(owner ? owner : GetForegroundWindow());
|
||||
for (auto &name : config.excluded)
|
||||
if (lower(name) == lower(app))
|
||||
return;
|
||||
if(!config.history){
|
||||
auto clip=readClipboard(window,(size_t)config.maxImageMiB*1024*1024,ClipboardRead::All,sequence);
|
||||
for(const auto &f:clip.formats)if(f.name==L"CanIncludeInClipboardHistory"&&f.data.size()>=4&&!f.data[0]&&!f.data[1]&&!f.data[2]&&!f.data[3])return;
|
||||
if(sequence==GetClipboardSequenceNumber())publishClipboard(std::move(clip),app);
|
||||
return;
|
||||
}
|
||||
if (IsClipboardFormatAvailable(CF_DIB) || IsClipboardFormatAvailable(CF_DIBV5) ||
|
||||
IsClipboardFormatAvailable(CF_BITMAP) ||
|
||||
IsClipboardFormatAvailable(RegisterClipboardFormatW(L"PNG")))
|
||||
{
|
||||
if (history->captureImage(sequence, app, config))
|
||||
{
|
||||
auto entries=history->entries();
|
||||
if(share&&!entries.empty())publishClipboard(history->load(entries.front().id),app);
|
||||
scheduleHistoryOcr(config);
|
||||
PostMessageW(window, HistoryMessage, 0, 0);
|
||||
}
|
||||
@@ -1848,6 +1907,7 @@ void App::captureHistory()
|
||||
!f.data[1] && !f.data[2] && !f.data[3])
|
||||
return;
|
||||
history->add(clip, app, config);
|
||||
if(share)publishClipboard(clip,app);
|
||||
scheduleHistoryOcr(config);
|
||||
PostMessageW(window, HistoryMessage, 0, 0);
|
||||
}
|
||||
@@ -2222,13 +2282,26 @@ LRESULT CALLBACK App::windowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
case LanClipboardPublishMessage:
|
||||
{
|
||||
std::unique_ptr<LanClipboard> packet(reinterpret_cast<LanClipboard*>(lp));
|
||||
try{if(app->lanService)app->lanService->publishClipboard(std::move(packet->data),packet->app);}
|
||||
catch(const std::exception &e){log(L"LAN clipboard send: "+wide(e.what()));}
|
||||
return 0;
|
||||
}
|
||||
case LanClipboardReadyMessage: app->receiveClipboard();return 0;
|
||||
case LanInputReadyMessage:
|
||||
if(app->lanService&&app->lanService->tickInput(!app->busy&&!app->suspended&&!app->hotkeyCapture))app->resetLanInput();
|
||||
return 0;
|
||||
case WM_TIMER:
|
||||
if (wp == 7)
|
||||
{
|
||||
if (app->lanService)
|
||||
{
|
||||
if (app->suspended) app->lanService->cancelInput();
|
||||
if (app->lanService->tickInput(!app->busy && !app->hotkeyCapture)) app->resetInput();
|
||||
// suspended also guards clipboard/text transactions (correction,
|
||||
// conversion, snippets). Those must not disconnect remote input.
|
||||
// Actual system suspension is handled by WM_POWERBROADCAST.
|
||||
if (app->lanService->tickInput(!app->busy && !app->suspended && !app->hotkeyCapture)) app->resetLanInput();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -2346,6 +2419,7 @@ int App::execute()
|
||||
historyTop = wide(state.value("top", std::string{}));
|
||||
historyQuery = wide(state.value("query", std::string{}));
|
||||
historyPinnedOnly = state.value("pinnedOnly", false);
|
||||
historyComputers = state.value("computers",std::vector<std::string>{});
|
||||
} catch (...) {}
|
||||
showHistory(*this);
|
||||
remoteHistory("ready", std::to_wstring(reinterpret_cast<uintptr_t>(historyWindow)));
|
||||
@@ -2373,7 +2447,7 @@ int App::execute()
|
||||
addTray();
|
||||
if (!smoke) startBrowserChatMonitor();
|
||||
if (!smoke && std::filesystem::exists(data / L"lan-pairing.bin"))
|
||||
try { lanService = std::make_unique<LanService>(data); }
|
||||
try { lanService = std::make_unique<LanService>(data,window); }
|
||||
catch (const std::exception &e) { report(L"LAN module: " + wide(e.what())); }
|
||||
if (lanService && lanService->inputEnabled()) SetTimer(window, 7, 10, nullptr);
|
||||
SetTimer(window, 1, 60000, nullptr);
|
||||
|
||||
Reference in new issue
Block a user