forked from Boria138/PortProtonQt
Compare commits
6 Commits
8184a0bc71
...
e6e46d1aee
Author | SHA1 | Date | |
---|---|---|---|
e6e46d1aee
|
|||
c64c01165d
|
|||
4d7caa33b5
|
|||
7fb05322ad
|
|||
fea5ff9877
|
|||
dc06f78c43
|
@@ -72,8 +72,8 @@
|
|||||||
- [ ] Доделать светлую тему
|
- [ ] Доделать светлую тему
|
||||||
- [ ] Добавить подсказки к управлению с геймпада
|
- [ ] Добавить подсказки к управлению с геймпада
|
||||||
- [ ] Добавить загрузку звуков в темы например для добавления звука запуска в тему и тд
|
- [ ] Добавить загрузку звуков в темы например для добавления звука запуска в тему и тд
|
||||||
- [ ] Добавить миниатюры к выбору файлов в диалоге добавления игры
|
- [X] Добавить миниатюры к выбору файлов в диалоге добавления игры
|
||||||
- [ ] Добавить быстрый доступ к смонтированным дискам к выбору файлов в диалоге добавления игры
|
- [X] Добавить быстрый доступ к смонтированным дискам к выбору файлов в диалоге добавления игры
|
||||||
|
|
||||||
### Установка (devel)
|
### Установка (devel)
|
||||||
|
|
||||||
|
@@ -601,7 +601,6 @@ Icon={icon_path}
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def delete_game(self, game_name, exec_line):
|
def delete_game(self, game_name, exec_line):
|
||||||
"""Delete the .desktop file and associated custom data for the game."""
|
|
||||||
reply = QMessageBox.question(
|
reply = QMessageBox.question(
|
||||||
self.parent,
|
self.parent,
|
||||||
_("Confirm Deletion"),
|
_("Confirm Deletion"),
|
||||||
@@ -647,6 +646,10 @@ Icon={icon_path}
|
|||||||
_("Failed to delete custom data: {error}").format(error=str(e))
|
_("Failed to delete custom data: {error}").format(error=str(e))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Перезагрузка списка игр и обновление сетки
|
||||||
|
self.load_games()
|
||||||
|
self.update_game_grid()
|
||||||
|
|
||||||
def add_to_menu(self, game_name, exec_line):
|
def add_to_menu(self, game_name, exec_line):
|
||||||
"""Copy the .desktop file to ~/.local/share/applications."""
|
"""Copy the .desktop file to ~/.local/share/applications."""
|
||||||
if not self._check_portproton():
|
if not self._check_portproton():
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from typing import cast, TYPE_CHECKING
|
from typing import cast, TYPE_CHECKING
|
||||||
from PySide6.QtGui import QPixmap
|
from PySide6.QtGui import QPixmap, QIcon
|
||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QDialog, QLineEdit, QFormLayout, QPushButton,
|
QDialog, QLineEdit, QFormLayout, QPushButton,
|
||||||
QHBoxLayout, QDialogButtonBox, QLabel, QVBoxLayout, QListWidget
|
QHBoxLayout, QDialogButtonBox, QLabel, QVBoxLayout, QListWidget, QScrollArea, QWidget, QListWidgetItem
|
||||||
)
|
)
|
||||||
from PySide6.QtCore import Qt, QObject, Signal
|
from PySide6.QtCore import Qt, QObject, Signal, QMimeDatabase
|
||||||
from icoextract import IconExtractor, IconExtractorError
|
from icoextract import IconExtractor, IconExtractorError
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from portprotonqt.config_utils import get_portproton_location
|
from portprotonqt.config_utils import get_portproton_location
|
||||||
@@ -92,6 +92,7 @@ class FileExplorer(QDialog):
|
|||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.file_signal = FileSelectedSignal()
|
self.file_signal = FileSelectedSignal()
|
||||||
self.file_filter = file_filter # Store the file filter
|
self.file_filter = file_filter # Store the file filter
|
||||||
|
self.mime_db = QMimeDatabase() # Initialize QMimeDatabase for mimetype detection
|
||||||
self.setup_ui()
|
self.setup_ui()
|
||||||
|
|
||||||
# Настройки окна
|
# Настройки окна
|
||||||
@@ -111,16 +112,52 @@ class FileExplorer(QDialog):
|
|||||||
if self.input_manager:
|
if self.input_manager:
|
||||||
self.input_manager.enable_file_explorer_mode(self)
|
self.input_manager.enable_file_explorer_mode(self)
|
||||||
|
|
||||||
|
# Initialize drives list
|
||||||
|
self.update_drives_list()
|
||||||
|
|
||||||
|
def get_mounted_drives(self):
|
||||||
|
"""Получение списка смонтированных дисков из /proc/mounts, исключая системные пути"""
|
||||||
|
mounted_drives = []
|
||||||
|
try:
|
||||||
|
with open('/proc/mounts') as f:
|
||||||
|
for line in f:
|
||||||
|
parts = line.strip().split()
|
||||||
|
if len(parts) < 2:
|
||||||
|
continue
|
||||||
|
mount_point = parts[1]
|
||||||
|
# Исключаем системные и временные пути
|
||||||
|
if mount_point.startswith(('/run', '/dev', '/sys', '/proc', '/tmp', '/snap', '/var/lib')):
|
||||||
|
continue
|
||||||
|
# Проверяем, является ли точка монтирования директорией и доступна ли она
|
||||||
|
if os.path.isdir(mount_point) and os.access(mount_point, os.R_OK):
|
||||||
|
mounted_drives.append(mount_point)
|
||||||
|
return sorted(mounted_drives)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при получении смонтированных дисков: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
def setup_ui(self):
|
def setup_ui(self):
|
||||||
"""Настройка интерфейса"""
|
"""Настройка интерфейса"""
|
||||||
self.setWindowTitle("File Explorer")
|
self.setWindowTitle("File Explorer")
|
||||||
self.setGeometry(100, 100, 800, 600)
|
self.setGeometry(100, 100, 600, 600)
|
||||||
|
|
||||||
self.main_layout = QVBoxLayout()
|
self.main_layout = QVBoxLayout()
|
||||||
self.main_layout.setContentsMargins(10, 10, 10, 10)
|
self.main_layout.setContentsMargins(10, 10, 10, 10)
|
||||||
self.main_layout.setSpacing(10)
|
self.main_layout.setSpacing(10)
|
||||||
self.setLayout(self.main_layout)
|
self.setLayout(self.main_layout)
|
||||||
|
|
||||||
|
# Панель для смонтированных дисков
|
||||||
|
self.drives_layout = QHBoxLayout()
|
||||||
|
self.drives_scroll = QScrollArea()
|
||||||
|
self.drives_scroll.setWidgetResizable(True)
|
||||||
|
self.drives_container = QWidget()
|
||||||
|
self.drives_container.setLayout(self.drives_layout)
|
||||||
|
self.drives_scroll.setWidget(self.drives_container)
|
||||||
|
self.drives_scroll.setStyleSheet(FileExplorerStyles.BUTTON_STYLE)
|
||||||
|
self.drives_scroll.setFixedHeight(50)
|
||||||
|
self.main_layout.addWidget(self.drives_scroll)
|
||||||
|
|
||||||
|
# Путь
|
||||||
self.path_label = QLabel()
|
self.path_label = QLabel()
|
||||||
self.path_label.setStyleSheet(FileExplorerStyles.PATH_LABEL_STYLE)
|
self.path_label.setStyleSheet(FileExplorerStyles.PATH_LABEL_STYLE)
|
||||||
self.main_layout.addWidget(self.path_label)
|
self.main_layout.addWidget(self.path_label)
|
||||||
@@ -172,18 +209,65 @@ class FileExplorer(QDialog):
|
|||||||
full_path = os.path.join(self.current_path, selected)
|
full_path = os.path.join(self.current_path, selected)
|
||||||
|
|
||||||
if os.path.isdir(full_path):
|
if os.path.isdir(full_path):
|
||||||
self.current_path = full_path
|
# Если выбрана директория, нормализуем путь
|
||||||
|
self.current_path = os.path.normpath(full_path)
|
||||||
self.update_file_list()
|
self.update_file_list()
|
||||||
else:
|
else:
|
||||||
self.file_signal.file_selected.emit(full_path)
|
# Для файла отправляем нормализованный путь
|
||||||
|
self.file_signal.file_selected.emit(os.path.normpath(full_path))
|
||||||
self.accept()
|
self.accept()
|
||||||
|
|
||||||
|
def previous_dir(self):
|
||||||
|
"""Возврат к родительской директории"""
|
||||||
|
try:
|
||||||
|
if self.current_path == "/":
|
||||||
|
return # Уже в корне
|
||||||
|
|
||||||
|
# Нормализуем путь (убираем конечный слеш, если есть)
|
||||||
|
normalized_path = os.path.normpath(self.current_path)
|
||||||
|
# Получаем родительскую директорию
|
||||||
|
parent_dir = os.path.dirname(normalized_path)
|
||||||
|
|
||||||
|
if not parent_dir:
|
||||||
|
parent_dir = "/"
|
||||||
|
|
||||||
|
self.current_path = parent_dir
|
||||||
|
self.update_file_list()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error navigating to parent directory: {e}")
|
||||||
|
|
||||||
|
def update_drives_list(self):
|
||||||
|
"""Обновление списка смонтированных дисков"""
|
||||||
|
for i in reversed(range(self.drives_layout.count())):
|
||||||
|
widget = self.drives_layout.itemAt(i).widget()
|
||||||
|
if widget:
|
||||||
|
widget.deleteLater()
|
||||||
|
|
||||||
|
drives = self.get_mounted_drives()
|
||||||
|
for drive in drives:
|
||||||
|
drive_name = os.path.basename(drive) or drive.split('/')[-1] or drive
|
||||||
|
button = QPushButton(drive_name)
|
||||||
|
button.setStyleSheet(FileExplorerStyles.BUTTON_STYLE)
|
||||||
|
button.clicked.connect(lambda checked, path=drive: self.change_drive(path))
|
||||||
|
self.drives_layout.addWidget(button)
|
||||||
|
self.drives_layout.addStretch()
|
||||||
|
|
||||||
|
def change_drive(self, drive_path):
|
||||||
|
"""Переход к выбранному диску"""
|
||||||
|
if os.path.isdir(drive_path) and os.access(drive_path, os.R_OK):
|
||||||
|
self.current_path = os.path.normpath(drive_path)
|
||||||
|
self.update_file_list()
|
||||||
|
else:
|
||||||
|
logger.warning(f"Путь диска недоступен: {drive_path}")
|
||||||
|
|
||||||
def update_file_list(self):
|
def update_file_list(self):
|
||||||
"""Обновление списка файлов"""
|
"""Обновление списка файлов с превью в виде иконок"""
|
||||||
self.file_list.clear()
|
self.file_list.clear()
|
||||||
try:
|
try:
|
||||||
if self.current_path != "/":
|
if self.current_path != "/":
|
||||||
self.file_list.addItem("../")
|
item = QListWidgetItem("../")
|
||||||
|
item.setIcon(QIcon.fromTheme("folder-symbolic"))
|
||||||
|
self.file_list.addItem(item)
|
||||||
|
|
||||||
items = os.listdir(self.current_path)
|
items = os.listdir(self.current_path)
|
||||||
dirs = [d for d in items if os.path.isdir(os.path.join(self.current_path, d))]
|
dirs = [d for d in items if os.path.isdir(os.path.join(self.current_path, d))]
|
||||||
@@ -191,13 +275,45 @@ class FileExplorer(QDialog):
|
|||||||
# Apply file filter if provided
|
# Apply file filter if provided
|
||||||
files = [f for f in items if os.path.isfile(os.path.join(self.current_path, f))]
|
files = [f for f in items if os.path.isfile(os.path.join(self.current_path, f))]
|
||||||
if self.file_filter:
|
if self.file_filter:
|
||||||
|
if isinstance(self.file_filter, str):
|
||||||
files = [f for f in files if f.lower().endswith(self.file_filter)]
|
files = [f for f in files if f.lower().endswith(self.file_filter)]
|
||||||
|
elif isinstance(self.file_filter, tuple):
|
||||||
|
files = [f for f in files if any(f.lower().endswith(ext) for ext in self.file_filter)]
|
||||||
|
|
||||||
for d in sorted(dirs):
|
for d in sorted(dirs):
|
||||||
self.file_list.addItem(f"{d}/")
|
item = QListWidgetItem(f"{d}/")
|
||||||
|
item.setIcon(QIcon.fromTheme("folder-symbolic"))
|
||||||
|
self.file_list.addItem(item)
|
||||||
|
|
||||||
for f in sorted(files):
|
for f in sorted(files):
|
||||||
self.file_list.addItem(f)
|
item = QListWidgetItem(f)
|
||||||
|
file_path = os.path.join(self.current_path, f)
|
||||||
|
mime_type = self.mime_db.mimeTypeForFile(file_path).name()
|
||||||
|
|
||||||
|
if mime_type.startswith("image/"):
|
||||||
|
pixmap = QPixmap(file_path)
|
||||||
|
if not pixmap.isNull():
|
||||||
|
item.setIcon(QIcon(pixmap.scaled(64, 64, Qt.AspectRatioMode.KeepAspectRatio)))
|
||||||
|
else:
|
||||||
|
item.setIcon(QIcon.fromTheme("image-x-generic-symbolic"))
|
||||||
|
elif file_path.lower().endswith(".exe"):
|
||||||
|
tmp = tempfile.NamedTemporaryFile(suffix='.png', delete=False)
|
||||||
|
tmp.close()
|
||||||
|
if generate_thumbnail(file_path, tmp.name, size=64):
|
||||||
|
pixmap = QPixmap(tmp.name)
|
||||||
|
if not pixmap.isNull():
|
||||||
|
item.setIcon(QIcon(pixmap))
|
||||||
|
else:
|
||||||
|
item.setIcon(QIcon.fromTheme("application-x-executable-symbolic"))
|
||||||
|
os.unlink(tmp.name)
|
||||||
|
else:
|
||||||
|
item.setIcon(QIcon.fromTheme("application-x-executable-symbolic"))
|
||||||
|
else:
|
||||||
|
icon_name = self.mime_db.mimeTypeForFile(file_path).iconName()
|
||||||
|
symbolic_icon_name = icon_name + "-symbolic" if icon_name else "text-x-generic-symbolic"
|
||||||
|
item.setIcon(QIcon.fromTheme(symbolic_icon_name, QIcon.fromTheme("text-x-generic-symbolic")))
|
||||||
|
|
||||||
|
self.file_list.addItem(item)
|
||||||
|
|
||||||
self.path_label.setText(f"Path: {self.current_path}")
|
self.path_label.setText(f"Path: {self.current_path}")
|
||||||
self.file_list.setCurrentRow(0)
|
self.file_list.setCurrentRow(0)
|
||||||
|
@@ -44,6 +44,7 @@ BUTTONS = {
|
|||||||
'confirm': {ecodes.BTN_SOUTH}, # A (Xbox) / Cross (PS)
|
'confirm': {ecodes.BTN_SOUTH}, # A (Xbox) / Cross (PS)
|
||||||
'back': {ecodes.BTN_EAST}, # B (Xbox) / Circle (PS)
|
'back': {ecodes.BTN_EAST}, # B (Xbox) / Circle (PS)
|
||||||
'add_game': {ecodes.BTN_NORTH}, # X (Xbox) / Triangle (PS)
|
'add_game': {ecodes.BTN_NORTH}, # X (Xbox) / Triangle (PS)
|
||||||
|
'prev_dir': {ecodes.BTN_WEST}, # Y (Xbox) / Square (PS)
|
||||||
'prev_tab': {ecodes.BTN_TL}, # LB (Xbox) / L1 (PS)
|
'prev_tab': {ecodes.BTN_TL}, # LB (Xbox) / L1 (PS)
|
||||||
'next_tab': {ecodes.BTN_TR}, # RB (Xbox) / R1 (PS)
|
'next_tab': {ecodes.BTN_TR}, # RB (Xbox) / R1 (PS)
|
||||||
'context_menu': {ecodes.BTN_START}, # Start (Xbox) / Options (PS)
|
'context_menu': {ecodes.BTN_START}, # Start (Xbox) / Options (PS)
|
||||||
@@ -161,10 +162,12 @@ class InputManager(QObject):
|
|||||||
if not self.file_explorer or not hasattr(self.file_explorer, 'file_list'):
|
if not self.file_explorer or not hasattr(self.file_explorer, 'file_list'):
|
||||||
return
|
return
|
||||||
|
|
||||||
if button_code in BUTTONS['confirm']: # Кнопка A
|
if button_code in BUTTONS['confirm']:
|
||||||
self.file_explorer.select_item()
|
self.file_explorer.select_item()
|
||||||
elif button_code in BUTTONS['back']: # Кнопка B
|
elif button_code in BUTTONS['back']:
|
||||||
self.file_explorer.close()
|
self.file_explorer.close()
|
||||||
|
elif button_code in BUTTONS['prev_dir']:
|
||||||
|
self.file_explorer.previous_dir()
|
||||||
else:
|
else:
|
||||||
if self.original_button_handler:
|
if self.original_button_handler:
|
||||||
self.original_button_handler(button_code)
|
self.original_button_handler(button_code)
|
||||||
|
Reference in New Issue
Block a user