Время прочтения: 6 мин.

В любой работе рано или поздно наступает момент, когда на компьютере нужно выполнять монотонные задачи, повторяя одни и те же действия из раза в раз. Кажется, что это можно было бы легко автоматизировать, ведь алгоритм прост и понятен, но на практике оказывается, что подходящий инструмент для такой автоматизации найти трудно. Но это не значит, что такой инструмент нельзя создать.

Цель: написать программу на языке Python для создания простых макросов, сначала записывающих, а после воспроизводящих действия клавиатуры и мыши. Управление программой будет происходить через графический интерфейс.

В начале потребуется библиотека для записи действий мыши и клавиатуры. В качестве таковой возьмём pynput.

Для отслеживания действий клавиатуры нужно инициализировать класс Listener. В качестве аргументов он принимает две функции: первая активируется, когда пользователь нажимает клавишу, а вторая – когда отпускает. Задействованные клавиши передаются этим функциям в аргументе. Далее нужно запустить саму прослушку клавиатуры. Всё это выглядит следующем образом:

from pynput import keyboard

# Действие, когда пользователь нажимает клавишу на клавиатуре
def on_press(key):
    print(f'Была нажата клавиша {key}')

# Действие, когда пользователь отпускает клавишу на клавиатуре
def on_release(key):
    print(f'Была отпущена клавиша {key}')
 
# Инициализация прослушки клавиатуры
keyboard_listener = keyboard.Listener(
   on_press=on_press,
   on_release=on_release
)
# Старт прослушки клавиатуры
keyboard_listener.start()

# Здесь может выполняться другой код

# Остановка прослушки клавиатуры
keyboard_listener.stop()

Для прослушки мыши нужно выполнить похожие действия, отличие лишь в том, что Listener для мыши принимает три функции: для перемещения курсора, для нажатия клавиши мыши и для пролистывания. Единица координат курсора – пиксель, отсчет системы координат ведётся от левого верхнего угла монитора.

from pynput import mouse

# Действие при движении курсора
def on_move(x, y):
    print(f'Курсор переместился на позицию '
          f'{x} по горизонтали и {y} по вертикали')

# Действие при нажатии кнопки мыши
def on_click(x, y, button, is_pressed):
    print(f'Была {"нажата" if is_pressed else "отпущена"} '
          f'клавиша {button} на позиции '
          f'{x} по горизонтали и {y} по вертикали')

# Действие при прокручивании
def on_scroll(x, y, dx, dy):
    horizontal_scroll = ''
    if dx < 0: horizontal_scroll = 'влево'
    elif dx > 0: horizontal_scroll = 'вправо'   
    vertical_scroll = ''
    if dy < 0: vertical_scroll = 'вниз'
    elif dy > 0: vertical_scroll = 'вверх'
    print(f'Была прокрутка '
          f'{horizontal_scroll} {vertical_scroll} на позиции '
          f'{x} по горизонтали и {y} по вертикали')

# Прослушка мыши
mouse_listener = mouse.Listener(
   on_move=on_move,
   on_click=on_click,
   on_scroll=on_scroll
)
# Старт прослушки мыши
mouse_listener.start()
 
# Здесь может выполняться другой код
 
# Остановка прослушки мыши
mouse_listener.stop()

Чтобы иметь возможность воспроизвести действия пользователя, прослушку клавиатуры и мыши нужно куда-то сохранить. Удобнее всего каждое действие записывать в словарь, где указывать устройство и что нажал пользователь. Все эти словари помещаем в список, который соответствует последовательности ввода.

Исходя из этого, получается следующая функция:

# Последовательность действий клавиатуры и мыши
track = []
# Добавление новых действий клавиатуры и мыши
# device - название контроллера ('keyboard', 'mouse')
# action - действие ('press', 'release', 'move', 'scroll')
# kwargs - все остальные параметры
def add_action(device, action, **kwargs):
    if action not in ['press', 'release', 'move', 'scroll']:
        raise ValueError('Wrong action name.')
    if device == 'keyboard':
       # Если был ввод с клавиатуры, то просто перечисляем параметры
       track.append({
            'device': device,
            'action': action,
            'key': kwargs['key']
        })
    elif device == 'mouse':
       # Если был ввод с мыши, то сначала указываем положение курсора
        action_dict = {
            'device': device,
            'action': action,
            'x': kwargs['x'],
            'y': kwargs['y']
        }
        # а после добавляем дополнительные параметры
        # в зависимости от действия
        if action == 'press' or action == 'release':
            action_dict['key'] = kwargs['key']
        if action == 'scroll':
            action_dict['dx'] = kwargs['dx']
            action_dict['dy'] = kwargs['dy']
        self.track.append(action_dict)

А чтобы имитировать действия клавиатуры или мыши нужно использовать класс Controller из той же библиотеки pynput. Если исходить из того способа записи прослушки, что описан чуть выше, то функция для имитации будет иметь следующий вид:

from pynput import keyboard, mouse

