Add mouse button actions and application profiles, with volume controls and per-monitor settings layout fixes.
105 lines
7.8 KiB
C++
105 lines
7.8 KiB
C++
#include "settings_merge.h"
|
|
#include "mouse_buttons.h"
|
|
#include <iostream>
|
|
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<<e.what()<<'\n';return 1;}}
|