Implement autoshift module
This commit is contained in:
		@@ -229,6 +229,15 @@ class AbstractHID:
 | 
			
		||||
        for idx in range(2, len(self._pd_report)):
 | 
			
		||||
            self._pd_report[idx] = 0x00
 | 
			
		||||
 | 
			
		||||
    def has_key(self, key):
 | 
			
		||||
        if isinstance(key, ModifierKey):
 | 
			
		||||
            return bool(self.report_mods[0] & key.code)
 | 
			
		||||
        else:
 | 
			
		||||
            for code in self.report_non_mods:
 | 
			
		||||
                if code == key.code:
 | 
			
		||||
                    return True
 | 
			
		||||
        return False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class USBHID(AbstractHID):
 | 
			
		||||
    REPORT_BYTES = 9
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										83
									
								
								kmk/modules/autoshift.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								kmk/modules/autoshift.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,83 @@
 | 
			
		||||
from kmk.keys import KC, Key
 | 
			
		||||
from kmk.modules import Module
 | 
			
		||||
from kmk.scheduler import cancel_task, create_task
 | 
			
		||||
from kmk.utils import Debug
 | 
			
		||||
 | 
			
		||||
debug = Debug(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Autoshift(Module):
 | 
			
		||||
    def __init__(self, tap_time=300):
 | 
			
		||||
        self.tap_time = tap_time
 | 
			
		||||
 | 
			
		||||
        self._active = False
 | 
			
		||||
        self._task = None
 | 
			
		||||
        self._key = None
 | 
			
		||||
 | 
			
		||||
    def during_bootup(self, keyboard):
 | 
			
		||||
        self._task = create_task(lambda: self._shift(keyboard), after_ms=-1)
 | 
			
		||||
 | 
			
		||||
    def before_matrix_scan(self, keyboard):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def after_matrix_scan(self, keyboard):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def process_key(self, keyboard, key, is_pressed, int_coord):
 | 
			
		||||
        # Unshift on any key event
 | 
			
		||||
        if self._active:
 | 
			
		||||
            self._unshift(keyboard)
 | 
			
		||||
            return key
 | 
			
		||||
 | 
			
		||||
        # Only shift from an unshifted state
 | 
			
		||||
        if keyboard._hid_helper.has_key(KC.LSHIFT):
 | 
			
		||||
            return key
 | 
			
		||||
 | 
			
		||||
        # Ignore rolls from tapped to hold
 | 
			
		||||
        if not is_pressed and key is not self._key:
 | 
			
		||||
            return key
 | 
			
		||||
 | 
			
		||||
        # Only shift alpha keys, iff there's no pending potential shift
 | 
			
		||||
        if (
 | 
			
		||||
            is_pressed
 | 
			
		||||
            and not self._key
 | 
			
		||||
            and isinstance(key, Key)
 | 
			
		||||
            and key.code
 | 
			
		||||
            and KC.A.code <= key.code <= KC.Z.code
 | 
			
		||||
        ):
 | 
			
		||||
            create_task(self._task, after_ms=self.tap_time)
 | 
			
		||||
            self._key = key
 | 
			
		||||
        else:
 | 
			
		||||
            cancel_task(self._task)
 | 
			
		||||
            keyboard.resume_process_key(self, self._key, True)
 | 
			
		||||
            if key is self._key:
 | 
			
		||||
                keyboard.resume_process_key(self, self._key, False)
 | 
			
		||||
            else:
 | 
			
		||||
                keyboard.resume_process_key(self, key, True)
 | 
			
		||||
            self._key = None
 | 
			
		||||
 | 
			
		||||
    def before_hid_send(self, keyboard):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def after_hid_send(self, keyboard):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def on_powersave_enable(self, keyboard):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def on_powersave_disable(self, keyboard):
 | 
			
		||||
        pass
 | 
			
		||||
 | 
			
		||||
    def _shift(self, keyboard):
 | 
			
		||||
        if debug.enabled:
 | 
			
		||||
            debug('activate')
 | 
			
		||||
        self._active = True
 | 
			
		||||
        keyboard.keys_pressed.add(KC.LSFT)
 | 
			
		||||
        keyboard.resume_process_key(self, self._key, True)
 | 
			
		||||
 | 
			
		||||
    def _unshift(self, keyboard):
 | 
			
		||||
        if debug.enabled:
 | 
			
		||||
            debug('deactivate')
 | 
			
		||||
        self._active = False
 | 
			
		||||
        self._key = None
 | 
			
		||||
        keyboard.keys_pressed.remove(KC.LSFT)
 | 
			
		||||
							
								
								
									
										91
									
								
								tests/test_autoshift.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								tests/test_autoshift.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,91 @@
 | 
			
		||||
import unittest
 | 
			
		||||
 | 
			
		||||
from kmk.keys import KC
 | 
			
		||||
from kmk.modules.autoshift import Autoshift
 | 
			
		||||
from tests.keyboard_test import KeyboardTest
 | 
			
		||||
 | 
			
		||||
tap_time = 3 * KeyboardTest.loop_delay_ms
 | 
			
		||||
t_after = 4 * KeyboardTest.loop_delay_ms
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TestAutoshift(unittest.TestCase):
 | 
			
		||||
    def setUp(self):
 | 
			
		||||
        self.kb = KeyboardTest(
 | 
			
		||||
            [Autoshift(tap_time=tap_time)],
 | 
			
		||||
            [
 | 
			
		||||
                [
 | 
			
		||||
                    KC.A,
 | 
			
		||||
                    KC.N1,
 | 
			
		||||
                    KC.HASH,
 | 
			
		||||
                    KC.NO,
 | 
			
		||||
                ]
 | 
			
		||||
            ],
 | 
			
		||||
            debug_enabled=False,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_tap_alpha(self):
 | 
			
		||||
        self.kb.test(
 | 
			
		||||
            '',
 | 
			
		||||
            [(0, True), (0, False)],
 | 
			
		||||
            [{KC.A}, {}],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_hold_alpha(self):
 | 
			
		||||
        self.kb.test(
 | 
			
		||||
            '',
 | 
			
		||||
            [(0, True), t_after, (0, False)],
 | 
			
		||||
            [{KC.A, KC.LSHIFT}, {}],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_hold_num(self):
 | 
			
		||||
        self.kb.test(
 | 
			
		||||
            '',
 | 
			
		||||
            [(1, True), t_after, (1, False)],
 | 
			
		||||
            [{KC.N1}, {}],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_hold_alpha_tap_num_within(self):
 | 
			
		||||
        self.kb.test(
 | 
			
		||||
            '',
 | 
			
		||||
            [(0, True), (1, True), t_after, (1, False), (0, False)],
 | 
			
		||||
            [{KC.A}, {KC.A, KC.N1}, {KC.A}, {}],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_hold_alpha_tap_num_after(self):
 | 
			
		||||
        self.kb.test(
 | 
			
		||||
            '',
 | 
			
		||||
            [(0, True), t_after, (1, True), (1, False), (0, False)],
 | 
			
		||||
            [{KC.A, KC.LSHIFT}, {KC.A, KC.N1}, {KC.A}, {}],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_hold_num_hold_alpha(self):
 | 
			
		||||
        self.kb.test(
 | 
			
		||||
            '',
 | 
			
		||||
            [(1, True), (0, True), t_after, (0, False), (1, False)],
 | 
			
		||||
            [{KC.N1}, {KC.N1, KC.A, KC.LSHIFT}, {KC.N1}, {}],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_roll_num_hold_alpha(self):
 | 
			
		||||
        self.kb.test(
 | 
			
		||||
            '',
 | 
			
		||||
            [(1, True), (0, True), (1, False), t_after, (0, False)],
 | 
			
		||||
            [{KC.N1}, {}, {KC.A, KC.LSHIFT}, {}],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_hold_shifted_hold_alpha(self):
 | 
			
		||||
        self.kb.test(
 | 
			
		||||
            '',
 | 
			
		||||
            [(2, True), (0, True), t_after, (2, False), (0, False)],
 | 
			
		||||
            [{KC.LSHIFT, KC.HASH}, {KC.LSHIFT, KC.HASH, KC.A}, {KC.A}, {}],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    def test_hold_internal(self):
 | 
			
		||||
        self.kb.test(
 | 
			
		||||
            '',
 | 
			
		||||
            [(3, True), t_after, (3, False)],
 | 
			
		||||
            [],
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if __name__ == '__main__':
 | 
			
		||||
    unittest.main()
 | 
			
		||||
		Reference in New Issue
	
	Block a user