#include "configuration.h" #include "settings_merge.h" #include "../vendor/json.hpp" #include 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()); 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(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(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(message.lpData) > 3) throw std::runtime_error("Invalid sync command."); auto status = utf8(app.syncCommand(*static_cast(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(message.lpData) + sizeof(length); Bytes next(begin + length, static_cast(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(message.lpData) > 1) throw std::runtime_error("Invalid log selection."); app.clearLog(*static_cast(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; } }