у меня возникает ошибка:
Traceback (most recent call last): File "C:\Users\GameR\Desktop\Макросы.py", line 663, in <module> window = KeyPresser() File "C:\Users\GameR\Desktop\Макросы.py", line 473, in __init__ self.keyboard_listener = KeyboardListener(on_press=self.on_key_press) NameError: name 'KeyboardListener' is not defined

и исправь другие ошибки которые могли бы быть в этом коде но не меняя функциональности


import sys
import threading
import ctypes
from ctypes import wintypes
from PyQt5.QtWidgets import (
    QApplication, QWidget, QLabel, QLineEdit, QPushButton,
    QVBoxLayout, QHBoxLayout, QComboBox, QMessageBox, QScrollArea,
    QGroupBox, QTabWidget
)
from PyQt5.QtCore import Qt

# Define necessary Windows API constants and structures
user32 = ctypes.WinDLL('user32', use_last_error=True)

# C struct redefinitions
PUL = ctypes.POINTER(ctypes.c_ulong)

class KEYBDINPUT(ctypes.Structure):
    _fields_ = [
        ("wVk", wintypes.WORD),      # Virtual-Key code
        ("wScan", wintypes.WORD),    # Hardware scan code
        ("dwFlags", wintypes.DWORD), # Flags
        ("time", wintypes.DWORD),    # Time stamp
        ("dwExtraInfo", PUL)         # Additional data
    ]

class MOUSEINPUT(ctypes.Structure):
    _fields_ = [
        ("dx", wintypes.LONG),        # Motion in X
        ("dy", wintypes.LONG),        # Motion in Y
        ("mouseData", wintypes.DWORD),# Mouse wheel data
        ("dwFlags", wintypes.DWORD),  # Flags
        ("time", wintypes.DWORD),     # Time stamp
        ("dwExtraInfo", PUL)          # Additional data
    ]

class INPUT(ctypes.Structure):
    class _INPUT(ctypes.Union):
        _fields_ = [
            ("ki", KEYBDINPUT),
            ("mi", MOUSEINPUT),
            # Hardware input omitted
        ]
    _anonymous_ = ("_input",)
    _fields_ = [
        ("type", wintypes.DWORD),
        ("_input", _INPUT)
    ]

LPINPUT = ctypes.POINTER(INPUT)

def send_key_press(vk_code):
    """
    Uses SendInput to simulate a key press.
    """
    ki_down = KEYBDINPUT(
        wVk=vk_code,
        wScan=0,
        dwFlags=0,  # KEYEVENTF_KEYDOWN
        time=0,
        dwExtraInfo=None
    )
    ki_up = KEYBDINPUT(
        wVk=vk_code,
        wScan=0,
        dwFlags=0x0002,  # KEYEVENTF_KEYUP
        time=0,
        dwExtraInfo=None
    )
    input_down = INPUT(type=1, ki=ki_down)
    input_up = INPUT(type=1, ki=ki_up)
    ctypes.windll.user32.SendInput(2, ctypes.byref(input_down), ctypes.sizeof(INPUT))

def send_mouse_click(button):
    """
    Uses SendInput to simulate a mouse click.
    button: 'left', 'right', 'middle'
    """
    button_flag = {
        'left': 0x0002,    # MOUSEEVENTF_LEFTDOWN
        'right': 0x0008,   # MOUSEEVENTF_RIGHTDOWN
        'middle': 0x0010   # MOUSEEVENTF_MIDDLEDOWN
    }
    button_up_flag = {
        'left': 0x0004,    # MOUSEEVENTF_LEFTUP
        'right': 0x0010,   # MOUSEEVENTF_RIGHTUP
        'middle': 0x0020   # MOUSEEVENTF_MIDDLEUP
    }
    if button not in button_flag:
        return
    mi_down = MOUSEINPUT(
        dx=0,
        dy=0,
        mouseData=0,
        dwFlags=button_flag[button],
        time=0,
        dwExtraInfo=None
    )
    mi_up = MOUSEINPUT(
        dx=0,
        dy=0,
        mouseData=0,
        dwFlags=button_up_flag[button],
        time=0,
        dwExtraInfo=None
    )
    input_down = INPUT(type=0, mi=mi_down)
    input_up = INPUT(type=0, mi=mi_up)
    ctypes.windll.user32.SendInput(2, ctypes.byref(input_down), ctypes.sizeof(INPUT))

