CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-keyboard

Hook and simulate keyboard events on Windows and Linux

Pending
Overview
Eval results
Files

text-processing.mddocs/

Text Processing and Abbreviations

Advanced text input processing including word detection, automatic text replacement, typed string extraction, and abbreviation expansion. The keyboard package provides sophisticated text processing capabilities for creating text automation tools and improving typing efficiency.

Capabilities

Word Listening

Monitor typed words and trigger callbacks when specific words are detected.

def add_word_listener(word, callback, triggers=['space'], match_suffix=False, timeout=2):
    """
    Invokes a callback every time a sequence of characters is typed (e.g. 'pet')
    and followed by a trigger key (e.g. space). Modifiers are ignored.
    
    Parameters:
    - word: The typed text to be matched (case sensitive)
    - callback: Function to call when word is detected (no arguments)
    - triggers: List of keys that trigger word matching (default: ['space'])
    - match_suffix: If True, match word endings rather than whole words
    - timeout: Maximum seconds between characters before discarding current word
    
    Returns:
    Function to remove the word listener
    
    Examples:
    - add_word_listener('hello', greet_user)
    - add_word_listener('help', show_help, triggers=['space', 'enter'])
    - add_word_listener('pet', handle_pet, match_suffix=True)  # matches 'carpet'
    """

def register_word_listener(word, callback, triggers=['space'], match_suffix=False, timeout=2):
    """Alias for add_word_listener()."""

def remove_word_listener(word_or_handler):
    """
    Removes a previously registered word listener. Accepts either the word used
    during registration (exact string) or the event handler returned by
    add_word_listener().
    
    Parameters:
    - word_or_handler: Word string or handler function to remove
    """

Text Abbreviation and Replacement

Automatically replace typed abbreviations with expanded text.

def add_abbreviation(source_text, replacement_text, match_suffix=False, timeout=2):
    """
    Registers a hotkey that replaces one typed text with another. The
    replacement is done by sending backspace events to delete the source
    text, then typing the replacement.
    
    Parameters:
    - source_text: Text to be replaced when typed
    - replacement_text: Text to replace it with
    - match_suffix: If True, match endings of words instead of whole words
    - timeout: Maximum seconds between characters before discarding current word
    
    Returns:
    Function to remove the abbreviation
    
    Examples:
    - add_abbreviation('tm', '™')
    - add_abbreviation('addr', '123 Main St, City, State 12345')
    - add_abbreviation('email', 'user@example.com')
    """

def register_abbreviation(source_text, replacement_text, match_suffix=False, timeout=2):
    """Alias for add_abbreviation()."""

def remove_abbreviation(word_or_handler):
    """Alias for remove_word_listener()."""

Typed String Extraction

Extract meaningful text strings from keyboard event sequences.

def get_typed_strings(events, allow_backspace=True):
    """
    Given a sequence of events, tries to deduce what strings were typed.
    Strings are separated when a non-textual key is pressed (such as tab or
    enter). Characters are converted to uppercase according to shift and
    capslock status.
    
    Parameters:
    - events: Sequence of KeyboardEvent objects
    - allow_backspace: If True, backspaces remove the last character typed
    
    Yields:
    str: Extracted text strings
    
    Note: This function is a generator, so you can pass an infinite stream of
    events and convert them to strings in real time. This is merely a heuristic
    as it cannot access per-process keyboard state like actual keyboard layout.
    
    Example:
    get_typed_strings(record()) #-> ['This is what', 'I recorded', '']
    """

Usage Examples

Basic Word Listening

import keyboard

def greet():
    print('Hello there!')

def farewell():
    print('Goodbye!')

def help_command():
    print('Available commands: hello, bye, help')

# Register word listeners
keyboard.add_word_listener('hello', greet)
keyboard.add_word_listener('bye', farewell)
keyboard.add_word_listener('help', help_command)

print('Type "hello", "bye", or "help" followed by space.')
print('Press ESC to exit.')

keyboard.wait('esc')
keyboard.remove_word_listener('hello')
keyboard.remove_word_listener('bye') 
keyboard.remove_word_listener('help')

