Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
"settings": {
"start_minimized": True,
"start_at_login": False,
"hscroll_threshold": 1,
"hscroll_threshold": 0.1,
"invert_hscroll": False, # swap horizontal scroll directions
"invert_vscroll": False, # swap vertical scroll directions
"dpi": 1000, # pointer speed / DPI setting
Expand Down Expand Up @@ -358,6 +358,16 @@ def _merge_defaults(cfg, defaults):
return cfg



def _is_compatible_type(value, default_val):
"""Return True for values that are safe despite differing exact types."""
return (
isinstance(default_val, float)
and isinstance(value, int)
and not isinstance(value, bool)
)


def _validate_types(cfg, defaults, path=""):
"""Reset values whose type doesn't match the defaults template."""
for key, default_val in defaults.items():
Expand All @@ -370,6 +380,8 @@ def _validate_types(cfg, defaults, path=""):
print(f"[Config] Type mismatch at {path}.{key}: "
f"expected dict, got {type(cfg[key]).__name__}")
cfg[key] = json.loads(json.dumps(default_val))
elif _is_compatible_type(cfg[key], default_val):
continue
elif not isinstance(cfg[key], type(default_val)):
print(f"[Config] Type mismatch at {path}.{key}: "
f"expected {type(default_val).__name__}, "
Expand Down
31 changes: 31 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,37 @@ def test_migrate_updates_media_player_profile_apps(self):
self.assertFalse(migrated["settings"]["start_at_login"])
self.assertNotIn("start_with_windows", migrated["settings"])

def test_default_hscroll_threshold_supports_fractional_mac_deltas(self):
self.assertEqual(config.DEFAULT_CONFIG["settings"]["hscroll_threshold"], 0.1)

def test_load_config_preserves_integer_hscroll_threshold(self):
partial = {
"version": 9,
"active_profile": "default",
"profiles": {
"default": {
"label": "Default",
"apps": [],
"mappings": {},
}
},
"settings": {
"hscroll_threshold": 1,
},
}

with tempfile.TemporaryDirectory() as temp_dir:
config_file = Path(temp_dir) / "config.json"
config_file.write_text(json.dumps(partial), encoding="utf-8")

with (
patch.object(config, "CONFIG_DIR", temp_dir),
patch.object(config, "CONFIG_FILE", str(config_file)),
):
loaded = config.load_config()

self.assertEqual(loaded["settings"]["hscroll_threshold"], 1)

def test_load_config_merges_missing_defaults_from_disk(self):
partial = {
"version": 3,
Expand Down
18 changes: 16 additions & 2 deletions tests/test_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,12 @@ def run_target(self):


class EngineHorizontalScrollTests(unittest.TestCase):
def _make_engine(self):
def _make_engine(self, hscroll_threshold=1):
from core.engine import Engine

cfg = copy.deepcopy(DEFAULT_CONFIG)
cfg["settings"]["hscroll_threshold"] = 1
if hscroll_threshold is not None:
cfg["settings"]["hscroll_threshold"] = hscroll_threshold

with (
patch("core.engine.MouseHook", _FakeMouseHook),
Expand Down Expand Up @@ -152,6 +153,19 @@ def test_hscroll_accumulates_fractional_mac_deltas(self):

self.assertEqual(execute_action_mock.call_count, 1)

def test_default_hscroll_threshold_handles_m720_fractional_delta(self):
engine = self._make_engine(hscroll_threshold=None)
handler = engine._make_hscroll_handler("space_right")

with patch("core.engine.execute_action") as execute_action_mock:
handler(SimpleNamespace(
event_type=MouseEvent.HSCROLL_RIGHT,
raw_data=0.100006103515625,
timestamp=3.00,
))

self.assertEqual(execute_action_mock.call_count, 1)

def test_connection_callback_receives_current_state_immediately(self):
engine = self._make_engine()
engine.hook.device_connected = True
Expand Down