#61 copying presets, fixed autoload after rename

meta
sezanzeb 3 years ago
parent 5a49131343
commit 5ce1ce91e6

@ -449,6 +449,7 @@ Don't hold down any keys while the injection starts.</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Hold down ctrl and click here to copy the current preset</property>
<property name="image">new-icon</property>
<property name="always-show-image">True</property>
<signal name="clicked" handler="on_create_preset_clicked" swapped="no"/>

@ -202,7 +202,7 @@ class GlobalConfig(ConfigBase):
self.set(['autoload', device], preset)
else:
logger.info(
'Not loading injecting for "%s" automatically anmore',
'Not injecting for "%s" automatically anmore',
device
)
self.remove(['autoload', device])

@ -185,7 +185,7 @@ class Window:
# now show the proper finished content of the window
self.get('vertical-wrapper').set_opacity(1)
self.ctrl = 0
self.ctrl = False
self.unreleased_warn = 0
def unsaved_changes_dialog(self):
@ -465,11 +465,24 @@ class Window:
try:
self.save_preset()
if new_name not in ['', self.selected_preset]:
# if a new name is entered
rename_preset(
self.selected_device,
self.selected_preset,
new_name
)
# if the old preset was being autoloaded, change the
# name there as well
is_autoloaded = config.is_autoloaded(
self.selected_device,
self.selected_preset
)
if is_autoloaded:
config.set_autoload_preset(
self.selected_device,
new_name
)
# after saving the config, its modification date will be the
# newest, so populate_presets will automatically select the
# right one again.
@ -630,9 +643,18 @@ class Window:
if custom_mapping.changed and self.unsaved_changes_dialog() == GO_BACK:
return
copy = self.ctrl
try:
new_preset = get_available_preset_name(self.selected_device)
custom_mapping.empty()
new_preset = get_available_preset_name(
self.selected_device,
self.selected_preset,
copy
)
if not copy:
custom_mapping.empty()
path = get_preset_path(self.selected_device, new_preset)
custom_mapping.save(path)
self.get('preset_selection').append(new_preset, new_preset)

@ -115,10 +115,8 @@ class Mapping(ConfigBase):
if character is None:
raise ValueError('Expected `character` not to be None')
logger.debug(
'%s will map to "%s"',
new_key, character
)
character = character.strip()
logger.debug('%s will map to "%s"', new_key, character)
self.clear(new_key) # this also clears all equivalent keys
self._mapping[new_key] = character

@ -25,6 +25,7 @@
import os
import time
import glob
import re
from keymapper.paths import get_preset_path, mkdir, CONFIG_PATH
from keymapper.logger import logger
@ -53,15 +54,27 @@ def migrate_path():
migrate_path()
def get_available_preset_name(device, preset='new preset'):
def get_available_preset_name(device, preset='new preset', copy=False):
"""Increment the preset name until it is available."""
preset = preset.strip()
if copy and not re.match(r'^.+\scopy( \d+)?$', preset):
preset = f'{preset} copy'
# find a name that is not already taken
if os.path.exists(get_preset_path(device, preset)):
i = 2
# if there already is a trailing number, increment it instead of
# adding another one
match = re.match(r'^(.+) (\d+)$', preset)
if match:
preset = match[1]
i = int(match[2]) + 1
else:
i = 2
while os.path.exists(get_preset_path(device, f'{preset} {i}')):
i += 1
return f'{preset} {i}'
return preset

@ -92,6 +92,12 @@ delay of 10ms between key-down, key-up and at the end. See
Bear in mind that anti-cheat software might detect macros in games.
## UI Shortcuts
- Hold down `ctrl` and click on "new" to copy the current preset
- `shift` + `del` stops the injection (only works while the gui is in focus)
- `ctrl` + `q` closes the application
## Key Names
Check the autocompletion of the GUI for possible values. You can also