Text Abbreviations

import keyboard

# Common abbreviations  
keyboard.add_abbreviation('addr', '123 Main Street, Anytown, ST 12345')
keyboard.add_abbreviation('phone', '+1 (555) 123-4567')
keyboard.add_abbreviation('email', 'john.doe@example.com')
keyboard.add_abbreviation('sig', '\n\nBest regards,\nJohn Doe\nSoftware Engineer')

# Special characters
keyboard.add_abbreviation('tm', '™')
keyboard.add_abbreviation('copy', '©')
keyboard.add_abbreviation('reg', '®')

# Date/time shortcuts
import datetime
now = datetime.datetime.now()
keyboard.add_abbreviation('date', now.strftime('%Y-%m-%d'))
keyboard.add_abbreviation('time', now.strftime('%H:%M:%S'))

print('Abbreviation system active!')
print('Try typing: addr, phone, email, sig, tm, copy, reg, date, time')
print('Press ESC to exit.')

keyboard.wait('esc')

Advanced Text Processing

import keyboard
import re

class SmartTextProcessor:
    def __init__(self):
        self.abbreviations = {}
        self.word_handlers = {}
        self.setup_default_processing()
    
    def setup_default_processing(self):
        # Smart capitalization
        keyboard.add_word_listener('i', self.capitalize_i, triggers=['space', '.', '!', '?'])
        
        # Auto-correct common mistakes
        self.add_smart_abbreviation('teh', 'the')
        self.add_smart_abbreviation('adn', 'and')
        self.add_smart_abbreviation('recieve', 'receive')
        self.add_smart_abbreviation('occured', 'occurred')
        
        # Smart punctuation
        keyboard.add_word_listener('--', self.em_dash)
        keyboard.add_word_listener('...', self.ellipsis)
    
    def capitalize_i(self):
        """Auto-capitalize standalone 'i'."""
        keyboard.send('backspace')
        keyboard.write('I')
    
    def em_dash(self):
        """Replace -- with em dash."""
        keyboard.send('backspace, backspace')
        keyboard.write('—')
    
    def ellipsis(self):
        """Replace ... with proper ellipsis."""
        keyboard.send('backspace, backspace, backspace')
        keyboard.write('…')
    
    def add_smart_abbreviation(self, wrong, correct):
        """Add abbreviation with smart capitalization."""
        def replace_with_case():
            # Simple case handling - could be more sophisticated
            keyboard.send('backspace' * len(wrong))
            keyboard.write(correct)
        
        self.abbreviations[wrong] = keyboard.add_abbreviation(wrong, correct)
        # Also handle capitalized version
        wrong_cap = wrong.capitalize()
        correct_cap = correct.capitalize()
        self.abbreviations[wrong_cap] = keyboard.add_abbreviation(wrong_cap, correct_cap)
    
    def cleanup(self):
        """Remove all text processing."""
        for remove_func in self.abbreviations.values():
            remove_func()

# Usage
processor = SmartTextProcessor()
print('Smart text processing active!')
print('Try typing: i am, teh, adn, recieve, occured, --, ...')
print('Press ESC to exit.')

keyboard.wait('esc')
processor.cleanup()

Dynamic Abbreviation System

import keyboard
import json
import os

