Сделай чтобы интерфейс был ровным строчка за строчкой
import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext
import random
import re
import os
import subprocess
import shutil
import threading
import time
from datetime import datetime

# Генерация серийного номера для iMacPro1,1
def generate_serial_number():
    base = "C02"
    week_code = random.choice(["T", "U", "V", "W", "X", "Y"])
    model_code = "41"
    location_code = random.choice(["KY", "KX", "KW", "KV", "KU", "KT"])
    unique_id = ''.join(random.choices('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ', k=4))
    return f"{base}{week_code}{model_code}{location_code}{unique_id}"

# Генерация последних 3 октетов MAC-адреса
def generate_mac_suffix():
    return ':'.join(['{:02X}'.format(random.randint(0, 255)) for _ in range(3)])

# Обновление конфигурационного файла виртуальной машины
def update_config_file(file_path):
    backup_path = file_path + ".bak"
    shutil.copyfile(file_path, backup_path)

    if not os.path.isfile(file_path):
        raise FileNotFoundError(f"Файл не найден: {file_path}")

    with open(file_path, 'r', encoding='windows-1251') as file:
        config_lines = file.readlines()

    new_serial = generate_serial_number()
    new_mac_suffix = generate_mac_suffix()

    new_config = []
    for line in config_lines:
        if line.startswith("serialNumber = "):
            line = f'serialNumber = "{new_serial}"\n'
        elif line.startswith("ethernet0.Address = "):
            current_mac = re.search(r'"([^"]*)"', line).group(1)
            mac_parts = current_mac.split(':')
            if len(mac_parts) == 6:
                new_mac = ':'.join(mac_parts[:3] + new_mac_suffix.split(':'))
                line = f'ethernet0.Address = "{new_mac}"\n'
        new_config.append(line)

    with open(file_path, 'w', encoding='windows-1251') as file:
        file.writelines(new_config)

    return new_serial, new_mac_suffix

# Добавление сообщений в лог с временной меткой
def add_log(log_widget, message):
    current_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log_widget.insert(tk.END, f"[{current_time}] {message}\n")
    log_widget.see(tk.END)

# Предотвращение редактирования лога
def disable_log_edit(event):
    return "break"

# Проверка готовности гостевой ОС по состоянию VMware Tools
def wait_for_tools(vmrun_path, file_path, timeout=300):
    check_cmd = [vmrun_path, "checkToolsState", file_path]
    start_time = time.time()
    while time.time() - start_time < timeout:
        try:
            result = subprocess.run(check_cmd, capture_output=True, text=True, check=True)
            if "running" in result.stdout.lower():
                return True
        except subprocess.CalledProcessError:
            pass
        time.sleep(5)
    return False

# Функция ожидания остановки виртуальной машины
def wait_for_vm_to_stop(vmrun_path, file_path, timeout=300):
    list_cmd = [vmrun_path, "list"]
    start_time = time.time()
    while time.time() - start_time < timeout:
        try:
            result = subprocess.run(list_cmd, capture_output=True, text=True, check=True)
            running_vms = result.stdout.strip().split('\n')[1:]
            if file_path not in running_vms:
                return True
        except subprocess.CalledProcessError:
            pass
        time.sleep(5)
    return False

# Проверка состояния виртуальной машины
def is_vm_running(vmrun_path, file_path):
    list_cmd = [vmrun_path, "list"]
    try:
        result = subprocess.run(list_cmd, capture_output=True, text=True, check=True)
        running_vms = result.stdout.strip().split('\n')[1:]  # Первой строкой идет количество VMs
        return file_path in running_vms
    except subprocess.CalledProcessError:
        return False

# Остановка выполнения скрипта
def stop_script(stop_event, log_widget):
    stop_event.set()
    add_log(log_widget, f"[INFO] Запрос на остановку выполнения скрипта.")