@ -200,13 +200,14 @@ class TestIntegration(unittest.TestCase):
[('device 1', 'new preset')]
)
# switch the preset, the switch should be correct and the config
# not changed.
# create a new preset, the switch should be correctly off and the
# config not changed.
self.window.on_create_preset_clicked(None)
gtk_iteration()
self.assertEqual(self.window.selected_preset, 'new preset 2')
self.assertFalse(self.window.get('preset_autoload_switch').get_active())
self.assertTrue(config.is_autoloaded('device 1', 'new preset'))
self.assertFalse(config.is_autoloaded('device 1', 'new preset 2'))
# select a preset for the second device
self.window.on_select_device(FakeDropdown('device 2'))
@ -355,6 +356,7 @@ class TestIntegration(unittest.TestCase):
self.window.window.set_focus(row.keycode_input)
gtk_iteration()
gtk_iteration()
self.assertIsNone(row.get_key())
self.assertEqual(row.keycode_input.get_label(), 'press key')
@ -650,7 +652,6 @@ class TestIntegration(unittest.TestCase):
def test_problematic_combination(self):
combination = Key((EV_KEY, KEY_LEFTSHIFT, 1), (EV_KEY, 82, 1))
self.change_empty_row(combination, 'b')
status = self.window.get('status_bar')
text = self.get_status_text()
self.assertIn('shift', text)
@ -661,10 +662,15 @@ class TestIntegration(unittest.TestCase):
self.assertTrue(warning_icon.get_visible())
def test_rename_and_save(self):
self.assertEqual(self.window.selected_device, 'device 1')
self.assertFalse(config.is_autoloaded('device 1', 'new preset'))
custom_mapping.change(Key(EV_KEY, 14, 1), 'a', None)
self.assertEqual(self.window.selected_preset, 'new preset')
self.window.on_save_preset_clicked(None)
self.assertEqual(custom_mapping.get_character(Key(EV_KEY, 14, 1)), 'a')
config.set_autoload_preset('device 1', 'new preset')
self.assertTrue(config.is_autoloaded('device 1', 'new preset'))
custom_mapping.change(Key(EV_KEY, 14, 1), 'b', None)
self.window.get('preset_name_input').set_text('asdf')
@ -672,6 +678,8 @@ class TestIntegration(unittest.TestCase):
self.assertEqual(self.window.selected_preset, 'asdf')
self.assertTrue(os.path.exists(f'{CONFIG_PATH}/presets/device 1/asdf.json'))
self.assertEqual(custom_mapping.get_character(Key(EV_KEY, 14, 1)), 'b')
# after renaming the preset it is still set to autoload
self.assertTrue(config.is_autoloaded('device 1', 'asdf'))
error_icon = self.window.get('error_status_icon')
status = self.window.get('status_bar')
@ -743,6 +751,40 @@ class TestIntegration(unittest.TestCase):
sorted(['abc 123.json', 'new preset 2.json'])
)
def test_copy_preset(self):
key_list = self.window.get('key_list')
self.change_empty_row(Key(EV_KEY, 81, 1), 'a')
time.sleep(0.1)
gtk_iteration()
self.window.on_save_preset_clicked(None)
self.assertEqual(len(key_list.get_children()), 2)
self.window.ctrl = False
self.window.on_create_preset_clicked(None)
# the preset should be empty, only one empty row present
self.assertEqual(len(key_list.get_children()), 1)
# add one new row again
self.change_empty_row(Key(EV_KEY, 81, 1), 'b')
time.sleep(0.1)
gtk_iteration()
self.window.on_save_preset_clicked(None)
self.assertEqual(len(key_list.get_children()), 2)
# this time it should be copied
self.window.ctrl = True
self.window.on_create_preset_clicked(None)
self.assertEqual(self.window.selected_preset, 'new preset 2 copy')
self.assertEqual(len(key_list.get_children()), 2)
self.assertEqual(key_list.get_children()[0].get_character(), 'b')
# make another copy
self.window.on_create_preset_clicked(None)
self.assertEqual(self.window.selected_preset, 'new preset 2 copy 2')
self.assertEqual(len(key_list.get_children()), 2)
self.assertEqual(key_list.get_children()[0].get_character(), 'b')
def test_gamepad_config(self):
# set some stuff in the beginning, otherwise gtk fails to
# do handler_unblock_by_func, which makes no sense at all.

@ -138,7 +138,7 @@ class TestMapping(unittest.TestCase):
# setting mapping.whatever does not overwrite the mapping
# after saving. It should be ignored.
self.mapping.change(Key(EV_KEY, 81, 1), 'a')
self.mapping.change(Key(EV_KEY, 81, 1), ' a ')
self.mapping.set('mapping.a', 2)
self.assertEqual(self.mapping.num_saved_keys, 0)
self.mapping.save(get_preset_path('foo', 'bar'))
@ -167,9 +167,9 @@ class TestMapping(unittest.TestCase):
ev_2 = Key(EV_KEY, 2, 0)
mapping1 = Mapping()
mapping1.change(ev_1, 'a')
mapping1.change(ev_1, ' a')
mapping2 = mapping1.clone()
mapping1.change(ev_2, 'b')
mapping1.change(ev_2, 'b ')
self.assertEqual(mapping1.get_character(ev_1), 'a')
self.assertEqual(mapping1.get_character(ev_2), 'b')

@ -42,6 +42,35 @@ def create_preset(device, name='new preset'):
PRESETS = os.path.join(CONFIG_PATH, 'presets')
class TestPresets(unittest.TestCase):
def test_get_available_preset_name(self):
# no filename conflict
self.assertEqual(get_available_preset_name('_', 'qux 2'), 'qux 2')
touch(get_preset_path('_', 'qux 5'))
self.assertEqual(get_available_preset_name('_', 'qux 5'), 'qux 6')
touch(get_preset_path('_', 'qux'))
self.assertEqual(get_available_preset_name('_', 'qux'), 'qux 2')
touch(get_preset_path('_', 'qux1'))
self.assertEqual(get_available_preset_name('_', 'qux1'), 'qux1 2')
touch(get_preset_path('_', 'qux 2 3'))
self.assertEqual(get_available_preset_name('_', 'qux 2 3'), 'qux 2 4')
touch(get_preset_path('_', 'qux 5'))
self.assertEqual(get_available_preset_name('_', 'qux 5', True), 'qux 5 copy')
touch(get_preset_path('_', 'qux 5 copy'))
self.assertEqual(get_available_preset_name('_', 'qux 5', True), 'qux 5 copy 2')
touch(get_preset_path('_', 'qux 5 copy 2'))
self.assertEqual(get_available_preset_name('_', 'qux 5', True), 'qux 5 copy 3')
touch(get_preset_path('_', 'qux 5copy'))
self.assertEqual(get_available_preset_name('_', 'qux 5copy', True), 'qux 5copy copy')
touch(get_preset_path('_', 'qux 5copy 2'))
self.assertEqual(get_available_preset_name('_', 'qux 5copy 2', True), 'qux 5copy 2 copy')
touch(get_preset_path('_', 'qux 5copy 2 copy'))
self.assertEqual(get_available_preset_name('_', 'qux 5copy 2 copy', True), 'qux 5copy 2 copy 2')
class TestMigrate(unittest.TestCase):
def test_migrate(self):
if os.path.exists(tmp):

Loading…
Cancel
Save