Додаток для створення та управління ярликами
Цей додаток дозволяє створювати .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()