# Функция для запуска скрипта на одной виртуальной машине
def run_script(file_path, guest_user, guest_password, command, log_widget, num_runs, pause_duration, continuous, stop_event,
              upload_host_path, guest_upload_path, guest_download_path, host_download_path, host_output_path):
    vmrun_path = r"C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe"

    if not os.path.isfile(vmrun_path):
        add_log(log_widget, f"[ERROR] vmrun.exe не найден по пути: {vmrun_path}")
        return

    if not os.path.isfile(file_path):
        add_log(log_widget, f"[ERROR] Файл VMX не найден: {file_path}")
        return

    while not stop_event.is_set():
        # Проверяем, запущена ли виртуальная машина
        if is_vm_running(vmrun_path, file_path):
            add_log(log_widget, f"[INFO] Виртуальная машина уже запущена. Остановка виртуальной машины.")
            # Остановка виртуальной машины
            try:
                stop_cmd = [vmrun_path, "-T", "ws", "stop", file_path, "soft"]
                subprocess.run(stop_cmd, capture_output=True, text=True, check=True)
                add_log(log_widget, f"[SUCCESS] Виртуальная машина остановлена.")
            except subprocess.CalledProcessError as e:
                error = e.stderr.strip() if e.stderr else "Неизвестная ошибка."
                add_log(log_widget, f"[ERROR] Не удалось остановить виртуальную машину: {error}")
                if not continuous:
                    break

            # Ожидание полной остановки виртуальной машины
            add_log(log_widget, f"[INFO] Ожидание полной остановки виртуальной машины...")
            if not wait_for_vm_to_stop(vmrun_path, file_path):
                add_log(log_widget, f"[ERROR] Виртуальная машина не остановлена. Превышено время ожидания.")
                if not continuous:
                    break
            else:
                add_log(log_widget, f"[SUCCESS] Виртуальная машина успешно остановлена.")

        # Генерация и обновление конфигурации перед запуском виртуальной машины
        try:
            serial, mac_suffix = update_config_file(file_path)
            add_log(log_widget, f"[SUCCESS] Обновлен файл конфигурации.\n  Новый серийный номер: {serial}\n  Новый MAC-адрес: {mac_suffix}")
        except Exception as e:
            add_log(log_widget, f"[ERROR] При обновлении файла конфигурации: {e}")
            if not continuous:
                break

        # Запуск виртуальной машины
        add_log(log_widget, f"[INFO] Запуск виртуальной машины.")

        try:
            start_cmd = [vmrun_path, "-T", "ws", "start", file_path, "nogui"]
            subprocess.run(start_cmd, capture_output=True, text=True, check=True)
            add_log(log_widget, f"[SUCCESS] Виртуальная машина запущена.")
        except subprocess.CalledProcessError as e:
            error = e.stderr.strip() if e.stderr else "Неизвестная ошибка."
            add_log(log_widget, f"[ERROR] Не удалось запустить виртуальную машину: {error}")
            if not continuous:
                break

        # Ожидание готовности гостевой ОС
        add_log(log_widget, f"[INFO] Ожидание готовности гостевой ОС...")
        if not wait_for_tools(vmrun_path, file_path, timeout=300):
            add_log(log_widget, f"[ERROR] Гостевая ОС не готова для выполнения команд. Превышено время ожидания.")
            if not continuous:
                break
        else:
            add_log(log_widget, f"[SUCCESS] Гостевая ОС готова для выполнения команд.")

        # Извлечение N строк из хостового файла и загрузка на виртуальную машину
        if upload_host_path and guest_upload_path:
            try:
                with open(upload_host_path, 'r', encoding='utf-8') as host_file:
                    lines = [next(host_file) for _ in range(num_runs)]
            except StopIteration:
                add_log(log_widget, f"[WARNING] В файле {upload_host_path} меньше строк, чем указано в итерациях ({num_runs}). Загрузка доступных строк.")
                with open(upload_host_path, 'r', encoding='utf-8') as host_file:
                    lines = host_file.readlines()
            except Exception as e:
                add_log(log_widget, f"[ERROR] Не удалось прочитать файл для загрузки: {e}")
                if not continuous:
                    break
            else:
                # Запись извлечённых строк в временный файл для загрузки
                temp_upload_path = r"c:\icloudreg\temp_upload.txt"
                try:
                    with open(temp_upload_path, 'w', encoding='utf-8') as temp_file:
                        temp_file.writelines(lines)
                    add_log(log_widget, f"[INFO] Извлечено {len(lines)} строк для загрузки на виртуальную машину.")
                except Exception as e:
                    add_log(log_widget, f"[ERROR] Не удалось записать временный файл для загрузки: {e}")
                    if not continuous:
                        break

                # Загрузка временного файла на виртуальную машину
                add_log(log_widget, f"[INFO] Загрузка строк на виртуальную машину.")
                copy_to_guest_cmd = [
                    vmrun_path, "-T", "ws",
                    "-gu", guest_user, "-gp", guest_password,
                    "copyFileFromHostToGuest",
                    file_path, temp_upload_path, guest_upload_path
                ]
                try:
                    subprocess.run(copy_to_guest_cmd, capture_output=True, text=True, check=True)
                    add_log(log_widget, f"[SUCCESS] Строки успешно загружены на виртуальную машину.")
                except subprocess.CalledProcessError as e:
                    error = e.stderr.strip() if e.stderr else "Неизвестная ошибка."
                    add_log(log_widget, f"[ERROR] Не удалось загрузить строки на виртуальную машину: {error}")
                    if not continuous:
                        break

                # Удаление загруженных строк из хостового файла
                try:
                    remaining_lines = []
                    with open(upload_host_path, 'r', encoding='utf-8') as host_file:
                        remaining_lines = host_file.readlines()[len(lines):]
                    with open(upload_host_path, 'w', encoding='utf-8') as host_file:
                        host_file.writelines(remaining_lines)
                    add_log(log_widget, f"[SUCCESS] Удалены загруженные строки из файла {upload_host_path}.")
                except Exception as e:
                    add_log(log_widget, f"[ERROR] Не удалось удалить загруженные строки из файла {upload_host_path}: {e}")
                    if not continuous:
                        break
        else:
            add_log(log_widget, f"[WARNING] Пути для загрузки строк не указаны. Пропуск этапа загрузки.")

        # Добавляем паузу перед итерациями
        add_log(log_widget, f"[INFO] Ждем {pause_duration} секунд перед началом итераций...")
        for _ in range(pause_duration):
            if stop_event.is_set():
                break
            time.sleep(1)
        if stop_event.is_set():
            break

        # Парсинг команды на программу и аргументы
        command_parts = command.strip().split()
        if len(command_parts) == 0:
            add_log(log_widget, f"[ERROR] Команда для выполнения пуста.")
            if not continuous:
                break

        program_path = command_parts[0]
        program_args = command_parts[1:]  # Может быть пустым списком

        # Запуск команды несколько раз с регулируемой паузой
        for i in range(num_runs):
            if stop_event.is_set():
                break

            # Логируем запуск команды
            add_log(log_widget, f"[INFO] Запуск команды {i+1}/{num_runs} в гостевой ОС.")

            cmd = [
                vmrun_path, "-T", "ws",
                "-gu", guest_user, "-gp", guest_password,
                "runProgramInGuest",
                file_path, program_path
            ] + program_args

            try:
                result = subprocess.run(cmd, capture_output=True, text=True, check=True)
                add_log(log_widget, f"[SUCCESS] Команда выполнена {i+1}/{num_runs}.\n  Вывод: {result.stdout.strip()}")
            except subprocess.CalledProcessError as e:
                if e.returncode == 1:
                    # Считаем выполнение успешным
                    add_log(log_widget, f"[SUCCESS] Команда выполнена {i+1}/{num_runs} (код выхода 1)")
                else:
                    error_output = e.stderr.strip() if e.stderr else "Нет вывода ошибки."
                    output = e.stdout.strip() if e.stdout else "Нет вывода."
                    add_log(log_widget, f"[ERROR] При выполнении команды {i+1}/{num_runs}:\nСтандартный вывод: {output}\nСтандартный вывод ошибок: {error_output}")
                    if not continuous:
                        break

            # Ожидание между запусками
            if i < num_runs - 1:
                add_log(log_widget, f"[INFO] Ожидание {pause_duration} секунд перед следующей итерацией команды...")
                for _ in range(pause_duration):
                    if stop_event.is_set():
                        break
                    time.sleep(1)
                if stop_event.is_set():
                    break

        # Выгрузка файла из гостевой системы на хостовую
        if guest_download_path and host_download_path:
            add_log(log_widget, f"[INFO] Выгрузка файла из гостевой системы на хостовую.")
            copy_to_host_cmd = [
                vmrun_path, "-T", "ws",
                "-gu", guest_user, "-gp", guest_password,
                "copyFileFromGuestToHost",
                file_path, guest_download_path, host_download_path
            ]
            try:
                subprocess.run(copy_to_host_cmd, capture_output=True, text=True, check=True)
                add_log(log_widget, f"[SUCCESS] Файл успешно выгружен с гостевой системы.")
            except subprocess.CalledProcessError as e:
                error = e.stderr.strip() if e.stderr else "Неизвестная ошибка."
                add_log(log_widget, f"[ERROR] Не удалось выгрузить файл с гостевой системы: {error}")
                if not continuous:
                    break
        else:
            add_log(log_widget, f"[WARNING] Пути для выгрузки файла не указаны. Пропуск этапа выгрузки.")

        # Обработка выгруженного файла: извлечение всех строк и добавление в файл на хостовой системе
        if host_download_path and host_output_path:
            try:
                with open(host_download_path, 'r', encoding='utf-8') as downloaded_file:
                    lines = downloaded_file.readlines()
            except Exception as e:
                add_log(log_widget, f"[ERROR] Не удалось прочитать выгруженный файл {host_download_path}: {e}")
                if not continuous:
                    break
            else:
                try:
                    with open(host_output_path, 'a', encoding='utf-8') as output_file:
                        output_file.writelines(lines)
                    add_log(log_widget, f"[SUCCESS] Добавлено {len(lines)} строк из файла {host_download_path} в {host_output_path}.")
                except Exception as e:
                    add_log(log_widget, f"[ERROR] Не удалось записать в файл {host_output_path}: {e}")
                    if not continuous:
                        break
        else:
            add_log(log_widget, f"[WARNING] Путь к файлу для записи на хостовой системе не указан. Пропуск этапа обработки выгруженного файла.")

        # Остановка виртуальной машины
        add_log(log_widget, f"[INFO] Остановка виртуальной машины.")

        try:
            stop_cmd = [vmrun_path, "-T", "ws", "stop", file_path, "soft"]
            subprocess.run(stop_cmd, capture_output=True, text=True, check=True)
            add_log(log_widget, f"[SUCCESS] Виртуальная машина остановлена.")
        except subprocess.CalledProcessError as e:
            error = e.stderr.strip() if e.stderr else "Неизвестная ошибка."
            add_log(log_widget, f"[ERROR] Не удалось остановить виртуальную машину: {error}")
            if not continuous:
                break

        # Ожидание полной остановки виртуальной машины
        add_log(log_widget, f"[INFO] Ожидание полной остановки виртуальной машины...")
        if not wait_for_vm_to_stop(vmrun_path, file_path):
            add_log(log_widget, f"[ERROR] Виртуальная машина не остановлена. Превышено время ожидания.")
            if not continuous:
                break
        else:
            add_log(log_widget, f"[SUCCESS] Виртуальная машина успешно остановлена.")

        add_log(log_widget, f"[INFO] Завершено выполнение скрипта.")

        if not continuous:
            break  # Выходим из цикла, если не требуется непрерывная работа