def send_mouse_scroll(direction):
    """
    Uses SendInput to simulate mouse scroll.
    direction: 'wheel_up', 'wheel_down'
    """
    scroll_flag = {
        'wheel_up': 0x0800,    # MOUSEEVENTF_WHEEL with positive wheelData
        'wheel_down': 0x0800   # MOUSEEVENTF_WHEEL with negative wheelData
    }
    wheel_data = {
        'wheel_up': 120,        # Typical delta for one notch
        'wheel_down': -120
    }
    if direction not in scroll_flag:
        return
    mi = MOUSEINPUT(
        dx=0,
        dy=0,
        mouseData=wheel_data[direction],
        dwFlags=scroll_flag[direction],
        time=0,
        dwExtraInfo=None
    )
    input = INPUT(type=0, mi=mi)
    ctypes.windll.user32.SendInput(1, ctypes.byref(input), ctypes.sizeof(INPUT))

# Global Lists for Keys and Mouse Actions
KEY_LIST = [
    # Alphabets
    'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k',
    'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
    'w', 'x', 'y', 'z',
    # Numbers
    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
    # Function Keys
    'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12',
    # Navigation Keys
    'space', 'enter', 'tab', 'backspace', 'shift', 'ctrl',
    'alt', 'esc', 'home', 'end', 'page_up', 'page_down', 'insert', 'delete',
    'left', 'right', 'up', 'down',
    'caps_lock',
    # Numpad Keys
    'num_lock', 'numpad_0', 'numpad_1', 'numpad_2', 'numpad_3', 'numpad_4',
    'numpad_5', 'numpad_6', 'numpad_7', 'numpad_8', 'numpad_9',
    'numpad_add', 'numpad_subtract', 'numpad_multiply', 'numpad_divide',
    # Mouse Buttons
    'left_click', 'right_click', 'middle_click', 'wheel_up', 'wheel_down',
    # Mouse Wheel Click for Activation
    'middle_wheel_click'  # Represents clicking the mouse wheel (usually middle click)
]

# Mouse Actions List
MOUSE_BUTTONS = [
    'left_click', 'right_click', 'middle_click', 'wheel_up', 'wheel_down', 'middle_wheel_click'
]

# Mapping from string to Virtual-Key codes
# You can extend this mapping as needed
KEY_MAPPING = {
    'space': 0x20,
    'enter': 0x0D,
    'tab': 0x09,
    'backspace': 0x08,
    'shift': 0x10,
    'ctrl': 0x11,
    'alt': 0x12,
    'esc': 0x1B,
    'home': 0x24,
    'end': 0x23,
    'page_up': 0x21,
    'page_down': 0x22,
    'insert': 0x2D,
    'delete': 0x2E,
    'left': 0x25,
    'up': 0x26,
    'right': 0x27,
    'down': 0x28,
    'caps_lock': 0x14,
    'num_lock': 0x90,
    # Function keys
    'f1': 0x70,
    'f2': 0x71,
    'f3': 0x72,
    'f4': 0x73,
    'f5': 0x74,
    'f6': 0x75,
    'f7': 0x76,
    'f8': 0x77,
    'f9': 0x78,
    'f10': 0x79,
    'f11': 0x7A,
    'f12': 0x7B,
    # Numpad keys
    'numpad_0': 0x60,
    'numpad_1': 0x61,
    'numpad_2': 0x62,
    'numpad_3': 0x63,
    'numpad_4': 0x64,
    'numpad_5': 0x65,
    'numpad_6': 0x66,
    'numpad_7': 0x67,
    'numpad_8': 0x68,
    'numpad_9': 0x69,
    'numpad_add': 0x6B,
    'numpad_subtract': 0x6D,
    'numpad_multiply': 0x6A,
    'numpad_divide': 0x6F,
}

class KeyButtonDelayPair(QWidget):
    """
    Widget representing a single key/mouse-button and its delay
    """
    def __init__(self, parent=None):
        super().__init__(parent)
        layout = QHBoxLayout()

        self.action_label = QLabel("Select Action:")
        self.action_combo = QComboBox()
        self.action_combo.addItems(KEY_LIST)

        self.delay_label = QLabel("Delay (seconds):")
        self.delay_input = QLineEdit()
        self.delay_input.setPlaceholderText("e.g., 0.001 or 0.000001")

        self.remove_button = QPushButton("Remove")
        self.remove_button.setStyleSheet("background-color: #FF6347; color: white;")
        self.remove_button.setFixedWidth(80)
        self.remove_button.clicked.connect(self.remove_self)

        layout.addWidget(self.action_label)
        layout.addWidget(self.action_combo)
        layout.addWidget(self.delay_label)
        layout.addWidget(self.delay_input)
        layout.addWidget(self.remove_button)

        self.setLayout(layout)

    def get_action_delay(self):
        action = self.action_combo.currentText()
        delay_text = self.delay_input.text()
        try:
            delay = float(delay_text)
            if delay <= 0:
                raise ValueError("Delay must be positive.")
        except ValueError as ve:
            QMessageBox.warning(
                self, 
                "Invalid Input", 
                f"Please enter a valid positive number for delay in action '{action}'.\nError: {ve}"
            )
            return None, None
        return action, delay

    def remove_self(self):
        parent = self.parent()
        while parent and not isinstance(parent, ProfileWidget):
            parent = parent.parent()
        if isinstance(parent, ProfileWidget):
            parent.remove_action_delay_pair(self)

