151 lines
6.9 KiB
C++
151 lines
6.9 KiB
C++
#include "configuration.h"
|
|
#include "settings_merge.h"
|
|
#include "../vendor/json.hpp"
|
|
#include <cstring>
|
|
namespace ks
|
|
{
|
|
bool configurationPeer(HWND window, const wchar_t *filename)
|
|
{
|
|
DWORD pid{};
|
|
GetWindowThreadProcessId(window, &pid);
|
|
HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid);
|
|
if (!process) return false;
|
|
wchar_t path[32768]; DWORD count = std::size(path);
|
|
bool ok = QueryFullProcessImageNameW(process, 0, path, &count) != 0;
|
|
CloseHandle(process);
|
|
auto expected = std::filesystem::path(executable()).parent_path() / filename;
|
|
return ok && _wcsicmp(path, expected.c_str()) == 0;
|
|
}
|
|
void launchConfiguration(App &app, bool snippets, bool logs)
|
|
{
|
|
auto path = std::filesystem::path(executable()).parent_path() / L"KSwitcher.Config.exe";
|
|
if (!std::filesystem::exists(path))
|
|
throw std::runtime_error("KSwitcher.Config.exe is missing. Extract the complete application package.");
|
|
auto command = L"\"" + path.wstring() + L"\"" + (logs ? L" --logs" : snippets ? L" --snippets" : L"");
|
|
STARTUPINFOW startup{sizeof(startup)};
|
|
PROCESS_INFORMATION process{};
|
|
if (!CreateProcessW(path.c_str(), command.data(), nullptr, nullptr, FALSE, 0, nullptr,
|
|
path.parent_path().c_str(), &startup, &process))
|
|
throw std::runtime_error(utf8(lastError(L"Cannot start configurator")));
|
|
AllowSetForegroundWindow(process.dwProcessId);
|
|
CloseHandle(process.hThread);
|
|
CloseHandle(process.hProcess);
|
|
(void)app;
|
|
}
|
|
LRESULT configurationRequest(App &app, HWND sender, const COPYDATASTRUCT &message)
|
|
{
|
|
const bool filesPeers = message.dwData == FilesLanPeers;
|
|
if (!configurationPeer(sender, filesPeers ? L"KSwitcher.Files.exe" : L"KSwitcher.Config.exe") || message.cbData > ConfigLimit ||
|
|
(message.cbData && !message.lpData)) return FALSE;
|
|
Bytes reply;
|
|
bool ok = false;
|
|
try
|
|
{
|
|
switch (message.dwData)
|
|
{
|
|
case FilesLanPeers:
|
|
{
|
|
if(message.cbData) return FALSE;
|
|
const std::string request=R"({"action":"filesPeers"})";
|
|
const auto snapshot=nlohmann::json::parse(app.lanCommand(Bytes(request.begin(),request.end())));
|
|
auto paired=nlohmann::json::array();
|
|
for(const auto &peer:snapshot.at("peers"))
|
|
{
|
|
if(!peer.value("paired",false)) continue;
|
|
const auto name=wide(peer.at("name").get<std::string>());
|
|
if(name.empty() || name.find_first_of(std::wstring_view(L"\0\r\n\t",4))!=name.npos) continue;
|
|
paired.push_back({{"id",peer.at("id")},{"name",peer.at("name")},{"address",peer.at("address")},{"filesBrowse",peer.value("filesBrowse",false)}});
|
|
}
|
|
const auto encoded=paired.dump();
|
|
reply.assign(encoded.begin(),encoded.end());
|
|
break;
|
|
}
|
|
case ConfigLan:
|
|
{
|
|
if (!message.cbData || message.cbData > 4096) throw std::runtime_error("Invalid LAN request.");
|
|
auto first = static_cast<const unsigned char *>(message.lpData);
|
|
reply = app.lanCommand(Bytes(first, first + message.cbData));
|
|
break;
|
|
}
|
|
case ConfigSyncConflicts:
|
|
reply = app.syncConflictData();
|
|
break;
|
|
case ConfigSyncResolve:
|
|
{
|
|
if (!message.cbData) throw std::runtime_error("Empty conflict choices.");
|
|
auto first = static_cast<const unsigned char *>(message.lpData);
|
|
auto status = utf8(app.syncCommand(4, decodeSyncChoices(Bytes(first, first + message.cbData))));
|
|
reply.assign(status.begin(), status.end());
|
|
break;
|
|
}
|
|
case ConfigSync:
|
|
{
|
|
if (message.cbData != 1 || *static_cast<const unsigned char *>(message.lpData) > 3)
|
|
throw std::runtime_error("Invalid sync command.");
|
|
auto status = utf8(app.syncCommand(*static_cast<const unsigned char *>(message.lpData)));
|
|
reply.assign(status.begin(), status.end());
|
|
break;
|
|
}
|
|
case ConfigSave:
|
|
case ConfigSaveEdited:
|
|
{
|
|
if (app.busy) throw std::runtime_error("Wait for the current text operation to finish.");
|
|
if (message.cbData < sizeof(DWORD)) throw std::runtime_error("Invalid settings request.");
|
|
DWORD length{};
|
|
memcpy(&length, message.lpData, sizeof(length));
|
|
if (length > message.cbData - sizeof(length)) throw std::runtime_error("Invalid settings snapshot.");
|
|
auto begin = static_cast<const unsigned char *>(message.lpData) + sizeof(length);
|
|
Bytes next(begin + length, static_cast<const unsigned char *>(message.lpData) + message.cbData);
|
|
if (next.empty()) throw std::runtime_error("Empty settings request.");
|
|
auto merged=mergeConfigurationSettings(Bytes(begin,begin+length),next,encodeSettings(app.settings),message.dwData==ConfigSaveEdited);
|
|
app.applySettings(decodeSettings(merged, app.layouts));
|
|
reply = encodeSettings(app.settings);
|
|
break;
|
|
}
|
|
case ConfigLayouts:
|
|
if (app.busy) throw std::runtime_error("Wait for the current text operation to finish.");
|
|
app.reloadLayouts();
|
|
[[fallthrough]];
|
|
case ConfigRead:
|
|
reply = encodeSettings(app.settings);
|
|
break;
|
|
case ConfigClearHistory:
|
|
app.clearHistory();
|
|
break;
|
|
case ConfigCapture:
|
|
{
|
|
if (message.cbData != sizeof(HWND)) throw std::runtime_error("Invalid shortcut field.");
|
|
HWND field{}; memcpy(&field, message.lpData, sizeof(field));
|
|
DWORD fieldPid{}, senderPid{};
|
|
GetWindowThreadProcessId(field, &fieldPid);
|
|
GetWindowThreadProcessId(sender, &senderPid);
|
|
if (field && fieldPid != senderPid) throw std::runtime_error("Invalid shortcut field owner.");
|
|
app.setHotkeyCapture(field);
|
|
break;
|
|
}
|
|
case ConfigLogs:
|
|
PostMessageW(app.window, WM_HOTKEY, 22, 0);
|
|
break;
|
|
case ConfigClearLog:
|
|
if (message.cbData != 1 || *static_cast<const unsigned char *>(message.lpData) > 1)
|
|
throw std::runtime_error("Invalid log selection.");
|
|
app.clearLog(*static_cast<const unsigned char *>(message.lpData) != 0);
|
|
break;
|
|
default: return FALSE;
|
|
}
|
|
ok = true;
|
|
}
|
|
catch (const std::exception &error)
|
|
{
|
|
std::string text = error.what();
|
|
reply.assign(text.begin(), text.end());
|
|
}
|
|
if(filesPeers && !ok) return FALSE;
|
|
COPYDATASTRUCT response{filesPeers?FilesLanPeersReply:ConfigReply, (DWORD)reply.size(), reply.data()};
|
|
DWORD_PTR received{};
|
|
if (!SendMessageTimeoutW(sender, WM_COPYDATA, (WPARAM)app.window, (LPARAM)&response,
|
|
SMTO_ABORTIFHUNG, 2000, &received)) return FALSE;
|
|
return ok ? 1 : 2;
|
|
}
|
|
}
|