class AbbreviationManager:
    def __init__(self, config_file='abbreviations.json'):
        self.config_file = config_file
        self.active_abbreviations = {}
        self.load_abbreviations()
        self.setup_management_hotkeys()
    
    def load_abbreviations(self):
        """Load abbreviations from config file."""
        if os.path.exists(self.config_file):
            with open(self.config_file, 'r') as f:
                abbrevs = json.load(f)
                for short, full in abbrevs.items():
                    self.add_abbreviation(short, full)
            print(f'Loaded {len(abbrevs)} abbreviations')
    
    def save_abbreviations(self):
        """Save current abbreviations to config file."""
        abbrevs = {}
        # Extract abbreviation data (simplified)
        with open(self.config_file, 'w') as f:
            json.dump(abbrevs, f, indent=2)
    
    def add_abbreviation(self, short, full):
        """Add a new abbreviation."""
        if short in self.active_abbreviations:
            self.remove_abbreviation(short)
        
        remove_func = keyboard.add_abbreviation(short, full)
        self.active_abbreviations[short] = {
            'full': full,
            'remove_func': remove_func
        }
        print(f'Added abbreviation: {short} -> {full}')
    
    def remove_abbreviation(self, short):
        """Remove an abbreviation."""
        if short in self.active_abbreviations:
            self.active_abbreviations[short]['remove_func']()
            del self.active_abbreviations[short]
            print(f'Removed abbreviation: {short}')
    
    def list_abbreviations(self):
        """List all active abbreviations."""
        print('Active abbreviations:')
        for short, data in self.active_abbreviations.items():
            print(f'  {short} -> {data["full"]}')
    
    def setup_management_hotkeys(self):
        """Set up hotkeys for managing abbreviations."""
        keyboard.add_hotkey('ctrl+alt+a', self.interactive_add)
        keyboard.add_hotkey('ctrl+alt+r', self.interactive_remove)
        keyboard.add_hotkey('ctrl+alt+l', self.list_abbreviations)
    
    def interactive_add(self):
        """Interactively add abbreviation."""
        print('\n=== Add Abbreviation ===')
        short = input('Enter abbreviation: ').strip()
        if not short:
            return
        
        full = input('Enter full text: ').strip()
        if not full:
            return
        
        self.add_abbreviation(short, full)
    
    def interactive_remove(self):
        """Interactively remove abbreviation."""
        print('\n=== Remove Abbreviation ===')
        self.list_abbreviations()
        short = input('Enter abbreviation to remove: ').strip()
        if short:
            self.remove_abbreviation(short)
    
    def cleanup(self):
        """Clean up all abbreviations."""
        keyboard.unhook_all_hotkeys()
        for data in self.active_abbreviations.values():
            data['remove_func']()

# Usage
manager = AbbreviationManager()

# Add some default abbreviations
manager.add_abbreviation('brb', 'be right back')
manager.add_abbreviation('omw', 'on my way')
manager.add_abbreviation('lol', 'laugh out loud')

print('Abbreviation manager active!')
print('Ctrl+Alt+A: Add abbreviation')
print('Ctrl+Alt+R: Remove abbreviation')
print('Ctrl+Alt+L: List abbreviations')
print('Press ESC to exit.')

keyboard.wait('esc')
manager.cleanup()

Text Analysis from Recording

import keyboard
from collections import Counter
import re

def analyze_typing_patterns():
    """Analyze typing patterns from a recording."""
    
    print('Type some text for analysis. Press ESC when done.')
    events = keyboard.record()
    
    # Extract typed strings
    typed_strings = list(keyboard.get_typed_strings(events))
    full_text = ' '.join(typed_strings)
    
    print(f'\n=== Typing Analysis ===')
    print(f'Full text: "{full_text}"')
    print(f'Total strings: {len(typed_strings)}')
    print(f'Total characters: {len(full_text)}')
    
    # Word frequency analysis
    words = re.findall(r'\b\w+\b', full_text.lower())
    word_freq = Counter(words)
    
    print(f'\nWord frequency:')
    for word, count in word_freq.most_common(10):
        print(f'  {word}: {count}')
    
    # Character frequency
    char_freq = Counter(c.lower() for c in full_text if c.isalpha())
    print(f'\nCharacter frequency:')
    for char, count in char_freq.most_common(10):
        print(f'  {char}: {count}')
    
    # Suggest abbreviations for common phrases
    print(f'\nSuggested abbreviations:')
    phrases = re.findall(r'\b\w+\s+\w+\b', full_text.lower())
    phrase_freq = Counter(phrases)
    
    for phrase, count in phrase_freq.most_common(5):
        if count > 1 and len(phrase) > 10:
            abbrev = ''.join(word[0] for word in phrase.split())
            print(f'  "{phrase}" -> "{abbrev}" (used {count} times)')