class ProfileWidget(QGroupBox):
    """
    Widget representing a single profile, containing its activation key and action-delay pairs
    """
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setTitle("New Profile")
        self.layout = QVBoxLayout()

        # Activation Key Selection
        activation_layout = QHBoxLayout()
        self.activation_label = QLabel("Activation Key:")
        self.activation_combo = QComboBox()
        # Add keys and mouse wheel clicks for activation
        activation_keys = [key for key in KEY_LIST if key != 'middle_wheel_click']
        activation_keys.append('middle_wheel_click')  # Ensure it's included
        self.activation_combo.addItems(activation_keys)
        activation_layout.addWidget(self.activation_label)
        activation_layout.addWidget(self.activation_combo)
        activation_layout.addStretch()

        self.layout.addLayout(activation_layout)

        # Action-Delay Pairs
        self.actions_layout = QVBoxLayout()
        self.actions_layout.setAlignment(Qt.AlignTop)

        # Scroll area for action-delay pairs
        self.scroll = QScrollArea()
        self.scroll.setWidgetResizable(True)
        self.scroll_content = QWidget()
        self.scroll_actions_layout = QVBoxLayout(self.scroll_content)
        self.scroll_actions_layout.setAlignment(Qt.AlignTop)
        self.scroll.setWidget(self.scroll_content)

        self.actions_layout.addWidget(self.scroll)

        self.layout.addLayout(self.actions_layout)

        # Add Action-Delay Pair Button
        action_button_layout = QHBoxLayout()
        self.add_action_button = QPushButton("Add Action-Delay Pair")
        self.add_action_button.setStyleSheet("background-color: #4CAF50; color: white;")
        self.add_action_button.clicked.connect(self.add_action_delay_pair)
        action_button_layout.addWidget(self.add_action_button)
        action_button_layout.addStretch()

        self.layout.addLayout(action_button_layout)

        # Remove Profile Button
        profile_control_layout = QHBoxLayout()
        self.remove_profile_button = QPushButton("Remove Profile")
        self.remove_profile_button.setStyleSheet("background-color: #FF6347; color: white;")
        self.remove_profile_button.setFixedWidth(120)
        self.remove_profile_button.clicked.connect(self.remove_profile)
        profile_control_layout.addWidget(self.remove_profile_button)
        profile_control_layout.addStretch()

        self.layout.addLayout(profile_control_layout)

        self.setLayout(self.layout)

    def add_action_delay_pair(self):
        pair = KeyButtonDelayPair()
        self.scroll_actions_layout.addWidget(pair)

    def remove_action_delay_pair(self, pair_widget):
        self.scroll_actions_layout.removeWidget(pair_widget)
        pair_widget.deleteLater()

    def get_profile_config(self):
        activation_key = self.activation_combo.currentText()
        action_delays = []
        for i in range(self.scroll_actions_layout.count()):
            widget = self.scroll_actions_layout.itemAt(i).widget()
            if isinstance(widget, KeyButtonDelayPair):
                action, delay = widget.get_action_delay()
                if action is None:
                    return None, None  # Invalid input detected
                action_delays.append((action, delay))
        if not action_delays:
            QMessageBox.warning(
                self, 
                "No Actions", 
                "Please add at least one action-delay pair in the profile."
            )
            return None, None
        return activation_key, action_delays

    def remove_profile(self):
        parent = self.parent()
        while parent and not isinstance(parent, KeyPresser):
            parent = parent.parent()
        if isinstance(parent, KeyPresser):
            parent.remove_profile(self)

