#!/usr/bin/python3 # -*- coding: utf-8 -*- # input-remapper - GUI for device specific keyboard mappings # Copyright (C) 2022 sezanzeb # # This file is part of input-remapper. # # input-remapper is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # input-remapper is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with input-remapper. If not, see . import os import unittest from unittest.mock import patch from evdev.ecodes import EV_KEY, EV_ABS from inputremapper.configs.mapping import Mapping from inputremapper.configs.mapping import UIMapping from inputremapper.configs.paths import get_preset_path, get_config_path, CONFIG_PATH from inputremapper.configs.preset import Preset from inputremapper.event_combination import EventCombination from inputremapper.input_event import InputEvent from tests.test import quick_cleanup, get_key_mapping class TestPreset(unittest.TestCase): def setUp(self): self.preset = Preset(get_preset_path("foo", "bar2")) self.assertFalse(self.preset.has_unsaved_changes()) def tearDown(self): quick_cleanup() def test_is_mapped_multiple_times(self): combination = EventCombination.from_string("1,1,1+2,2,2+3,3,3+4,4,4") permutations = combination.get_permutations() self.assertEqual(len(permutations), 6) self.preset._mappings[permutations[0]] = Mapping( event_combination=permutations[0], target_uinput="keyboard", output_symbol="a", ) self.assertFalse(self.preset._is_mapped_multiple_times(permutations[2])) self.preset._mappings[permutations[1]] = Mapping( event_combination=permutations[1], target_uinput="keyboard", output_symbol="a", ) self.assertTrue(self.preset._is_mapped_multiple_times(permutations[2])) def test_has_unsaved_changes(self): self.preset.path = get_preset_path("foo", "bar2") self.preset.add(get_key_mapping()) self.assertTrue(self.preset.has_unsaved_changes()) self.preset.save() self.assertFalse(self.preset.has_unsaved_changes()) self.preset.empty() self.assertEqual(len(self.preset), 0) # empty preset but non-empty file self.assertTrue(self.preset.has_unsaved_changes()) # load again from the disc self.preset.load() self.assertEqual( self.preset.get_mapping(EventCombination([99, 99, 99])), get_key_mapping(), ) self.assertFalse(self.preset.has_unsaved_changes()) # change the path to a non exiting file self.preset.path = get_preset_path("bar", "foo") # the preset has a mapping, the file has not self.assertTrue(self.preset.has_unsaved_changes()) # change back to the original path self.preset.path = get_preset_path("foo", "bar2") # no difference between file and memory self.assertFalse(self.preset.has_unsaved_changes()) # modify the mapping mapping = self.preset.get_mapping(EventCombination([99, 99, 99])) mapping.gain = 0.5 self.assertTrue(self.preset.has_unsaved_changes()) self.preset.load() self.preset.path = get_preset_path("bar", "foo") self.preset.remove(get_key_mapping().event_combination) # empty preset and empty file self.assertFalse(self.preset.has_unsaved_changes()) self.preset.path = get_preset_path("foo", "bar2") # empty preset, but non-empty file self.assertTrue(self.preset.has_unsaved_changes()) self.preset.load() self.assertEqual(len(self.preset), 1) self.assertFalse(self.preset.has_unsaved_changes()) # delete the preset from the system: self.preset.empty() self.preset.save() self.preset.load() self.assertFalse(self.preset.has_unsaved_changes()) self.assertEqual(len(self.preset), 0) def test_save_load(self): one = InputEvent.from_tuple((EV_KEY, 10, 1)) two = InputEvent.from_tuple((EV_KEY, 11, 1)) three = InputEvent.from_tuple((EV_KEY, 12, 1)) self.preset.add(get_key_mapping(EventCombination(one), "keyboard", "1")) self.preset.add(get_key_mapping(EventCombination(two), "keyboard", "2")) self.preset.add( get_key_mapping(EventCombination((two, three)), "keyboard", "3"), ) self.preset.path = get_preset_path("Foo Device", "test") self.preset.save() path = os.path.join(CONFIG_PATH, "presets", "Foo Device", "test.json") self.assertTrue(os.path.exists(path)) loaded = Preset(get_preset_path("Foo Device", "test")) self.assertEqual(len(loaded), 0) loaded.load() self.assertEqual(len(loaded), 3) self.assertRaises(TypeError, loaded.get_mapping, one) self.assertEqual( loaded.get_mapping(EventCombination(one)), get_key_mapping(EventCombination(one), "keyboard", "1"), ) self.assertEqual( loaded.get_mapping(EventCombination(two)), get_key_mapping(EventCombination(two), "keyboard", "2"), ) self.assertEqual( loaded.get_mapping(EventCombination((two, three))), get_key_mapping(EventCombination((two, three)), "keyboard", "3"), ) # load missing file preset = Preset(get_config_path("missing_file.json")) self.assertRaises(FileNotFoundError, preset.load) def test_modify_mapping(self): # the reader would not report values like 111 or 222, only 1 or -1. # the preset just does what it is told, so it accepts them. ev_1 = EventCombination((EV_KEY, 1, 111)) ev_2 = EventCombination((EV_KEY, 1, 222)) ev_3 = EventCombination((EV_KEY, 2, 111)) # only values between -99 and 99 are allowed as mapping for EV_ABS or EV_REL ev_4 = EventCombination((EV_ABS, 1, 99)) # add the first mapping self.preset.add(get_key_mapping(ev_1, "keyboard", "a")) self.assertTrue(self.preset.has_unsaved_changes()) self.assertEqual(len(self.preset), 1) # change ev_1 to ev_3 and change a to b mapping = self.preset.get_mapping(ev_1) mapping.event_combination = ev_3 mapping.output_symbol = "b" self.assertIsNone(self.preset.get_mapping(ev_1)) self.assertEqual( self.preset.get_mapping(ev_3), get_key_mapping(ev_3, "keyboard", "b"), ) self.assertEqual(len(self.preset), 1) # add 4 self.preset.add(get_key_mapping(ev_4, "keyboard", "c")) self.assertEqual( self.preset.get_mapping(ev_3), get_key_mapping(ev_3, "keyboard", "b"), ) self.assertEqual( self.preset.get_mapping(ev_4), get_key_mapping(ev_4, "keyboard", "c"), ) self.assertEqual(len(self.preset), 2) # change the preset of 4 to d mapping = self.preset.get_mapping(ev_4) mapping.output_symbol = "d" self.assertEqual( self.preset.get_mapping(ev_4), get_key_mapping(ev_4, "keyboard", "d"), ) self.assertEqual(len(self.preset), 2) # try to change combination of 4 to 3 mapping = self.preset.get_mapping(ev_4) with self.assertRaises(KeyError): mapping.event_combination = ev_3 self.assertEqual( self.preset.get_mapping(ev_3), get_key_mapping(ev_3, "keyboard", "b"), ) self.assertEqual( self.preset.get_mapping(ev_4), get_key_mapping(ev_4, "keyboard", "d"), ) self.assertEqual(len(self.preset), 2) def test_avoids_redundant_saves(self): with patch.object(self.preset, "has_unsaved_changes", lambda: False): self.preset.path = get_preset_path("foo", "bar2") self.preset.add(get_key_mapping()) self.preset.save() with open(get_preset_path("foo", "bar2"), "r") as f: content = f.read() self.assertFalse(content) def test_combinations(self): ev_1 = InputEvent.from_tuple((EV_KEY, 1, 111)) ev_2 = InputEvent.from_tuple((EV_KEY, 1, 222)) ev_3 = InputEvent.from_tuple((EV_KEY, 2, 111)) ev_4 = InputEvent.from_tuple((EV_ABS, 1, 99)) combi_1 = EventCombination((ev_1, ev_2, ev_3)) combi_2 = EventCombination((ev_2, ev_1, ev_3)) combi_3 = EventCombination((ev_1, ev_2, ev_4)) self.preset.add(get_key_mapping(combi_1, "keyboard", "a")) self.assertEqual( self.preset.get_mapping(combi_1), get_key_mapping(combi_1, "keyboard", "a"), ) self.assertEqual( self.preset.get_mapping(combi_2), get_key_mapping(combi_1, "keyboard", "a"), ) # since combi_1 and combi_2 are equivalent, this raises a KeyError self.assertRaises( KeyError, self.preset.add, get_key_mapping(combi_2, "keyboard", "b"), ) self.assertEqual( self.preset.get_mapping(combi_1), get_key_mapping(combi_1, "keyboard", "a"), ) self.assertEqual( self.preset.get_mapping(combi_2), get_key_mapping(combi_1, "keyboard", "a"), ) self.preset.add(get_key_mapping(combi_3, "keyboard", "c")) self.assertEqual( self.preset.get_mapping(combi_1), get_key_mapping(combi_1, "keyboard", "a"), ) self.assertEqual( self.preset.get_mapping(combi_2), get_key_mapping(combi_1, "keyboard", "a"), ) self.assertEqual( self.preset.get_mapping(combi_3), get_key_mapping(combi_3, "keyboard", "c"), ) mapping = self.preset.get_mapping(combi_1) mapping.output_symbol = "c" with self.assertRaises(KeyError): mapping.event_combination = combi_3 self.assertEqual( self.preset.get_mapping(combi_1), get_key_mapping(combi_1, "keyboard", "c"), ) self.assertEqual( self.preset.get_mapping(combi_2), get_key_mapping(combi_1, "keyboard", "c"), ) self.assertEqual( self.preset.get_mapping(combi_3), get_key_mapping(combi_3, "keyboard", "c"), ) def test_remove(self): # does nothing ev_1 = EventCombination((EV_KEY, 40, 1)) ev_2 = EventCombination((EV_KEY, 30, 1)) ev_3 = EventCombination((EV_KEY, 20, 1)) ev_4 = EventCombination((EV_KEY, 10, 1)) self.assertRaises(TypeError, self.preset.remove, (EV_KEY, 10, 1)) self.preset.remove(ev_1) self.assertFalse(self.preset.has_unsaved_changes()) self.assertEqual(len(self.preset), 0) self.preset.add(get_key_mapping(combination=ev_1)) self.assertEqual(len(self.preset), 1) self.preset.remove(ev_1) self.assertEqual(len(self.preset), 0) self.preset.add(get_key_mapping(ev_4, "keyboard", "KEY_KP1")) self.assertTrue(self.preset.has_unsaved_changes()) self.preset.add(get_key_mapping(ev_3, "keyboard", "KEY_KP2")) self.preset.add(get_key_mapping(ev_2, "keyboard", "KEY_KP3")) self.assertEqual(len(self.preset), 3) self.preset.remove(ev_3) self.assertEqual(len(self.preset), 2) self.assertEqual( self.preset.get_mapping(ev_4), get_key_mapping(ev_4, "keyboard", "KEY_KP1"), ) self.assertIsNone(self.preset.get_mapping(ev_3)) self.assertEqual( self.preset.get_mapping(ev_2), get_key_mapping(ev_2, "keyboard", "KEY_KP3"), ) def test_empty(self): self.preset.add( get_key_mapping(EventCombination([EV_KEY, 10, 1]), "keyboard", "1"), ) self.preset.add( get_key_mapping(EventCombination([EV_KEY, 11, 1]), "keyboard", "2"), ) self.preset.add( get_key_mapping(EventCombination([EV_KEY, 12, 1]), "keyboard", "3"), ) self.assertEqual(len(self.preset), 3) self.preset.path = get_config_path("test.json") self.preset.save() self.assertFalse(self.preset.has_unsaved_changes()) self.preset.empty() self.assertEqual(self.preset.path, get_config_path("test.json")) self.assertTrue(self.preset.has_unsaved_changes()) self.assertEqual(len(self.preset), 0) def test_clear(self): self.preset.add( get_key_mapping(EventCombination([EV_KEY, 10, 1]), "keyboard", "1"), ) self.preset.add( get_key_mapping(EventCombination([EV_KEY, 11, 1]), "keyboard", "2"), ) self.preset.add( get_key_mapping(EventCombination([EV_KEY, 12, 1]), "keyboard", "3"), ) self.assertEqual(len(self.preset), 3) self.preset.path = get_config_path("test.json") self.preset.save() self.assertFalse(self.preset.has_unsaved_changes()) self.preset.clear() self.assertFalse(self.preset.has_unsaved_changes()) self.assertIsNone(self.preset.path) self.assertEqual(len(self.preset), 0) def test_dangerously_mapped_btn_left(self): # btn left is mapped self.preset.add( get_key_mapping( EventCombination(InputEvent.btn_left()), "keyboard", "1", ) ) self.assertTrue(self.preset.dangerously_mapped_btn_left()) self.preset.add( get_key_mapping( EventCombination([EV_KEY, 41, 1]), "keyboard", "2", ) ) self.assertTrue(self.preset.dangerously_mapped_btn_left()) # another mapping maps to btn_left self.preset.add( get_key_mapping( EventCombination([EV_KEY, 42, 1]), "mouse", "btn_left", ) ) self.assertFalse(self.preset.dangerously_mapped_btn_left()) mapping = self.preset.get_mapping(EventCombination([EV_KEY, 42, 1])) mapping.output_symbol = "BTN_Left" self.assertFalse(self.preset.dangerously_mapped_btn_left()) mapping.target_uinput = "keyboard" mapping.output_symbol = "3" self.assertTrue(self.preset.dangerously_mapped_btn_left()) # btn_left is not mapped self.preset.remove(EventCombination(InputEvent.btn_left())) self.assertFalse(self.preset.dangerously_mapped_btn_left()) def test_save_load_with_invalid_mappings(self): ui_preset = Preset(get_config_path("test.json"), mapping_factory=UIMapping) ui_preset.add(UIMapping()) self.assertFalse(ui_preset.is_valid()) # make the mapping valid m = ui_preset.get_mapping(EventCombination.empty_combination()) m.output_symbol = "a" m.target_uinput = "keyboard" self.assertTrue(ui_preset.is_valid()) m2 = UIMapping(event_combination="1,2,1") ui_preset.add(m2) self.assertFalse(ui_preset.is_valid()) ui_preset.save() # only the valid preset is loaded preset = Preset(get_config_path("test.json")) preset.load() self.assertEqual(len(preset), 1) a = preset.get_mapping(m.event_combination).dict() b = m.dict() a.pop("mapping_type") b.pop("mapping_type") self.assertEqual(a, b) # self.assertEqual(preset.get_mapping(m.event_combination), m) # both presets load ui_preset.clear() ui_preset.path = get_config_path("test.json") ui_preset.load() self.assertEqual(len(ui_preset), 2) a = ui_preset.get_mapping(m.event_combination).dict() b = m.dict() a.pop("mapping_type") b.pop("mapping_type") self.assertEqual(a, b) # self.assertEqual(ui_preset.get_mapping(m.event_combination), m) self.assertEqual(ui_preset.get_mapping(m2.event_combination), m2) if __name__ == "__main__": unittest.main()