def run():
    keyboard_controller = keyboard.Controller()
    mouse_controller = mouse.Controller()

    for t in self.track:
        time.sleep(0.1)
        # Действия клавиатуры
        if t['device'] == 'keyboard':
            # Нажатие клавиши клавиатуры
            if t['action'] == 'press':
                keyboard_controller.press(t['key'])
            # Отпуск клавиши клавиатуры
            elif t['action'] == 'release':
                keyboard_controller.release(t['key'])

        # Действия мыши
        if t['device'] == 'mouse':
            # Движение мыши
            mouse_controller.move(
                t['x'] - mouse_controller.position[0],
                t['y'] - mouse_controller.position[1]
            )                
            # Прокручивание колёсика
            if t['action'] == 'scroll':
                mouse_controller.scroll(t['dx'], t['dy'])
            # Нажатие кнопки мыши
            elif t['action'] == 'press':
                mouse_controller.press(t['key'])
            # Отпуск кнопки мыши
            elif t['action'] == 'release':
                mouse_controller.release(t['key'])

Чтобы не переполнять текст вставками кода, полная версия с использованием библиотеки pynput для прослушки и имитации клавиатуры или мыши расположена в репозитории github, ссылка на который находится в конце поста.

Единственное, что стоит отметить – это остановка прослушки при нажатии на правый shift. Клавишу можно выбрать любую, но так как правый shift используется реже, то удобнее использовать его. В коде это выглядит так:

def on_press(key):
    if key == keyboard.Key.shift_r:
        keyboard_listener.stop()
        mouse_listener.stop()
        return
    ...

Теперь, для удобства восприятия, будет создан простой интерфейс с использованием библиотеки PySimpleGUI. В этом интерфейсе будут отображаться записанные действия пользователя, а также кнопки для старта записи или запуска имитации. Чтобы создать окно, с которым можно взаимодействовать, нужно инициализировать класс Window, передав в него двумерный список layout, представляющий собой взаимное расположение элементов:

import PySimpleGUI as psg

# Элементы интерфейса
layout = [
    # Клавиши для изменения последовательности записанных действий
    [psg.Button('Up'), psg.Button('Down')],
    # Дерево для отображения самих записанных действий
    [psg.Tree(data=psg.TreeData(), key='Keys tree', headings=[])],
    # Кнопки для управления записью
    [psg.Button('Run'), psg.Button('Record')]
]

# Создание окна
wnd = psg.Window('Holop', layout, size=(500, 500))

Перехват нажатий осуществляется в бесконечном цикле, выйти из которого можно только тогда, когда пользователь закроет окно интерфейса:

while True:
    # Ожидание события окна
    event, values = wnd.read()
    # Выбор действия в зависимости от нажатой кнопки
    if event == psg.WINDOW_CLOSED:
        # Выход
        break
    elif event == 'Up':
        # Поднять выбранные элементы
        replace_elem(wnd.Element('Keys tree'), 'up')
    elif event == 'Down':
        # Опустить выбранные элементы
        replace_elem(wnd.Element('Keys tree'), 'down')
    elif event == 'Run':
        # Запустить имитацию действий клавиатуры и мыши
        run()
    elif event == 'Record':
        # Запустить прослушку клавиатуры и мыши
        start_record()

wnd.close()

Как производится запись и имитация действий клавиатуры и мыши рассказано выше, а вот для перемещения элементов используется следующий код:

# Поднимает или опускает выбранные элементы
def replace_elem(tree, direction):
   # Список выбранных элементов
   select_elems = tree.Widget.selection()
   # Если ни один элемент не выбран, то выход
   if len(select_elems) == 0:
       return

   # Получение основных элементов дерева
   treedata = tree.TreeData
   tree_root = treedata.tree_dict['']
   # Перемещение всех выбранных элементов
   for s_e in select_elems:
       # Получение отмеченного элемента
       elem = treedata.tree_dict[tree.IdToKey[s_e]]
       ind = tree_root.children.index(elem)
       # Перемещение этого элемента
       if direction == 'up' and ind >= 0:
           tree_root.children[ind-1], tree_root.children[ind] =\
               tree_root.children[ind], tree_root.children[ind-1]
           track[ind-1], track[ind] = track[ind], track[ind-1]
       elif direction == 'down' and ind < len(tree_root.children):
           tree_root.children[ind], tree_root.children[ind+1] =\
               tree_root.children[ind+1], tree_root.children[ind]
           track[ind], track[ind+1] = track[ind+1], track[ind]
   # Обновление дерева
   tree.update(values=treedata)

После запуска кода перед нами появляется окно, с которым можно взаимодействовать:

В итоге получаем программу, которая может запоминать и воспроизводить действия клавиатуры и мыши, имеет простой интерфейс, чтобы ориентироваться в этих записях.

Здесь были разобраны основные компоненты программы, её полная версия с дополнительным функционалом размещена по ссылке ниже. Воспользоваться ей можно, например, при заполнении множества типовых форм, копируя данные из файла excel, или при установке программ на разных компьютерах через их графический интерфейс, когда скриптом это сделать нельзя.

Ссылка на репозиторий.