# Обработка нажатия кнопки "Запустить Скрипт"
def handle_run_script(file_entry, guest_user_entry, guest_pass_entry, command_entry, log_widget,
                     num_runs_entry, pause_entry, continuous_var, stop_event,
                     upload_host_entry, guest_upload_entry, guest_download_entry, host_download_entry,
                     host_output_entry):
    file_path = file_entry.get().strip()
    guest_user = guest_user_entry.get().strip()
    guest_password = guest_pass_entry.get().strip()
    command = command_entry.get().strip()
    num_runs = num_runs_entry.get().strip()
    pause_duration_str = pause_entry.get().strip()
    continuous = continuous_var.get()
    upload_host_path = upload_host_entry.get().strip()
    guest_upload_path = guest_upload_entry.get().strip()
    guest_download_path = guest_download_entry.get().strip()
    host_download_path = host_download_entry.get().strip()
    host_output_path = host_output_entry.get().strip()

    # Валидация входных данных
    if not file_path:
        messagebox.showwarning("Внимание", "Пожалуйста, выберите файл конфигурации.")
        return
    if not guest_user or not guest_password:
        messagebox.showwarning("Внимание", "Пожалуйста, введите гостевые учетные данные.")
        return
    if not command:
        messagebox.showwarning("Внимание", "Пожалуйста, введите команду для выполнения.")
        return
    if not num_runs.isdigit() or int(num_runs) < 1:
        messagebox.showwarning("Внимание", "Пожалуйста, введите корректное количество запусков (целое число больше 0).")
        return
    if not pause_duration_str.isdigit() or int(pause_duration_str) < 0:
        messagebox.showwarning("Внимание", "Пожалуйста, введите корректную паузу в секундах (целое число 0 или больше).")
        return

    num_runs = int(num_runs)
    pause_duration = int(pause_duration_str)

    # Создаем событие остановки
    stop_event.clear()

    # Запускаем функцию run_script в отдельном потоке
    thread = threading.Thread(target=run_script, args=(
        file_path, guest_user, guest_password, command, log_widget,
        num_runs, pause_duration, continuous, stop_event,
        upload_host_path, guest_upload_path, guest_download_path, host_download_path,
        host_output_path))
    thread.daemon = True
    thread.start()

    mode = "Непрерывной работы" if continuous else "однократного выполнения"
    add_log(log_widget, f"[INFO] Запуск выполнения скрипта с параметрами:\n"
                        f"- Файл конфигурации: {file_path}\n"
                        f"- Количество запусков команды: {num_runs}\n"
                        f"- Пауза между итерациями: {pause_duration} секунд\n"
                        f"- Режим: {mode}")

