This file provides guidelines for agentic coding agents working in the RainingKeysPython repository.
python main.pypython build.pyThis creates both release and debug builds:
RainingKeysPython.zip- Release build (no console)RainingKeysPython-debug.zip- Debug build (with console)
The build script automatically:
- Cleans previous build artifacts
- Creates release build with PyInstaller
- Creates debug build with console enabled
- Copies config.ini to dist directories
- Packages both builds into zip files
pyinstaller RainingKeysPython.specpip install -r requirements.txtThe application uses config.ini (also accepts config.cfg) for persistent configuration.
scroll_speed = 800 # Pixels per second for falling bars
bar_color = 0,255,255,200 # RGBA color values (R,G,B,A)
fall_direction = up # Direction bars fall (up/down)x = 0 # Overlay window X position
y = 0 # Overlay window Y positionenabled = True # Enable KeyViewer panel
layout = horizontal # Layout mode (horizontal/vertical)
panel_position = below # Panel position (above/below)
panel_offset_x = 0 # Horizontal offset
panel_offset_y = 0 # Vertical offset
show_counts = True # Display key press counts
height = 60 # Panel height in pixels
opacity = 0.2 # Inactive key opacity (0.0-1.0)keys = 'd','f','j','k' # Comma-separated key strings mapped to lanes- Invalid values are automatically clamped to valid ranges
- Missing sections are created with default values
- Config file encoding is detected automatically (UTF-8 preferred)
- Atomic writes prevent corruption on save errors
Current config version: 1.0 Migration system handles future format changes automatically.
RainingKeysPython is a high-performance, external rhythm game input visualizer built with:
- Python 3.10+
- PySide6 (Qt) - GUI and rendering
- pynput - Global keyboard monitoring
- pywin32 - Windows API for transparency/click-through
- Import standard library modules first
- Import third-party modules second
- Import local modules last
- Use relative imports within the core package (e.g.,
from .configuration import AppConfig) - Group imports by type with blank lines between sections
import time
from collections import deque
from PySide6.QtWidgets import QWidget
from pynput import keyboard
from .configuration import AppConfig- Use type hints for function signatures, especially for public methods
- Use dataclasses for configuration objects
- Type hint complex structures with
typing.Dict,typing.List, etc.
from dataclasses import dataclass, field
from typing import Dict, List
@dataclass
class VisualSettings:
scroll_speed: int = 800
bar_color_str: str = "0,255,255,200"- Classes: PascalCase (e.g.,
BarPool,RainingKeysOverlay) - Functions/Methods: snake_case (e.g.,
update_canvas,handle_input) - Constants: UPPER_SNAKE_CASE (e.g.,
MAX_BARS,FADE_START_Y) - Private methods: Leading underscore (e.g.,
_init_key_counts) - Instance variables: snake_case (e.g.,
active_bars,config)
- Use dataclasses for configuration objects
- Use
__slots__for performance-critical classes with many instances - Inherit from Qt classes (QWidget, QObject) appropriately
- Use Qt's Signal/Slot pattern for inter-object communication
@dataclass
class AppConfig:
MAX_BARS: int = 300
class Bar:
__slots__ = ['lane_index', 'press_time', 'release_time', 'active']- Use try/except with print statements for debugging (not logging module)
- Provide fallback values for error conditions
- Check for module availability with ImportError handling
try:
import win32gui
HAS_WIN32 = True
except ImportError:
HAS_WIN32 = False
print("Warning: pywin32 not found.")- Initialize UI in
init_ui()method - Use
super().__init__()for all custom Qt widgets - Connect signals in constructor or init methods
- Use
blockSignals(True/False)when programmatically updating UI controls - Set window flags for overlay:
FramelessWindowHint | WindowStaysOnTopHint | Tool | WindowTransparentForInput
- Use object pooling for frequently created/destroyed objects (see
BarPoolclass) - Use
dequefor active/inactive object collections - Cache geometry calculations that don't change frequently
- Use
time.perf_counter()for high-precision timing
- All config flows through
SettingsManagerandAppConfigdataclasses - Settings are persisted to
config.iniusing ConfigParser - Emit
settings_changedSignal after any config modifications - Always save config after changes using
settings.save()
- Input monitoring runs in separate QThread (
InputMonitor) - Use Qt Signals for thread communication (key_pressed, key_released)
- Worker objects use
active_keysset to filter autorepeat events
main.py- Application entry point, initializes all componentscore/configuration.py- Dataclasses for all configcore/settings_manager.py- Loads/saves config, emits change signalscore/input_mon.py- Global keyboard hook via pynput in QThreadcore/overlay.py- Main rendering window, object pooling, paint logiccore/gui.py- Configuration UI windowcore/ui/components.py- UI widget groupscore/ui/theme.py- Dark theme constants
- Dependency Injection: Pass
AppConfigorSettingsManagerto components - Signal-Slot: Use Qt signals for loose coupling between components
- Object Pooling: Reuse Bar objects to avoid GC pressure
- Configuration Separation: Data holds values, SettingsManager handles I/O
This project currently does not have formal tests configured. When adding tests:
- Consider pytest as the test framework
- Mock Qt components for unit tests
- Test configuration persistence logic
- Verify object pool behavior under load
- Test input filtering (autorepeat prevention)
core/
├── __init__.py
├── configuration.py # Dataclasses for all settings
├── settings_manager.py # Config persistence, signal emission
├── input_mon.py # Keyboard hook in QThread
├── overlay.py # Main rendering window, paint logic
├── gui.py # Settings UI window
└── ui/
├── __init__.py
├── theme.py # Dark theme constants
└── components.py # UI widget groups
- Windows Only: Uses pywin32 for click-through transparency
- Python 3.10+ required
- Requires external overlay compatibility with game window