# Run analysis
analyze_typing_patterns()

Context-Aware Text Replacement

import keyboard
import time

class ContextualReplacements:
    def __init__(self):
        self.contexts = {}
        self.current_context = 'default'
        self.setup_contexts()
    
    def setup_contexts(self):
        """Set up different contexts for text replacement."""
        
        # Programming context
        self.contexts['programming'] = {
            'fn': 'function',
            'ret': 'return',
            'var': 'variable',
            'cls': 'class',
            'imp': 'import',
            'def': 'definition'
        }
        
        # Email context
        self.contexts['email'] = {
            'ty': 'Thank you',
            'br': 'Best regards',
            'fyi': 'For your information',
            'asap': 'as soon as possible',
            'mtg': 'meeting',
            'fup': 'follow up'
        }
        
        # Default context
        self.contexts['default'] = {
            'btw': 'by the way',
            'imo': 'in my opinion',
            'afaik': 'as far as I know',
            'tbh': 'to be honest'
        }
        
        self.activate_context('default')
        self.setup_context_switching()
    
    def activate_context(self, context_name):
        """Activate a specific context."""
        if context_name not in self.contexts:
            print(f'Unknown context: {context_name}')
            return
        
        # Remove current abbreviations
        if hasattr(self, 'active_abbrevs'):
            for remove_func in self.active_abbrevs.values():
                remove_func()
        
        # Add new context abbreviations
        self.active_abbrevs = {}
        self.current_context = context_name
        
        for abbrev, full in self.contexts[context_name].items():
            remove_func = keyboard.add_abbreviation(abbrev, full)
            self.active_abbrevs[abbrev] = remove_func
        
        print(f'Activated {context_name} context with {len(self.contexts[context_name])} abbreviations')
    
    def setup_context_switching(self):
        """Set up hotkeys for context switching."""
        keyboard.add_hotkey('ctrl+1', lambda: self.activate_context('default'))
        keyboard.add_hotkey('ctrl+2', lambda: self.activate_context('programming'))
        keyboard.add_hotkey('ctrl+3', lambda: self.activate_context('email'))
    
    def cleanup(self):
        """Clean up all abbreviations and hotkeys."""
        if hasattr(self, 'active_abbrevs'):
            for remove_func in self.active_abbrevs.values():
                remove_func()
        keyboard.unhook_all_hotkeys()

# Usage
contextual = ContextualReplacements()

print('Contextual text replacement active!')
print('Ctrl+1: Default context (btw, imo, afaik, tbh)')
print('Ctrl+2: Programming context (fn, ret, var, cls, imp, def)')
print('Ctrl+3: Email context (ty, br, fyi, asap, mtg, fup)')
print('Press ESC to exit.')

keyboard.wait('esc')
contextual.cleanup()

Text Processing Considerations

Performance

  • Word listening adds overhead to all keyboard input
  • Large numbers of abbreviations can impact responsiveness
  • Pattern matching is performed on every keystroke

Timing and Reliability

  • Timeout values affect responsiveness vs accuracy
  • Fast typing may not trigger word detection properly
  • System load can affect timing-sensitive operations

Language and Layout Support

  • Text extraction works best with standard Latin keyboards
  • Special characters and diacritics may not be handled perfectly
  • Keyboard layout changes can affect character mapping

Case Sensitivity

  • Word matching is case-sensitive by default
  • Consider both lowercase and capitalized versions for abbreviations
  • Smart capitalization requires additional logic

Error Handling

Text processing may encounter:

  • Invalid regular expressions in pattern matching
  • Timeout issues with fast typing
  • Platform limitations on text input simulation
  • Conflicts between multiple word listeners

The package handles most errors gracefully but may miss text detection in edge cases or high-load situations.

Install with Tessl CLI

npx tessl i tessl/pypi-keyboard

docs

hotkeys.md

index.md

interactive-input.md

key-simulation.md

keyboard-events.md

recording-playback.md

text-processing.md

tile.json