# Диалог выбора файла
def browse_file(file_entry):
    user_profile = os.environ.get('USERPROFILE', r'C:\Users\Default')
    default_dir = os.path.join(user_profile, 'Documents', 'Virtual Machines')
    file_path = filedialog.askopenfilename(
        title="Выберите конфигурационный файл VMX",
        initialdir=default_dir,
        filetypes=[("VMX файлы", "*.vmx"), ("Все файлы", "*.*")]
    )
    if file_path:
        file_entry.delete(0, tk.END)
        file_entry.insert(0, file_path)

# Диалог выбора файла для загрузки на гостевую систему
def browse_upload_host(upload_host_entry):
    file_path = filedialog.askopenfilename(
        title="Выберите текстовый файл для загрузки на гостевую систему",
        filetypes=[("Текстовые файлы", "*.txt"), ("Все файлы", "*.*")]
    )
    if file_path:
        upload_host_entry.delete(0, tk.END)
        upload_host_entry.insert(0, file_path)

# Диалог выбора файла на хостовой системе для сохранения выгрузки
def browse_download_host(host_download_entry):
    file_path = filedialog.asksaveasfilename(
        title="Выберите место для сохранения выгруженного файла",
        defaultextension=".txt",
        filetypes=[("Текстовые файлы", "*.txt"), ("Все файлы", "*.*")]
    )
    if file_path:
        host_download_entry.delete(0, tk.END)
        host_download_entry.insert(0, file_path)

