#include "configuration.h" #include #include namespace ks { App *App::current = nullptr; namespace { HWND service{}; Bytes response; bool replied{}; Bytes request(App &app, ULONG_PTR command, const Bytes &payload = {}) { if (!service || !configurationPeer(service, L"KSwitcher.exe")) throw std::runtime_error("KSwitcher has stopped. Close and reopen the configurator."); if (payload.size() > ConfigLimit) throw std::runtime_error("Settings are too large."); response.clear(); replied = false; COPYDATASTRUCT message{command, (DWORD)payload.size(), const_cast(payload.data())}; DWORD_PTR result{}; SetLastError(ERROR_SUCCESS); if (!SendMessageTimeoutW(service, WM_COPYDATA, (WPARAM)app.window, (LPARAM)&message, SMTO_ABORTIFHUNG | SMTO_ERRORONEXIT, 15000, &result) || !replied) { if (GetLastError() == ERROR_ACCESS_DENIED) throw std::runtime_error("Open Settings from the KSwitcher tray menu so both processes use the same privileges."); throw std::runtime_error("KSwitcher did not respond. The operation may still finish; reopen Settings to check."); } if (result != 1) throw std::runtime_error(response.empty() ? "KSwitcher rejected the request." : std::string(response.begin(), response.end())); return response; } LRESULT CALLBACK controller(HWND window, UINT message, WPARAM wp, LPARAM lp) { auto *app = App::current; if (!app) return DefWindowProcW(window, message, wp, lp); if (message == WM_COPYDATA) { auto *data = reinterpret_cast(lp); if ((HWND)wp == service && data && data->dwData == ConfigReply && data->cbData <= ConfigLimit && (!data->cbData || data->lpData)) { response.clear(); if (data->cbData) { auto first = static_cast(data->lpData); response.assign(first, first + data->cbData); } replied = true; return TRUE; } return FALSE; } if (message == WM_APP + 20) { try { if (wp == 21) showSnippets(*app); else if (wp == 22) showLogs(*app); else showSettings(*app); } catch (const std::exception &e) { MessageBoxW(app->settingsWindow, wide(e.what()).c_str(), L"KSwitcher configurator", MB_OK | MB_ICONERROR); } return 0; } if (message == WM_CLOSE) { if (app->settingsWindow) DestroyWindow(app->settingsWindow); if (app->snippetsWindow) DestroyWindow(app->snippetsWindow); if (app->logWindow) DestroyWindow(app->logWindow); PostQuitMessage(0); return 0; } return DefWindowProcW(window, message, wp, lp); } } // The configurator owns no history store, dictionaries, worker or input hooks. // All mutations are validated and committed by the resident process. App::App(HINSTANCE h, bool, const std::filesystem::path &) : instance(h) { current = this; layouts = readLayouts(); } App::~App() { if (settingsWindow) DestroyWindow(settingsWindow); if (snippetsWindow) DestroyWindow(snippetsWindow); if (logWindow) DestroyWindow(logWindow); if (window) DestroyWindow(window); current = nullptr; } void App::applySettings(const Settings &next) { validateSettings(next); auto before = encodeSettings(settings), after = encodeSettings(next); DWORD length = (DWORD)before.size(); Bytes payload(sizeof(length)); memcpy(payload.data(), &length, sizeof(length)); payload.insert(payload.end(), before.begin(), before.end()); payload.insert(payload.end(), after.begin(), after.end()); settings = decodeSettings(request(*this, ConfigSave, payload), layouts); } void App::reloadLayouts() { auto bytes = request(*this, ConfigLayouts); layouts = readLayouts(); settings = decodeSettings(bytes, layouts); } void App::clearHistory() { request(*this, ConfigClearHistory); } void App::setHotkeyCapture(HWND field) { hotkeyCapture = field; Bytes payload(sizeof(field)); memcpy(payload.data(), &field, sizeof(field)); // Focus notifications must not let an exception escape a window callback. try { request(*this, ConfigCapture, payload); } catch (const std::exception &e) { hotkeyCapture = nullptr; if (field) MessageBoxW(settingsWindow, wide(e.what()).c_str(), L"Shortcut capture unavailable", MB_OK | MB_ICONWARNING); } } void App::clearLog(bool previous) { request(*this, ConfigClearLog, Bytes{static_cast(previous)}); } int runConfigurator(HINSTANCE instance, bool snippets, bool logs) { HANDLE singleton = CreateMutexW(nullptr, TRUE, L"Local\\KSwitcher.Config.Singleton"); if (!singleton) throw std::runtime_error("Cannot create configurator instance lock."); const bool existing = GetLastError() == ERROR_ALREADY_EXISTS; struct Close { HANDLE handle; ~Close() { CloseHandle(handle); } } close{singleton}; if (existing) { HWND window{}; for (int i = 0; i < 100 && !window; ++i) { window = FindWindowW(L"KSwitcher.Config", nullptr); if (!window) Sleep(50); } if (!window || !configurationPeer(window, L"KSwitcher.Config.exe")) throw std::runtime_error("The configurator is starting or belongs to another installation. Try again shortly."); DWORD pid{}; GetWindowThreadProcessId(window, &pid); AllowSetForegroundWindow(pid); PostMessageW(window, WM_APP + 20, logs ? 22 : snippets ? 21 : 20, 0); return 0; } service = FindWindowW(L"KSwitcher.Main", L"KSwitcher"); if (!service) { auto path = std::filesystem::path(executable()).parent_path() / L"KSwitcher.exe"; auto command = L"\"" + path.wstring() + 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("Cannot start KSwitcher beside the configurator."); CloseHandle(process.hThread); CloseHandle(process.hProcess); for (int i = 0; i < 100 && !service; ++i) { Sleep(50); service = FindWindowW(L"KSwitcher.Main", L"KSwitcher"); } } if (!configurationPeer(service, L"KSwitcher.exe")) throw std::runtime_error("Run the configurator from the active KSwitcher installation."); INITCOMMONCONTROLSEX controls{sizeof(controls), ICC_STANDARD_CLASSES}; InitCommonControlsEx(&controls); App app(instance); WNDCLASSW cls{}; cls.hInstance = instance; cls.lpfnWndProc = controller; cls.lpszClassName = L"KSwitcher.Config"; RegisterClassW(&cls); app.window = CreateWindowExW(WS_EX_TOOLWINDOW, cls.lpszClassName, L"KSwitcher configurator", WS_POPUP, 0, 0, 0, 0, nullptr, nullptr, instance, nullptr); if (!app.window) throw std::runtime_error("Cannot create configurator window."); app.settings = decodeSettings(request(app, ConfigRead), app.layouts); setLoggingEnabled(app.settings.writeLog); if (logs) showLogs(app); else if (snippets) showSnippets(app); else showSettings(app); MSG message; while ((app.settingsWindow || app.snippetsWindow || app.logWindow) && GetMessageW(&message, nullptr, 0, 0) > 0) { if (app.settingsWindow && !IsWindowEnabled(app.settingsWindow)) { HWND popup = GetLastActivePopup(app.settingsWindow); if (popup != app.settingsWindow && IsDialogMessageW(popup, &message)) continue; } if (app.settingsWindow && IsDialogMessageW(app.settingsWindow, &message)) continue; if (app.snippetsWindow && IsDialogMessageW(app.snippetsWindow, &message)) continue; if (app.logWindow && IsDialogMessageW(app.logWindow, &message)) continue; TranslateMessage(&message); DispatchMessageW(&message); } return 0; } } int WINAPI wWinMain(HINSTANCE instance, HINSTANCE, PWSTR, int) { CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); int result = 1; try { int count{}; auto args = CommandLineToArgvW(GetCommandLineW(), &count); bool snippets = count > 1 && std::wstring(args[1]) == L"--snippets"; bool logs = count > 1 && std::wstring(args[1]) == L"--logs"; LocalFree(args); result = ks::runConfigurator(instance, snippets, logs); } catch (const std::exception &e) { MessageBoxW(nullptr, ks::wide(e.what()).c_str(), L"KSwitcher configurator", MB_OK | MB_ICONERROR); } CoUninitialize(); return result; }