#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, merged, {}, false).mouseBindings != merged.mouseBindings) throw std::runtime_error("mouse profiles must stay local during sync"); 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"); std::cout<<"Settings-save merge checks passed\n"; }catch(const std::exception &e){std::cerr<