# Диалог выбора выходного файла на хостовой системе для сохранения извлечённых строк
def browse_output_host(host_output_entry):
    file_path = filedialog.asksaveasfilename(
        title="Выберите место для сохранения выходного файла",
        defaultextension=".txt",
        filetypes=[("Текстовые файлы", "*.txt"), ("Все файлы", "*.*")]
    )
    if file_path:
        host_output_entry.delete(0, tk.END)
        host_output_entry.insert(0, file_path)

# Функции для контекстного меню (копировать, вставить, вырезать)
def create_context_menu(widget):
    context_menu = tk.Menu(widget, tearoff=0)
    context_menu.add_command(label="Копировать", command=lambda: widget.event_generate('<<Copy>>'))
    context_menu.add_command(label="Вставить", command=lambda: widget.event_generate('<<Paste>>'))
    context_menu.add_command(label="Вырезать", command=lambda: widget.event_generate('<<Cut>>'))
    widget.bind("<Button-3>", lambda event: show_context_menu(event, context_menu))

def show_context_menu(event, menu):
    try:
        menu.tk_popup(event.x_root, event.y_root)
    finally:
        menu.grab_release()

# Главная функция создания GUI
def main():
    root = tk.Tk()
    root.title("Генератор Серийного Номера и Управление Виртуальной Машиной")
    root.geometry("1300x950")
    root.resizable(True, True)

    # Переменные и виджеты
    file_entry = tk.Entry(root, width=80)
    guest_user_entry = tk.Entry(root, width=30)
    guest_pass_entry = tk.Entry(root, width=30, show="*")  # Сокрытие пароля
    command_entry = tk.Entry(root, width=80)
    num_runs_entry = tk.Entry(root, width=15)
    pause_entry = tk.Entry(root, width=15)
    continuous_var = tk.BooleanVar(value=False)
    log_widget = scrolledtext.ScrolledText(root, width=160, height=25)
    stop_event = threading.Event()
    upload_host_entry = tk.Entry(root, width=80)
    guest_upload_entry = tk.Entry(root, width=80)
    guest_download_entry = tk.Entry(root, width=80)
    host_download_entry = tk.Entry(root, width=80)
    host_output_entry = tk.Entry(root, width=80)

    # Установка предопределённых путей
    # Файл на хостовой системе для загрузки на виртуалку
    upload_host_entry.insert(0, r"c:\icloudreg\out.txt")
    # Путь на гостевой системе для записи
    guest_upload_entry.insert(0, "/Users/deno/Desktop/flow.txt")
    # Файл для записи на хостовой системе
    host_download_entry.insert(0, r"c:\icloudreg\ok.txt")
    # Файл на виртуальной системе для выгрузки
    guest_download_entry.insert(0, "/Users/deno/Desktop/activated vm1.txt")

    # Создание виджетов
    # Поля для конфигурации виртуальной машины
    tk.Label(root, text="Путь к файлу конфигурации VMX:").grid(row=0, column=0, padx=5, pady=5, sticky='e')
    file_entry.grid(row=0, column=1, padx=5, pady=5, columnspan=4, sticky='w')
    tk.Button(root, text="Обзор", command=lambda: browse_file(file_entry)).grid(row=0, column=5, padx=5, pady=5, sticky='w')
    create_context_menu(file_entry)

    tk.Label(root, text="Гость Username:").grid(row=1, column=0, padx=5, pady=5, sticky='e')
    guest_user_entry.insert(0, "deno")  # Значение по умолчанию
    guest_user_entry.grid(row=1, column=1, padx=5, pady=5, sticky='w')
    create_context_menu(guest_user_entry)

    tk.Label(root, text="Гость Password:").grid(row=1, column=2, padx=5, pady=5, sticky='e')
    guest_pass_entry.insert(0, "qwas")  # Значение по умолчанию
    guest_pass_entry.grid(row=1, column=3, padx=5, pady=5, sticky='w')
    create_context_menu(guest_pass_entry)

    tk.Label(root, text="Команда для выполнения:").grid(row=2, column=0, padx=5, pady=5, sticky='e')
    command_entry.insert(0, "/usr/bin/osascript /Users/Deno/ISMS.applescript")  # Значение по умолчанию
    command_entry.grid(row=2, column=1, padx=5, pady=5, columnspan=4, sticky='w')
    create_context_menu(command_entry)

    # Поля для ввода количества запусков и паузы
    tk.Label(root, text="Количество запусков команды:").grid(row=3, column=0, padx=5, pady=5, sticky='e')
    num_runs_entry.insert(0, "5")  # Значение по умолчанию
    num_runs_entry.grid(row=3, column=1, padx=5, pady=5, sticky='w')
    create_context_menu(num_runs_entry)

    tk.Label(root, text="Пауза между итерациями (секунды):").grid(row=3, column=2, padx=5, pady=5, sticky='e')
    pause_entry.insert(0, "60")  # Значение по умолчанию
    pause_entry.grid(row=3, column=3, padx=5, pady=5, sticky='w')
    create_context_menu(pause_entry)

    # Поля для загрузки файла на гостевую систему
    tk.Label(root, text="Файл для загрузки на гостевую систему:").grid(row=4, column=0, padx=5, pady=5, sticky='e')
    upload_host_entry.grid(row=4, column=1, padx=5, pady=5, columnspan=4, sticky='w')
    tk.Button(root, text="Обзор", command=lambda: browse_upload_host(upload_host_entry)).grid(row=4, column=5, padx=5, pady=5, sticky='w')
    create_context_menu(upload_host_entry)

    tk.Label(root, text="Путь на гостевой системе для загрузки файла:").grid(row=5, column=0, padx=5, pady=5, sticky='e')
    guest_upload_entry.grid(row=5, column=1, padx=5, pady=5, columnspan=4, sticky='w')
    create_context_menu(guest_upload_entry)

    # Поля для выгрузки файла из гостевой системы
    tk.Label(root, text="Путь к файлу на гостевой системе для выгрузки:").grid(row=6, column=0, padx=5, pady=5, sticky='e')
    guest_download_entry.grid(row=6, column=1, padx=5, pady=5, columnspan=2, sticky='w')
    create_context_menu(guest_download_entry)

    tk.Label(root, text="Путь на хостовой системе для сохранения выгруженного файла:").grid(row=6, column=3, padx=5, pady=5, sticky='e')
    host_download_entry.grid(row=6, column=4, padx=5, pady=5, columnspan=1, sticky='w')
    tk.Button(root, text="Обзор", command=lambda: browse_download_host(host_download_entry)).grid(row=6, column=5, padx=5, pady=5, sticky='w')
    create_context_menu(host_download_entry)

    # Поле для выбора выходного файла на хостовой системе для сохранения извлечённых строк
    tk.Label(root, text="Путь к выходному файлу на хостовой системе для сохранения извлечённых строк:").grid(row=7, column=0, padx=5, pady=5, sticky='e')
    host_output_entry.grid(row=7, column=1, padx=5, pady=5, columnspan=4, sticky='w')
    tk.Button(root, text="Обзор", command=lambda: browse_output_host(host_output_entry)).grid(row=7, column=5, padx=5, pady=5, sticky='w')
    create_context_menu(host_output_entry)

    # Галочка для непрерывной работы
    continuous_checkbox = tk.Checkbutton(root, text="Непрерывная работа", variable=continuous_var)
    continuous_checkbox.grid(row=8, column=0, padx=5, pady=5, sticky='w')

    # Кнопки запуска и остановки
    tk.Button(root, text="Запустить Скрипт", command=lambda: handle_run_script(
        file_entry, guest_user_entry, guest_pass_entry, command_entry, log_widget,
        num_runs_entry, pause_entry, continuous_var, stop_event,
        upload_host_entry, guest_upload_entry, guest_download_entry, host_download_entry,
        host_output_entry
    )).grid(row=8, column=1, padx=5, pady=5, sticky='w')

    tk.Button(root, text="Стоп", command=lambda: stop_script(stop_event, log_widget),
              bg="red", fg="white").grid(row=8, column=2, padx=5, pady=5, sticky='w')

    # Логи
    tk.Label(root, text="Логи:", font=('Arial', 12, 'bold')).grid(row=9, column=0, padx=5, pady=5, sticky='w')
    log_widget.grid(row=10, column=0, columnspan=6, padx=5, pady=5)
    create_context_menu(log_widget)

    # Предотвращение редактирования лога
    log_widget.bind("<Key>", disable_log_edit)
    log_widget.bind("<Insert>", disable_log_edit)  # Блокировка клавиши Insert

    root.mainloop()

if __name__ == "__main__":
    main()