#include "settings_merge.h" #include "mouse_buttons.h" #include using namespace ks; int main(){try{ Settings base;base.autoCorrect=false;base.toggle=L"Ctrl+Alt+F11"; auto draft=base,current=base;draft.toggle=L"Ctrl+Alt+F12";current.autoCorrect=true; auto merged=decodeSettings(mergeConfigurationSettings(encodeSettings(base),encodeSettings(draft),encodeSettings(current)),{}); if(merged.toggle!=draft.toggle||!merged.autoCorrect)throw std::runtime_error("independent changes lost"); current.toggle=L"Ctrl+Alt+F10";bool rejected=false; try{mergeConfigurationSettings(encodeSettings(base),encodeSettings(draft),encodeSettings(current));} catch(const std::exception &e){rejected=std::string(e.what()).starts_with("Settings conflict: ");} if(!rejected)throw std::runtime_error("same-field conflict silently overwritten"); merged=decodeSettings(mergeConfigurationSettings(encodeSettings(base),encodeSettings(draft),encodeSettings(current),true),{}); if(merged.toggle!=draft.toggle||!merged.autoCorrect)throw std::runtime_error("confirmed edits discarded unrelated current changes"); merged=decodeSettings(mergeConfigurationSettings(encodeSettings(base),encodeSettings(base),encodeSettings(current)),{}); if(merged.toggle!=current.toggle||!merged.autoCorrect)throw std::runtime_error("unchanged draft reverted current settings"); draft=base;current=base;draft.chatCycles[L"telegram\nfirst"]={L"win:04090409"};current.chatCycles[L"telegram\nsecond"]={L"win:04220422"}; merged=decodeSettings(mergeConfigurationSettings(encodeSettings(base),encodeSettings(draft),encodeSettings(current)),{}); if(merged.chatCycles.size()!=2)throw std::runtime_error("independent nested changes lost"); draft = base; current = base; draft.mouseBindings[L"chrome"][L"XButton1"] = {MouseActionType::Shortcut, {MOD_CONTROL, VK_TAB}}; current.mouseBindings[L"chrome"][L"XButton2"] = {MouseActionType::Shortcut, {MOD_CONTROL | MOD_SHIFT, VK_TAB}}; merged = decodeSettings(mergeConfigurationSettings(encodeSettings(base), encodeSettings(draft), encodeSettings(current)), {}); if (merged.mouseBindings.at(L"chrome").size() != 2) throw std::runtime_error("independent mouse bindings lost"); auto shared = sharedSyncSettings(merged); if (shared == sharedSyncSettings(base) || restoreSyncSettings(shared, Settings{}, {}, false).mouseBindings != merged.mouseBindings) throw std::runtime_error("mouse profiles must restore from a synchronized backup"); auto withButtons = merged; draft = current = withButtons; draft.mouseBindings.at(L"chrome").erase(L"XButton1"); current.mouseBindings.at(L"chrome")[L"XButton2"] = {MouseActionType::Shortcut, {MOD_CONTROL, VK_F4}}; merged = decodeSettings(mergeConfigurationSettings(encodeSettings(withButtons), encodeSettings(draft), encodeSettings(current)), {}); const auto &buttons = merged.mouseBindings.at(L"chrome"); if (buttons.size() != 1 || buttons.at(L"XButton2") != MouseAction{MouseActionType::Shortcut, {MOD_CONTROL, VK_F4}}) throw std::runtime_error("mouse button removal lost during merge"); merged.mouseBindings[GlobalMouseProfile][L"Double middle"] = {MouseActionType::Mute}; merged.mouseBindings[L"chrome"][L"Double middle"] = {MouseActionType::Standard}; merged.mouseBindings[L"edge"][L"XButton1"] = {MouseActionType::Launch, {}, L"C:\\Tools\\app.exe"}; merged.mouseBindings[L"edge"][L"XButton2"] = {MouseActionType::Launch, {}, L"calc.exe"}; merged.mouseBindings[GlobalMouseProfile][L"Left"] = {MouseActionType::Shortcut, {MOD_CONTROL, 'W'}, {}, {L"chrome", L"msedge"}}; merged.mouseBindings[GlobalMouseProfile][L"Middle"] = {MouseActionType::Back, {}, {}, {L"chrome"}}; auto restored = decodeSettings(encodeSettings(merged), {}); if (restored.mouseBindings != merged.mouseBindings || effectiveMouseAction(restored, L"chrome", 7) || !effectiveMouseAction(restored, L"edge", 7) || effectiveMouseAction(restored, L"edge", 7)->type != MouseActionType::Mute) throw std::runtime_error("mouse action override or inheritance failed"); if (!effectiveMouseAction(restored, L"chrome", 0) || !effectiveMouseAction(restored, L"msedge", 0) || effectiveMouseAction(restored, L"explorer", 0) || !effectiveMouseAction(restored, L"chrome", 2) || effectiveMouseAction(restored, L"msedge", 2) || !effectiveMouseAction(restored, L"explorer", 7)) throw std::runtime_error("application restrictions or unrestricted fallback failed"); draft = current = restored; draft.mouseBindings[GlobalMouseProfile][L"Middle"].applications.clear(); current.mouseBindings[GlobalMouseProfile][L"Middle"].type = MouseActionType::Mute; const auto competing = mergeSyncProfile(encodeSettings(restored), encodeSettings(draft), encodeSettings(current)); if (competing.conflicts.size() != 1) throw std::runtime_error("competing edits to a mouse action and its scope must conflict"); Settings profiles; profiles.mouseBindings[L"name:Browsing"][L"XButton1"] = {MouseActionType::Forward}; profiles.mouseBindings[L"name:Browsing"][L"XButton2"] = {MouseActionType::Back}; profiles.mouseProfileApplications[L"name:Browsing"] = {L"chrome", L"msedge"}; profiles.mouseBindings[L"name:Empty"]; profiles.mouseBindings[GlobalMouseProfile][L"XButton1"] = {MouseActionType::VolumeUp}; profiles.mouseBindings[GlobalMouseProfile][L"XButton2"] = {MouseActionType::VolumeDown}; profiles.mouseBindings[GlobalMouseProfile][L"XButton2"].volumeStep = 7; auto resolve = [&](const wchar_t *app, int event, bool foreground = true) { const auto *action = effectiveMouseAction(profiles, app, event, foreground); return action ? action->type : MouseActionType::Standard; }; profiles = decodeSettings(mergeConfigurationSettings(encodeSettings(base), encodeSettings(profiles), encodeSettings(base)), {}); if (profiles.mouseBindings.at(GlobalMouseProfile).at(L"XButton1").volumeStep != DefaultMouseVolumeStep || profiles.mouseBindings.at(GlobalMouseProfile).at(L"XButton2").volumeStep != 7) throw std::runtime_error("default and custom volume steps must round-trip independently"); auto invalidVolume = profiles; invalidVolume.mouseBindings[GlobalMouseProfile][L"XButton1"].volumeStep = 0; rejected = false; try { validateSettings(invalidVolume); } catch (const std::runtime_error &) { rejected = true; } if (!rejected) throw std::runtime_error("invalid volume steps must be rejected"); if (!profiles.mouseBindings.contains(L"name:Empty") || resolve(L"chrome", 3) != MouseActionType::Forward || resolve(L"msedge", 4) != MouseActionType::Back || resolve(L"chrome", 3, false) != MouseActionType::VolumeUp || resolve(L"explorer", 4) != MouseActionType::VolumeDown) throw std::runtime_error("named profiles must persist and override fallback only for the matching foreground app"); profiles.mouseBindings[L"name:Alternative"][L"XButton1"] = {MouseActionType::Back}; profiles.mouseProfileApplications[L"name:Alternative"] = {L"chrome"}; profiles.disabledMouseProfiles.insert(L"name:Browsing"); profiles = decodeSettings(encodeSettings(profiles), {}); if (resolve(L"chrome", 3) != MouseActionType::Back) throw std::runtime_error("disabled profiles must not resolve actions"); profiles.disabledMouseProfiles.clear(); rejected = false; try { validateSettings(profiles); } catch (const std::runtime_error &e) { rejected = std::string(e.what()).starts_with("Mouse action conflict:"); } if (!rejected) throw std::runtime_error("overlapping active profiles must be rejected"); Settings wheel; const int down = mouseWheelEvent(WM_MOUSEWHEEL, -WHEEL_DELTA); RAWMOUSE raw{}; raw.usButtonFlags = RI_MOUSE_WHEEL; raw.usButtonData = static_cast(-WHEEL_DELTA); if (rawMouseSignal(raw) != down || mouseEventEnabled(wheel, down)) throw std::runtime_error("wheel capture must match the runtime signal and be opt-in"); raw.usButtonFlags = RI_MOUSE_HWHEEL; if (rawMouseSignal(raw) != mouseWheelEvent(WM_MOUSEHWHEEL, -WHEEL_DELTA)) throw std::runtime_error("horizontal wheel capture must match the runtime signal"); int remainder{}; if (mouseWheelSteps(remainder, -30) != 0 || mouseWheelSteps(remainder, -90) != 1 || mouseWheelSteps(remainder, 240) != 2 || remainder != 0) throw std::runtime_error("high-resolution wheel deltas must accumulate by detent"); wheel.customMouseEvents.emplace(MouseEvents[down], L"Lower volume"); wheel.mouseBindings[GlobalMouseProfile][MouseEvents[down]] = {MouseActionType::VolumeDown}; wheel.mouseBindings[L"name:Browser"][L"XButton1"] = {MouseActionType::Back}; wheel.mouseProfileApplications[L"name:Browser"] = {L"chrome"}; wheel.disabledMouseProfiles.insert(L"name:Browser"); wheel.appLanguageSets[L"Browser"] = {{L"win:00000409"}, {L"chrome"}}; const auto restoredWheel = decodeSettings(encodeSettings(wheel), {}); const auto syncedWheel = restoreSyncSettings(sharedSyncSettings(wheel), Settings{}, {}); if (mouseEventName(restoredWheel, down) != L"Lower volume" || !effectiveMouseAction(restoredWheel, L"explorer", down) || sharedSyncSettings(syncedWheel) != sharedSyncSettings(wheel)) throw std::runtime_error("custom wheel event and action must restore from a synchronized backup"); if (!restoreSyncSettings(sharedSyncSettings(Settings{}), wheel, {}).customMouseEvents.empty()) throw std::runtime_error("an explicit empty backup must clear custom mouse events"); wheel.customMouseEvents.clear(); rejected = false; try { validateSettings(wheel); } catch (const std::runtime_error &) { rejected = true; } if (!rejected) throw std::runtime_error("unregistered mouse signals cannot have bindings"); std::cout<<"Settings-save merge checks passed\n"; }catch(const std::exception &e){std::cerr<