class ProfileThread(threading.Thread):
    """
    Thread dedicated to executing actions for a single profile.
    """
    def __init__(self, profile, lock):
        super().__init__(daemon=True)
        self.profile = profile
        self.lock = lock
        self.active = True

    def run(self):
        activation_key, actions = self.profile.get_profile_config()
        if activation_key is None or actions is None:
            return  # Invalid configuration

        print(f"Profile '{self.profile.title()}' started.")
        while self.active and self.profile.isVisible():
            for action, delay in actions:
                if not self.active:
                    break
                start_time = time.perf_counter()
                self.perform_action(action)
                elapsed = time.perf_counter() - start_time
                sleep_time = delay - elapsed
                if sleep_time > 0:
                    time.sleep(sleep_time)
        print(f"Profile '{self.profile.title()}' stopped.")

    def perform_action(self, action):
        """
        Performs the specified action (keyboard or mouse).
        """
        try:
            if action in MOUSE_BUTTONS:
                if action == 'left_click':
                    send_mouse_click('left')
                elif action == 'right_click':
                    send_mouse_click('right')
                elif action == 'middle_click':
                    send_mouse_click('middle')
                elif action == 'wheel_up':
                    send_mouse_scroll('wheel_up')
                elif action == 'wheel_down':
                    send_mouse_scroll('wheel_down')
                elif action == 'middle_wheel_click':
                    send_mouse_click('middle')
            else:
                vk_code = KEY_MAPPING.get(action.lower())
                if vk_code:
                    send_key_press(vk_code)
                else:
                    # Handle single character keys
                    if len(action) == 1:
                        vk_code = ord(action.upper())
                        send_key_press(vk_code)
        except Exception as e:
            print(f"Error performing action '{action}' in profile '{self.profile.title()}': {e}")

    def stop(self):
        self.active = False

