138 lines
10 KiB
C++
138 lines
10 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, 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<USHORT>(-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<<e.what()<<'\n';return 1;}}
|