Skip to content

Додаток для створення та управління ярликами

Цей додаток дозволяє створювати .desktop ярлики для програм, обирати системні або власні іконки, вказувати категорію, опис та запуск у терміналі, а також керувати вже створеними ярликами.

#!/usr/bin/env python3

import gi
gi.require_version("Gtk", "3.0")
gi.require_version("GLib", "2.0")
from gi.repository import Gtk, GdkPixbuf, GLib
import os
import subprocess
import shutil
import glob
import re

class DesktopEntryCreator(Gtk.Window):
    def __init__(self):
        super().__init__(title="Desktop Entry Creator & Manager")
        self.set_border_width(10)
        self.set_default_size(700, 600)

        self.exec_path = ""
        self.icon_path = ""
        self.selected_system_icon = ""
        self.icon_mode = "system"  # "system" или "custom"
        self.desktop_dir = os.path.expanduser("~/.local/share/applications")
        self.categories = self.get_system_categories()
        self.system_icons = self.get_system_icons()

        # Создаем notebook для переключения между созданием и управлением
        notebook = Gtk.Notebook()
        self.add(notebook)

        # Вкладка создания ярлыков
        create_page = self.create_create_page()
        notebook.append_page(create_page, Gtk.Label(label="Создать ярлык"))

        # Вкладка управления ярлыками
        manage_page = self.create_manage_page()
        notebook.append_page(manage_page, Gtk.Label(label="Управление ярлыками"))

    def get_system_icons(self):
        """Получаем список системных иконок из всех источников"""
        icons = []
        processed_names = set()

        # 1. Получаем иконки из всех доступных тем
        try:
            theme = Gtk.IconTheme.get_default()
            # Получаем все доступные темы
            icon_list = theme.list_icons(None)
            for icon_name in icon_list[:200]:  # Ограничиваем количество
                if icon_name not in processed_names:
                    icons.append((icon_name, icon_name))
                    processed_names.add(icon_name)
        except Exception as e:
            print(f"Ошибка получения иконок из темы: {e}")

        # 2. Ищем иконки в стандартных директориях
        icon_dirs = [
            "/usr/share/icons",
            "/usr/share/pixmaps",
            os.path.expanduser("~/.local/share/icons"),
            os.path.expanduser("~/.icons")
        ]

        # Получаем все поддиректории с иконками
        all_icon_paths = []
        for base_dir in icon_dirs:
            if os.path.exists(base_dir):
                try:
                    # Рекурсивно ищем иконки во всех поддиректориях
                    for root, dirs, files in os.walk(base_dir):
                        for filename in files[:10]:  # Ограничиваем количество файлов в каждой директории
                            if filename.endswith(('.png', '.svg', '.xpm', '.ico', '.jpg', '.jpeg')):
                                icon_path = os.path.join(root, filename)
                                icon_name = os.path.splitext(filename)[0]
                                if icon_path not in [path for _, path in all_icon_paths]:
                                    all_icon_paths.append((icon_name, icon_path))
                except:
                    continue

        # Добавляем найденные иконки
        for icon_name, icon_path in all_icon_paths[:100]:  # Ограничиваем общее количество
            if icon_name not in processed_names:
                icons.append((icon_name, icon_path))
                processed_names.add(icon_name)

        # 3. Получаем иконки из .desktop файлов
        desktop_dirs = [
            "/usr/share/applications",
            "/usr/local/share/applications",
            os.path.expanduser("~/.local/share/applications")
        ]

        for desktop_dir in desktop_dirs:
            if os.path.exists(desktop_dir):
                try:
                    for filename in os.listdir(desktop_dir)[:50]:  # Ограничиваем количество
                        if filename.endswith('.desktop'):
                            filepath = os.path.join(desktop_dir, filename)
                            try:
                                with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
                                    content = f.read()

                                    # Ищем Name= в файле
                                    name_match = re.search(r'Name=([^\n]+)', content)
                                    icon_match = re.search(r'Icon=([^\n]+)', content)

                                    if name_match and icon_match:
                                        app_name = name_match.group(1).strip()
                                        icon_value = icon_match.group(1).strip()

                                        # Используем имя приложения как ключ иконки
                                        if app_name not in processed_names:
                                            icons.append((app_name, icon_value))
                                            processed_names.add(app_name)
                            except:
                                continue
                except:
                    continue

        return icons[:300]  # Ограничиваем общее количество

    def get_system_categories(self):
        """Получаем список доступных категорий из системных .desktop файлов"""
        categories = set()
        system_dirs = [
            "/usr/share/applications",
            "/usr/local/share/applications"
        ]

        # Стандартные категории
        standard_categories = [
            "AudioVideo", "Audio", "Video", "Development", "Education",
            "Game", "Graphics", "Network", "Office", "Science",
            "Settings", "System", "Utility"
        ]

        for category in standard_categories:
            categories.add(category)

        # Ищем категории в системных файлах
        for system_dir in system_dirs:
            if os.path.exists(system_dir):
                try:
                    for filename in os.listdir(system_dir):
                        if filename.endswith('.desktop'):
                            filepath = os.path.join(system_dir, filename)
                            try:
                                with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
                                    content = f.read()
                                    # Ищем Categories= в файле
                                    match = re.search(r'Categories=([^;\n]+)', content)
                                    if match:
                                        cats = match.group(1).split(';')
                                        for cat in cats:
                                            if cat.strip():
                                                categories.add(cat.strip())
                            except:
                                continue
                except:
                    continue

        return sorted(list(categories))

    def create_create_page(self):
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=15)

        # Executable
        self.exec_button = Gtk.Button(label="Выбрать исполняемый файл")
        self.exec_button.connect("clicked", self.on_exec_clicked)
        vbox.pack_start(self.exec_button, False, False, 20)

        self.exec_label = Gtk.Label(label="Файл не выбран")
        self.exec_label.set_line_wrap(True)
        self.exec_label.set_justify(Gtk.Justification.LEFT)
        vbox.pack_start(self.exec_label, False, False, 0)

        # Выбор типа иконки
        icon_type_frame = Gtk.Frame(label="Выбор иконки")
        icon_type_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        icon_type_frame.add(icon_type_box)

        # Radio buttons для выбора типа иконки
        self.system_icon_radio = Gtk.RadioButton.new_with_label_from_widget(None, "Выбрать системную иконку")
        self.custom_icon_radio = Gtk.RadioButton.new_with_label_from_widget(self.system_icon_radio, "Загрузить свою иконку")
        self.system_icon_radio.set_active(True)

        self.system_icon_radio.connect("toggled", self.on_icon_type_changed)
        self.custom_icon_radio.connect("toggled", self.on_icon_type_changed)

        icon_type_box.pack_start(self.system_icon_radio, False, False, 0)
        icon_type_box.pack_start(self.custom_icon_radio, False, False, 0)

        # Кнопка для выбора системных иконок
        self.system_icon_button = Gtk.Button(label="Выбрать системную иконку")
        self.system_icon_button.connect("clicked", self.on_system_icon_clicked)
        icon_type_box.pack_start(self.system_icon_button, False, False, 0)

        # Кнопка для загрузки своей иконки
        self.custom_icon_button = Gtk.Button(label="Выбрать иконку из файла")
        self.custom_icon_button.connect("clicked", self.on_custom_icon_clicked)
        self.custom_icon_button.set_sensitive(False)
        icon_type_box.pack_start(self.custom_icon_button, False, False, 0)

        vbox.pack_start(icon_type_frame, False, False, 0)

        # Отображение выбранной иконки
        self.icon_image = Gtk.Image()
        self.icon_image.set_size_request(64, 64)
        vbox.pack_start(self.icon_image, False, False, 5)

        # Label для отображения имени выбранной иконки
        self.icon_name_label = Gtk.Label(label="Иконка не выбрана")
        vbox.pack_start(self.icon_name_label, False, False, 0)

        # Name
        self.name_entry = Gtk.Entry()
        self.name_entry.set_placeholder_text("Название приложения")
        self.name_entry.connect("changed", self.on_name_changed)
        vbox.pack_start(self.name_entry, False, False, 0)

        # Comment
        self.comment_entry = Gtk.Entry()
        self.comment_entry.set_placeholder_text("Описание приложения (опционально)")
        vbox.pack_start(self.comment_entry, False, False, 0)

        # Category
        category_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
        category_label = Gtk.Label(label="Категория:")
        category_box.pack_start(category_label, False, False, 0)

        self.category_combo = Gtk.ComboBoxText()
        self.category_combo.append_text("Не выбрано")
        for category in self.categories:
            self.category_combo.append_text(category)
        self.category_combo.set_active(0)
        category_box.pack_start(self.category_combo, True, True, 0)

        vbox.pack_start(category_box, False, False, 0)

        # Terminal checkbox
        self.terminal_check = Gtk.CheckButton(label="Запускать в терминале")
        vbox.pack_start(self.terminal_check, False, False, 0)

        # Create Button
        self.create_button = Gtk.Button(label="Создать ярлык")
        self.create_button.connect("clicked", self.on_create_clicked)
        vbox.pack_start(self.create_button, False, False, 10)

        return vbox

    def on_icon_type_changed(self, widget):
        """Обработчик изменения типа иконки"""
        if self.system_icon_radio.get_active():
            self.icon_mode = "system"
            self.custom_icon_button.set_sensitive(False)
            self.system_icon_button.set_sensitive(True)
        else:
            self.icon_mode = "custom"
            self.custom_icon_button.set_sensitive(True)
            self.system_icon_button.set_sensitive(False)

        self.update_icon_preview()

    def update_icon_preview(self):
        """Обновление превью иконки"""
        try:
            if hasattr(self, 'selected_icon_info') and self.selected_icon_info:
                icon_name, icon_value = self.selected_icon_info

                # Пытаемся загрузить иконку несколькими способами
                pixbuf = self.load_icon_by_value(icon_value)

                if pixbuf:
                    self.icon_image.set_from_pixbuf(pixbuf)
                    self.icon_name_label.set_text(f"Выбрана иконка: {icon_name}")
                else:
                    self.icon_image.set_from_icon_name("image-missing", Gtk.IconSize.DIALOG)
                    self.icon_name_label.set_text("Иконка не найдена")
            else:
                self.icon_image.set_from_icon_name("image-missing", Gtk.IconSize.DIALOG)
                self.icon_name_label.set_text("Иконка не выбрана")
        except Exception as e:
            print(f"Ошибка обновления превью иконки: {e}")
            self.icon_image.set_from_icon_name("image-missing", Gtk.IconSize.DIALOG)
            self.icon_name_label.set_text("Иконка не выбрана")

    def load_icon_by_value(self, icon_value):
        """Загрузка иконки по значению (имя или путь)"""
        try:
            # Если это путь к файлу
            if os.path.exists(icon_value):
                return GdkPixbuf.Pixbuf.new_from_file_at_scale(
                    icon_value, 64, 64, True
                )

            # Если это имя иконки, пытаемся загрузить из темы
            theme = Gtk.IconTheme.get_default()
            try:
                return theme.load_icon(icon_value, 64, 0)
            except:
                # Пробуем найти иконку в стандартных директориях
                icon_dirs = [
                    "/usr/share/icons",
                    "/usr/share/pixmaps",
                    os.path.expanduser("~/.local/share/icons"),
                    os.path.expanduser("~/.icons")
                ]

                for base_dir in icon_dirs:
                    if os.path.exists(base_dir):
                        for root, dirs, files in os.walk(base_dir):
                            for filename in files:
                                name_without_ext = os.path.splitext(filename)[0]
                                if name_without_ext == icon_value:
                                    icon_path = os.path.join(root, filename)
                                    return GdkPixbuf.Pixbuf.new_from_file_at_scale(
                                        icon_path, 64, 64, True
                                    )
        except:
            pass

        return None

    def on_system_icon_clicked(self, widget):
        """Открытие диалога выбора системной иконки"""
        dialog = Gtk.Dialog(title="Выбор системной иконки", parent=self, flags=0)
        dialog.set_default_size(700, 500)

        # Главный контейнер
        content_area = dialog.get_content_area()
        content_area.set_spacing(10)
        content_area.set_border_width(10)

        # Поле поиска
        search_entry = Gtk.Entry()
        search_entry.set_placeholder_text("Поиск иконок...")
        content_area.pack_start(search_entry, False, False, 0)

        # Создаем сетку для иконок
        scrolled = Gtk.ScrolledWindow()
        scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)

        # Создаем сетку иконок
        icon_grid = Gtk.FlowBox()
        icon_grid.set_valign(Gtk.Align.START)
        icon_grid.set_max_children_per_line(10)
        icon_grid.set_selection_mode(Gtk.SelectionMode.SINGLE)
        icon_grid.set_homogeneous(True)
        icon_grid.set_min_children_per_line(5)
        icon_grid.set_row_spacing(8)
        icon_grid.set_column_spacing(8)

        # Храним все иконки для поиска (хранить сами child элементы)
        self.all_icon_children = []  # (child, icon_name_lower)

        # Добавляем иконки в сетку
        for i, (icon_name, icon_value) in enumerate(self.system_icons):
            icon_box = self.create_icon_widget(icon_name, icon_value)
            if icon_box:
                child = Gtk.FlowBoxChild()
                child.add(icon_box)
                icon_grid.add(child)
                self.all_icon_children.append((child, icon_name.lower()))
                # Сохраняем информацию об иконке в child
                child.icon_info = (icon_name, icon_value)

        scrolled.add(icon_grid)
        content_area.pack_start(scrolled, True, True, 0)

        # Кнопки
        dialog.add_button(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL)
        dialog.add_button(Gtk.STOCK_OK, Gtk.ResponseType.OK)

        dialog.show_all()

        # Таймер для debounce поиска
        self.search_timer = None

        # Обработчик поиска
        def on_search_changed(entry):
            # Отменяем предыдущий таймер если есть
            if self.search_timer:
                GLib.source_remove(self.search_timer)

            # Устанавливаем новый таймер (300ms debounce)
            self.search_timer = GLib.timeout_add(300, self.perform_search, 
                                               entry.get_text().lower(), icon_grid)

        search_entry.connect("changed", on_search_changed)

        # Обработчик выбора иконки
        def on_icon_selected(flowbox, child):
            if hasattr(child, 'icon_info'):
                self.selected_icon_info = child.icon_info
                self.update_icon_preview()

        icon_grid.connect("child-activated", on_icon_selected)

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            selected_children = icon_grid.get_selected_children()
            if selected_children:
                child = selected_children[0]
                if hasattr(child, 'icon_info'):
                    self.selected_icon_info = child.icon_info
                    self.update_icon_preview()

        # Очищаем таймер
        if self.search_timer:
            GLib.source_remove(self.search_timer)
            self.search_timer = None

        dialog.destroy()

    def perform_search(self, search_text, icon_grid):
        """Выполнение поиска иконок"""
        try:
            # Очищаем сетку
            children = icon_grid.get_children()
            for child in children:
                icon_grid.remove(child)

            # Добавляем только подходящие иконки
            matching_children = []
            if search_text == "":
                # Если пустой поиск, показываем все иконки
                matching_children = self.all_icon_children
            else:
                # Иначе показываем только совпадающие
                for child, icon_name_lower in self.all_icon_children:
                    if search_text in icon_name_lower:
                        matching_children.append((child, icon_name_lower))

            # Добавляем подходящие иконки в сетку
            for child, icon_name_lower in matching_children:
                icon_grid.add(child)
                child.show_all()

            # Обновляем сетку
            icon_grid.show()

        except Exception as e:
            print(f"Ошибка поиска: {e}")

        # Сбрасываем таймер
        self.search_timer = None
        return False  # Важно: возвращаем False чтобы удалить timeout callback

    def create_icon_widget(self, icon_name, icon_value):
        """Создание виджета для отображения иконки"""
        try:
            icon_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=2)
            icon_box.set_size_request(70, 70)

            # Иконка
            pixbuf = self.load_icon_by_value(icon_value)
            if pixbuf:
                # Масштабируем иконку для отображения в сетке
                scaled_pixbuf = pixbuf.scale_simple(40, 40, GdkPixbuf.InterpType.BILINEAR)
                image = Gtk.Image.new_from_pixbuf(scaled_pixbuf)
            else:
                image = Gtk.Image.new_from_icon_name("image-missing", Gtk.IconSize.DIALOG)
        except:
            image = Gtk.Image.new_from_icon_name("image-missing", Gtk.IconSize.DIALOG)

        icon_box.pack_start(image, False, False, 0)

        # Название
        display_name = icon_name[:12] + "..." if len(icon_name) > 12 else icon_name
        label = Gtk.Label(label=display_name)
        label.set_max_width_chars(12)
        label.set_ellipsize(3)  # Pango.EllipsizeMode.END
        # Используем CSS для уменьшения шрифта
        css_provider = Gtk.CssProvider()
        css_provider.load_from_data(b"label { font-size: 9px; }")
        context = label.get_style_context()
        context.add_provider(css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
        icon_box.pack_start(label, False, False, 0)

        return icon_box

    def on_custom_icon_clicked(self, widget):
        """Выбор пользовательской иконки"""
        dialog = Gtk.FileChooserDialog(
            title="Выберите иконку",
            parent=self,
            action=Gtk.FileChooserAction.OPEN
        )
        dialog.add_buttons(
            Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
            Gtk.STOCK_OPEN, Gtk.ResponseType.OK
        )

        # Устанавливаем начальную папку - папку исполняемого файла
        if self.exec_path:
            exec_dir = os.path.dirname(self.exec_path)
            if os.path.exists(exec_dir):
                dialog.set_current_folder(exec_dir)

        filter_images = Gtk.FileFilter()
        filter_images.set_name("Изображения")
        filter_images.add_mime_type("image/png")
        filter_images.add_mime_type("image/svg+xml")
        filter_images.add_mime_type("image/jpeg")
        filter_images.add_mime_type("image/x-icon")
        filter_images.add_mime_type("image/gif")
        dialog.add_filter(filter_images)

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            self.icon_path = dialog.get_filename()
            # Для пользовательской иконки сохраняем информацию
            icon_name = os.path.splitext(os.path.basename(self.icon_path))[0]
            self.selected_icon_info = (icon_name, self.icon_path)
            self.update_icon_preview()
        dialog.destroy()

    def on_name_changed(self, widget):
        """Автоматический выбор иконки при изменении названия"""
        pass  # Пока отключено, так как выбор через UI

    def create_manage_page(self):
        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)

        # Заголовок
        label = Gtk.Label(label="Управление созданными ярлыками")
        vbox.pack_start(label, False, False, 0)

        # Список ярлыков
        scrolled = Gtk.ScrolledWindow()
        scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)

        # Создаем список
        self.liststore = Gtk.ListStore(str, str, str, str, str)  # имя, путь, иконка, комментарий, категория
        self.treeview = Gtk.TreeView(model=self.liststore)

        # Колонки
        renderer_text = Gtk.CellRendererText()
        column_name = Gtk.TreeViewColumn("Название", renderer_text, text=0)
        self.treeview.append_column(column_name)

        column_comment = Gtk.TreeViewColumn("Описание", renderer_text, text=3)
        self.treeview.append_column(column_comment)

        column_category = Gtk.TreeViewColumn("Категория", renderer_text, text=4)
        self.treeview.append_column(column_category)

        column_path = Gtk.TreeViewColumn("Путь", renderer_text, text=1)
        self.treeview.append_column(column_path)

        scrolled.add(self.treeview)
        vbox.pack_start(scrolled, True, True, 0)

        # Кнопки управления
        button_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)

        self.refresh_button = Gtk.Button(label="Обновить список")
        self.refresh_button.connect("clicked", self.on_refresh_clicked)
        button_box.pack_start(self.refresh_button, False, False, 0)

        self.delete_button = Gtk.Button(label="Удалить выбранный")
        self.delete_button.connect("clicked", self.on_delete_clicked)
        button_box.pack_start(self.delete_button, False, False, 0)

        vbox.pack_start(button_box, False, False, 0)

        # Загружаем список при первом открытии
        self.load_desktop_entries()

        return vbox

    def on_exec_clicked(self, widget):
        dialog = Gtk.FileChooserDialog(
            title="Выберите исполняемый файл",
            parent=self,
            action=Gtk.FileChooserAction.OPEN
        )
        dialog.add_buttons(
            Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
            Gtk.STOCK_OPEN, Gtk.ResponseType.OK
        )

        response = dialog.run()
        if response == Gtk.ResponseType.OK:
            self.exec_path = dialog.get_filename()
            self.exec_label.set_text(self.exec_path)
        dialog.destroy()

    def on_create_clicked(self, widget):
        name = self.name_entry.get_text().strip()
        comment = self.comment_entry.get_text().strip()
        category_index = self.category_combo.get_active()
        category = None
        if category_index > 0:  # Если выбрана не "Не выбрано"
            category = self.category_combo.get_active_text()

        if not name or not self.exec_path:
            dialog = Gtk.MessageDialog(
                transient_for=self,
                flags=0,
                message_type=Gtk.MessageType.WARNING,
                buttons=Gtk.ButtonsType.OK,
                text="Пожалуйста, укажите название и исполняемый файл."
            )
            dialog.run()
            dialog.destroy()
            return

        # Определяем имя файла ярлыка
        safe_name = "".join(c for c in name if c.isalnum() or c in " _-").strip()
        if not safe_name:
            safe_name = "application"

        os.makedirs(self.desktop_dir, exist_ok=True)

        # Определяем иконку
        icon_value = "application-x-executable"  # значение по умолчанию

        if hasattr(self, 'selected_icon_info') and self.selected_icon_info:
            icon_name, icon_source = self.selected_icon_info

            if self.icon_mode == "system":
                # Системная иконка - используем исходное значение
                icon_value = icon_source
            elif self.icon_mode == "custom" and os.path.exists(icon_source):
                # Пользовательская иконка - копируем и используем путь
                icon_ext = os.path.splitext(icon_source)[1]
                icon_target = os.path.join(self.desktop_dir, f"{safe_name}{icon_ext}")
                try:
                    shutil.copy(icon_source, icon_target)
                    icon_value = icon_target
                except Exception as e:
                    dialog_error = Gtk.MessageDialog(
                        transient_for=self,
                        flags=0,
                        message_type=Gtk.MessageType.ERROR,
                        buttons=Gtk.ButtonsType.OK,
                        text=f"Ошибка копирования иконки: {str(e)}"
                    )
                    dialog_error.run()
                    dialog_error.destroy()
                    return

        # Создаем .desktop файл
        desktop_file_path = os.path.join(self.desktop_dir, f"{safe_name}.desktop")

        terminal = "true" if self.terminal_check.get_active() else "false"

        # Формируем категории
        categories_str = "Utility;"  # По умолчанию
        if category:
            categories_str = f"{category};"

        desktop_content = f"""[Desktop Entry]
Name={name}
Comment={comment}
Exec={self.exec_path}
Icon={icon_value}
Terminal={terminal}
Type=Application
Categories={categories_str}
"""

        try:
            with open(desktop_file_path, 'w') as f:
                f.write(desktop_content)

            # Делаем файл исполняемым
            os.chmod(desktop_file_path, 0o755)

            self.refresh_applications()

            dialog = Gtk.MessageDialog(
                transient_for=self,
                flags=0,
                message_type=Gtk.MessageType.INFO,
                buttons=Gtk.ButtonsType.OK,
                text="Ярлык успешно создан и добавлен!"
            )
            dialog.run()
            dialog.destroy()

            # Очищаем поля
            self.clear_create_fields()

        except Exception as e:
            dialog_error = Gtk.MessageDialog(
                transient_for=self,
                flags=0,
                message_type=Gtk.MessageType.ERROR,
                buttons=Gtk.ButtonsType.OK,
                text=f"Ошибка создания ярлыка: {str(e)}"
            )
            dialog_error.run()
            dialog_error.destroy()

    def clear_create_fields(self):
        """Очищаем поля создания ярлыка"""
        self.exec_path = ""
        self.icon_path = ""
        self.selected_system_icon = ""
        self.selected_icon_info = None
        self.icon_mode = "system"
        self.system_icon_radio.set_active(True)
        self.exec_label.set_text("Файл не выбран")
        self.icon_image.set_from_icon_name("image-missing", Gtk.IconSize.DIALOG)
        self.icon_name_label.set_text("Иконка не выбрана")
        self.name_entry.set_text("")
        self.comment_entry.set_text("")
        self.category_combo.set_active(0)
        self.terminal_check.set_active(False)

    def load_desktop_entries(self):
        """Загружаем список .desktop файлов"""
        self.liststore.clear()
        os.makedirs(self.desktop_dir, exist_ok=True)

        desktop_files = glob.glob(os.path.join(self.desktop_dir, "*.desktop"))

        for file_path in desktop_files:
            try:
                name = ""
                comment = ""
                icon_path = ""
                categories = ""

                with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                    for line in f:
                        if line.startswith("Name="):
                            name = line.strip().split("=", 1)[1]
                        elif line.startswith("Comment="):
                            comment = line.strip().split("=", 1)[1]
                        elif line.startswith("Icon="):
                            icon_path = line.strip().split("=", 1)[1]
                        elif line.startswith("Categories="):
                            categories = line.strip().split("=", 1)[1].rstrip(';')

                if name:
                    self.liststore.append([name, file_path, icon_path, comment, categories])
            except Exception:
                # Пропускаем файлы с ошибками
                continue

    def on_refresh_clicked(self, widget):
        """Обновить список ярлыков"""
        # Обновляем список категорий
        self.categories = self.get_system_categories()
        self.category_combo.remove_all()
        self.category_combo.append_text("Не выбрано")
        for category in self.categories:
            self.category_combo.append_text(category)
        self.category_combo.set_active(0)

        # Обновляем список системных иконок
        self.system_icons = self.get_system_icons()

        self.load_desktop_entries()

    def on_delete_clicked(self, widget):
        """Удалить выбранный ярлык"""
        selection = self.treeview.get_selection()
        model, treeiter = selection.get_selected()

        if treeiter is None:
            dialog = Gtk.MessageDialog(
                transient_for=self,
                flags=0,
                message_type=Gtk.MessageType.WARNING,
                buttons=Gtk.ButtonsType.OK,
                text="Пожалуйста, выберите ярлык для удаления."
            )
            dialog.run()
            dialog.destroy()
            return

        name = model[treeiter][0]
        file_path = model[treeiter][1]
        icon_path = model[treeiter][2]

        # Подтверждение удаления
        dialog = Gtk.MessageDialog(
            transient_for=self,
            flags=0,
            message_type=Gtk.MessageType.QUESTION,
            buttons=Gtk.ButtonsType.YES_NO,
            text=f"Вы уверены, что хотите удалить ярлык '{name}'?"
        )
        dialog.format_secondary_text("Это действие нельзя отменить.")

        response = dialog.run()
        dialog.destroy()

        if response == Gtk.ResponseType.YES:
            try:
                # Удаляем .desktop файл
                if os.path.exists(file_path):
                    os.remove(file_path)

                # Удаляем иконку, если она существует и находится в той же папке
                if icon_path and os.path.exists(icon_path):
                    icon_dir = os.path.dirname(icon_path)
                    if icon_dir == self.desktop_dir:
                        os.remove(icon_path)

                # Обновляем список
                self.load_desktop_entries()
                self.refresh_applications()

                dialog_success = Gtk.MessageDialog(
                    transient_for=self,
                    flags=0,
                    message_type=Gtk.MessageType.INFO,
                    buttons=Gtk.ButtonsType.OK,
                    text="Ярлык успешно удален!"
                )
                dialog_success.run()
                dialog_success.destroy()

            except Exception as e:
                dialog_error = Gtk.MessageDialog(
                    transient_for=self,
                    flags=0,
                    message_type=Gtk.MessageType.ERROR,
                    buttons=Gtk.ButtonsType.OK,
                    text=f"Ошибка удаления ярлыка: {str(e)}"
                )
                dialog_error.run()
                dialog_error.destroy()

    def refresh_applications(self):
        """Пытаемся обновить меню приложений"""
        try:
            subprocess.run(["update-desktop-database", self.desktop_dir], 
                         check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        except:
            pass

        try:
            subprocess.run(["glib-compile-schemas", "/usr/share/glib-2.0/schemas"], 
                         check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        except:
            pass

        try:
            subprocess.run(["kbuildsycoca5"], 
                         check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        except:
            pass

        try:
            subprocess.run(["xdg-desktop-menu", "forceupdate"], 
                         check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        except:
            pass

        try:
            subprocess.run(["notify-send", "Desktop Manager", "Меню приложений обновлено"], 
                         check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
        except:
            pass

if __name__ == "__main__":
    app = DesktopEntryCreator()
    app.connect("destroy", Gtk.main_quit)
    app.show_all()
    Gtk.main()