class KeyPresser(QWidget):
    """
    Main application window managing multiple profiles
    """
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Key & Mouse Presser with Profiles")
        self.profiles = {}  # Stores profile configurations
        self.profile_threads = {}  # Stores ProfileThread instances
        self.active_profiles = set()  # Tracks active profiles

        # Lock for thread-safe operations
        self.lock = threading.Lock()

        # Main layout
        main_layout = QVBoxLayout()

        # Tabs for profiles
        self.tabs = QTabWidget()
        main_layout.addWidget(self.tabs)

        # Add Profile Button
        add_profile_layout = QHBoxLayout()
        self.add_profile_button = QPushButton("Add Profile")
        self.add_profile_button.setStyleSheet("background-color: #4CAF50; color: white;")
        self.add_profile_button.clicked.connect(self.add_profile)
        add_profile_layout.addWidget(self.add_profile_button)
        add_profile_layout.addStretch()

        main_layout.addLayout(add_profile_layout)

        # Finish Settings Button
        finish_button_layout = QHBoxLayout()
        self.finish_button = QPushButton("Finish Setting")
        self.finish_button.setStyleSheet("background-color: #008CBA; color: white;")
        self.finish_button.clicked.connect(self.finish_setting)
        finish_button_layout.addWidget(self.finish_button)
        finish_button_layout.addStretch()

        main_layout.addLayout(finish_button_layout)

        self.setLayout(main_layout)

        # Initialize Keyboard and Mouse Listeners
        self.keyboard_listener = KeyboardListener(on_press=self.on_key_press)
        self.keyboard_listener.start()

        self.mouse_listener = MouseListener(on_click=self.on_mouse_click)
        self.mouse_listener.start()

    def add_profile(self):
        profile = ProfileWidget()
        profile_number = self.tabs.count() + 1
        profile.setTitle(f"Profile {profile_number}")
        self.tabs.addTab(profile, profile.title())
        self.profiles[profile] = {
            'activation_key': None,
            'action_delays': [],
        }

    def remove_profile(self, profile_widget):
        if profile_widget in self.profiles:
            # Stop the profile if it's running
            if profile_widget in self.active_profiles:
                self.stop_profile(profile_widget)
            # Remove from GUI
            index = self.tabs.indexOf(profile_widget)
            self.tabs.removeTab(index)
            # Remove from internal tracking
            del self.profiles[profile_widget]

    def finish_setting(self):
        """
        Validate and apply settings from all profiles
        """
        with self.lock:
            for profile in self.profiles:
                activation_key, action_delays = profile.get_profile_config()
                if activation_key is None or action_delays is None:
                    return  # Invalid input; already notified the user
                self.profiles[profile]['activation_key'] = activation_key.lower()
                self.profiles[profile]['action_delays'] = action_delays
                # Reset active state
                if profile in self.active_profiles:
                    self.stop_profile(profile)

        QMessageBox.information(
            self, 
            "Settings Updated", 
            "Profiles have been updated.\nUse assigned activation keys to toggle profiles."
        )

    def on_key_press(self, key):
        """
        Keyboard Listener callback when a key is pressed.
        Toggles the corresponding profile if the key matches an activation key.
        """
        try:
            key_str = self.key_to_string(key)
            if not key_str:
                return

            with self.lock:
                for profile in self.profiles:
                    info = self.profiles[profile]
                    if 'activation_key' in info and key_str == info['activation_key']:
                        if profile not in self.active_profiles:
                            self.start_profile(profile)
                        else:
                            self.stop_profile(profile)
        except Exception as e:
            print(f"Error in on_key_press: {e}")

    def on_mouse_click(self, x, y, button, pressed):
        """
        Mouse Listener callback when a mouse button is clicked.
        Toggles the corresponding profile if the button matches an activation key.
        """
        if not pressed:
            return  # Only handle mouse button presses, not releases

        try:
            button_str = self.mouse_button_to_string(button)
            if not button_str:
                return

            with self.lock:
                for profile in self.profiles:
                    info = self.profiles[profile]
                    if 'activation_key' in info and button_str == info['activation_key']:
                        if profile not in self.active_profiles:
                            self.start_profile(profile)
                        else:
                            self.stop_profile(profile)
        except Exception as e:
            print(f"Error in on_mouse_click: {e}")

    def key_to_string(self, key):
        """
        Converts a pynput keyboard key to its string representation.
        """
        if isinstance(key, Key):
            for k, v in KEY_MAPPING.items():
                if key == Key(v):
                    return k
            return str(key).replace('Key.', '').lower()
        elif hasattr(key, 'char'):
            return key.char.lower() if key.char else ''
        return ''

    def mouse_button_to_string(self, button):
        """
        Converts a pynput mouse button to its string representation.
        """
        # Check for standard buttons
        for name, map_val in MOUSE_BUTTONS.items() if isinstance(MOUSE_BUTTONS, dict) else [m for m in MOUSE_BUTTONS]:
            if button == Button.left and name == 'left_click':
                return 'left_click'
            elif button == Button.right and name == 'right_click':
                return 'right_click'
            elif button == Button.middle and name == 'middle_click':
                return 'middle_click'
        # Handle wheel click
        if button == Button.middle:
            return 'middle_wheel_click'
        return ''

    def start_profile(self, profile):
        """
        Starts the action thread for a given profile.
        """
        with self.lock:
            if profile in self.active_profiles:
                return  # Already running

            # Create and start the profile thread
            thread = ProfileThread(
                profile=profile,
                lock=self.lock
            )
            thread.start()
            self.profile_threads[profile] = thread
            self.active_profiles.add(profile)
            print(f"Started profile '{profile.title()}'.")

    def stop_profile(self, profile):
        """
        Stops the action thread for a given profile.
        """
        with self.lock:
            if profile not in self.active_profiles:
                return  # Already stopped

            # Stop and remove the profile thread
            thread = self.profile_threads.get(profile)
            if thread:
                thread.stop()
                thread.join(timeout=1)
                del self.profile_threads[profile]
            self.active_profiles.discard(profile)
            print(f"Stopped profile '{profile.title()}'.")

    def closeEvent(self, event):
        """
        Ensures all threads and listeners are stopped before closing the application.
        """
        with self.lock:
            # Stop all active profiles
            for profile in list(self.active_profiles):
                self.stop_profile(profile)

        self.keyboard_listener.stop()
        self.mouse_listener.stop()
        event.accept()

if __name__ == "__main__":
    app = QApplication(sys.argv)

    # Apply a modern dark palette for better aesthetics
    app.setStyle("Fusion")
    dark_palette = app.palette()
    dark_palette.setColor(app.palette().Window, Qt.black)
    dark_palette.setColor(app.palette().WindowText, Qt.white)
    dark_palette.setColor(app.palette().Base, Qt.gray)
    dark_palette.setColor(app.palette().AlternateBase, Qt.darkGray)
    dark_palette.setColor(app.palette().ToolTipBase, Qt.white)
    dark_palette.setColor(app.palette().ToolTipText, Qt.white)
    dark_palette.setColor(app.palette().Text, Qt.white)
    dark_palette.setColor(app.palette().Button, Qt.darkGray)
    dark_palette.setColor(app.palette().ButtonText, Qt.white)
    dark_palette.setColor(app.palette().Highlight, Qt.blue)
    dark_palette.setColor(app.palette().HighlightedText, Qt.black)
    app.setPalette(dark_palette)

    window = KeyPresser()
    window.resize(600, 400)
    window.show()

    sys.exit(app.exec_())