123 lines
5.4 KiB
C++
123 lines
5.4 KiB
C++
#include "configuration.h"
|
|
#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)
|
|
{
|
|
if (!configurationPeer(sender, L"KSwitcher.Config.exe") || message.cbData > ConfigLimit ||
|
|
(message.cbData && !message.lpData)) return FALSE;
|
|
Bytes reply;
|
|
bool ok = false;
|
|
try
|
|
{
|
|
switch (message.dwData)
|
|
{
|
|
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:
|
|
{
|
|
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);
|
|
if (Bytes(begin, begin + length) != encodeSettings(app.settings))
|
|
throw std::runtime_error("Settings changed in KSwitcher. Close and reopen the configurator before saving.");
|
|
Bytes next(begin + length, static_cast<const unsigned char *>(message.lpData) + message.cbData);
|
|
if (next.empty()) throw std::runtime_error("Empty settings request.");
|
|
app.applySettings(decodeSettings(next, 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());
|
|
}
|
|
COPYDATASTRUCT response{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;
|
|
}
|
|
}
|