forked from Boria138/PortProtonQt
chore(logs): start rework
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
@@ -6,7 +6,6 @@ from portprotonqt.logger import get_logger
|
|||||||
from portprotonqt.config_utils import read_theme_from_config
|
from portprotonqt.config_utils import read_theme_from_config
|
||||||
from portprotonqt.theme_manager import ThemeManager
|
from portprotonqt.theme_manager import ThemeManager
|
||||||
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
class SafeOpacityEffect(QGraphicsOpacityEffect):
|
class SafeOpacityEffect(QGraphicsOpacityEffect):
|
||||||
@@ -210,7 +209,7 @@ class GameCardAnimations:
|
|||||||
|
|
||||||
def paint_border(self, painter: QPainter):
|
def paint_border(self, painter: QPainter):
|
||||||
if not painter.isActive():
|
if not painter.isActive():
|
||||||
logger.warning("Painter is not active; skipping border paint")
|
logger.debug("Painter is not active; skipping border paint")
|
||||||
return
|
return
|
||||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||||
pen = QPen()
|
pen = QPen()
|
||||||
@@ -259,7 +258,7 @@ class DetailPageAnimations:
|
|||||||
try:
|
try:
|
||||||
detail_page.setGraphicsEffect(original_effect) # type: ignore
|
detail_page.setGraphicsEffect(original_effect) # type: ignore
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
logger.debug("Original effect already deleted")
|
logger.warning("Original effect already deleted")
|
||||||
animation.finished.connect(restore_effect)
|
animation.finished.connect(restore_effect)
|
||||||
animation.finished.connect(load_image_and_restore_effect)
|
animation.finished.connect(load_image_and_restore_effect)
|
||||||
animation.finished.connect(opacity_effect.deleteLater)
|
animation.finished.connect(opacity_effect.deleteLater)
|
||||||
@@ -318,7 +317,7 @@ class DetailPageAnimations:
|
|||||||
if isinstance(animation, QAbstractAnimation) and animation.state() == QAbstractAnimation.State.Running:
|
if isinstance(animation, QAbstractAnimation) and animation.state() == QAbstractAnimation.State.Running:
|
||||||
animation.stop()
|
animation.stop()
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
logger.debug("Animation already deleted for page")
|
logger.warning("Animation already deleted for page")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error stopping existing animation: {e}", exc_info=True)
|
logger.error(f"Error stopping existing animation: {e}", exc_info=True)
|
||||||
finally:
|
finally:
|
||||||
|
@@ -26,7 +26,7 @@ def main():
|
|||||||
if qt_translator.load(system_locale, "qtbase", "_", translations_path):
|
if qt_translator.load(system_locale, "qtbase", "_", translations_path):
|
||||||
app.installTranslator(qt_translator)
|
app.installTranslator(qt_translator)
|
||||||
else:
|
else:
|
||||||
logger.error(f"Qt translations for {system_locale.name()} not found in {translations_path}")
|
logger.warning(f"Qt translations for {system_locale.name()} not found in {translations_path}, using english language")
|
||||||
|
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ logger = get_logger(__name__)
|
|||||||
|
|
||||||
_portproton_location = None
|
_portproton_location = None
|
||||||
|
|
||||||
# Пути к конфигурационным файлам
|
# Paths to configuration files
|
||||||
CONFIG_FILE = os.path.join(
|
CONFIG_FILE = os.path.join(
|
||||||
os.getenv("XDG_CONFIG_HOME", os.path.join(os.path.expanduser("~"), ".config")),
|
os.getenv("XDG_CONFIG_HOME", os.path.join(os.path.expanduser("~"), ".config")),
|
||||||
"PortProtonQt.conf"
|
"PortProtonQt.conf"
|
||||||
@@ -18,17 +18,32 @@ PORTPROTON_CONFIG_FILE = os.path.join(
|
|||||||
"PortProton.conf"
|
"PortProton.conf"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Пути к папкам с темами
|
# Paths to theme directories
|
||||||
xdg_data_home = os.getenv("XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share"))
|
xdg_data_home = os.getenv("XDG_DATA_HOME", os.path.join(os.path.expanduser("~"), ".local", "share"))
|
||||||
THEMES_DIRS = [
|
THEMES_DIRS = [
|
||||||
os.path.join(xdg_data_home, "PortProtonQt", "themes"),
|
os.path.join(xdg_data_home, "PortProtonQt", "themes"),
|
||||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "themes")
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), "themes")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def read_config_safely(config_file: str) -> configparser.ConfigParser | None:
|
||||||
|
"""Safely reads a configuration file and returns a ConfigParser object or None if reading fails."""
|
||||||
|
cp = configparser.ConfigParser()
|
||||||
|
if not os.path.exists(config_file):
|
||||||
|
logger.debug(f"Configuration file {config_file} not found")
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
cp.read(config_file, encoding="utf-8")
|
||||||
|
return cp
|
||||||
|
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e:
|
||||||
|
logger.warning(f"Invalid configuration file format: {e}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Failed to read configuration file: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
def read_config():
|
def read_config():
|
||||||
"""
|
"""Reads the configuration file and returns a dictionary of parameters.
|
||||||
Читает конфигурационный файл и возвращает словарь параметров.
|
Example line in config (no sections):
|
||||||
Пример строки в конфиге (без секций):
|
|
||||||
detail_level = detailed
|
detail_level = detailed
|
||||||
"""
|
"""
|
||||||
config_dict = {}
|
config_dict = {}
|
||||||
@@ -44,29 +59,17 @@ def read_config():
|
|||||||
return config_dict
|
return config_dict
|
||||||
|
|
||||||
def read_theme_from_config():
|
def read_theme_from_config():
|
||||||
|
"""Reads the theme from the [Appearance] section of the configuration file.
|
||||||
|
Returns 'standart' if the parameter is not set.
|
||||||
"""
|
"""
|
||||||
Читает из конфигурационного файла тему из секции [Appearance].
|
cp = read_config_safely(CONFIG_FILE)
|
||||||
Если параметр не задан, возвращает "standart".
|
if cp is None:
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e:
|
|
||||||
logger.error("Ошибка в конфигурационном файле: %s", e)
|
|
||||||
return "standart"
|
return "standart"
|
||||||
return cp.get("Appearance", "theme", fallback="standart")
|
return cp.get("Appearance", "theme", fallback="standart")
|
||||||
|
|
||||||
def save_theme_to_config(theme_name):
|
def save_theme_to_config(theme_name):
|
||||||
"""
|
"""Saves the selected theme name to the [Appearance] section of the configuration file."""
|
||||||
Сохраняет имя выбранной темы в секции [Appearance] конфигурационного файла.
|
cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser()
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e:
|
|
||||||
logger.error("Ошибка в конфигурационном файле: %s", e)
|
|
||||||
if "Appearance" not in cp:
|
if "Appearance" not in cp:
|
||||||
cp["Appearance"] = {}
|
cp["Appearance"] = {}
|
||||||
cp["Appearance"]["theme"] = theme_name
|
cp["Appearance"]["theme"] = theme_name
|
||||||
@@ -74,34 +77,18 @@ def save_theme_to_config(theme_name):
|
|||||||
cp.write(configfile)
|
cp.write(configfile)
|
||||||
|
|
||||||
def read_time_config():
|
def read_time_config():
|
||||||
|
"""Reads time settings from the [Time] section of the configuration file.
|
||||||
|
If the section or parameter is missing, saves and returns 'detailed' as default.
|
||||||
"""
|
"""
|
||||||
Читает настройки времени из секции [Time] конфигурационного файла.
|
cp = read_config_safely(CONFIG_FILE)
|
||||||
Если секция или параметр отсутствуют, сохраняет и возвращает "detailed" по умолчанию.
|
if cp is None or not cp.has_section("Time") or not cp.has_option("Time", "detail_level"):
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e:
|
|
||||||
logger.error("Ошибка в конфигурационном файле: %s", e)
|
|
||||||
save_time_config("detailed")
|
|
||||||
return "detailed"
|
|
||||||
if not cp.has_section("Time") or not cp.has_option("Time", "detail_level"):
|
|
||||||
save_time_config("detailed")
|
save_time_config("detailed")
|
||||||
return "detailed"
|
return "detailed"
|
||||||
return cp.get("Time", "detail_level", fallback="detailed").lower()
|
return cp.get("Time", "detail_level", fallback="detailed").lower()
|
||||||
return "detailed"
|
|
||||||
|
|
||||||
def save_time_config(detail_level):
|
def save_time_config(detail_level):
|
||||||
"""
|
"""Saves the time detail level to the [Time] section of the configuration file."""
|
||||||
Сохраняет настройку уровня детализации времени в секции [Time].
|
cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser()
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e:
|
|
||||||
logger.error("Ошибка в конфигурационном файле: %s", e)
|
|
||||||
if "Time" not in cp:
|
if "Time" not in cp:
|
||||||
cp["Time"] = {}
|
cp["Time"] = {}
|
||||||
cp["Time"]["detail_level"] = detail_level
|
cp["Time"]["detail_level"] = detail_level
|
||||||
@@ -109,48 +96,42 @@ def save_time_config(detail_level):
|
|||||||
cp.write(configfile)
|
cp.write(configfile)
|
||||||
|
|
||||||
def read_file_content(file_path):
|
def read_file_content(file_path):
|
||||||
"""
|
"""Reads the content of a file and returns it as a string."""
|
||||||
Читает содержимое файла и возвращает его как строку.
|
|
||||||
"""
|
|
||||||
with open(file_path, encoding="utf-8") as f:
|
with open(file_path, encoding="utf-8") as f:
|
||||||
return f.read().strip()
|
return f.read().strip()
|
||||||
|
|
||||||
def get_portproton_location():
|
def get_portproton_location():
|
||||||
"""
|
"""Returns the path to the PortProton directory.
|
||||||
Возвращает путь к директории PortProton.
|
Checks the cached path first. If not set, reads from PORTPROTON_CONFIG_FILE.
|
||||||
Сначала проверяется кэшированный путь. Если он отсутствует, проверяется
|
If the path is invalid, uses the default directory.
|
||||||
наличие пути в файле PORTPROTON_CONFIG_FILE. Если путь недоступен,
|
|
||||||
используется директория по умолчанию.
|
|
||||||
"""
|
"""
|
||||||
global _portproton_location
|
global _portproton_location
|
||||||
if _portproton_location is not None:
|
if _portproton_location is not None:
|
||||||
return _portproton_location
|
return _portproton_location
|
||||||
|
|
||||||
# Попытка чтения пути из конфигурационного файла
|
|
||||||
if os.path.isfile(PORTPROTON_CONFIG_FILE):
|
if os.path.isfile(PORTPROTON_CONFIG_FILE):
|
||||||
try:
|
try:
|
||||||
location = read_file_content(PORTPROTON_CONFIG_FILE).strip()
|
location = read_file_content(PORTPROTON_CONFIG_FILE).strip()
|
||||||
if location and os.path.isdir(location):
|
if location and os.path.isdir(location):
|
||||||
_portproton_location = location
|
_portproton_location = location
|
||||||
logger.info(f"Путь PortProton из конфигурации: {location}")
|
logger.info(f"PortProton path from configuration: {location}")
|
||||||
return _portproton_location
|
return _portproton_location
|
||||||
logger.warning(f"Недействительный путь в конфиге PortProton: {location}")
|
logger.warning(f"Invalid PortProton path in configuration: {location}, using default path")
|
||||||
except (OSError, PermissionError) as e:
|
except (OSError, PermissionError) as e:
|
||||||
logger.error(f"Ошибка чтения файла конфигурации PortProton: {e}")
|
logger.warning(f"Failed to read PortProton configuration file: {e}, using default path")
|
||||||
|
|
||||||
default_dir = os.path.join(os.path.expanduser("~"), ".var", "app", "ru.linux_gaming.PortProton")
|
default_dir = os.path.join(os.path.expanduser("~"), ".var", "app", "ru.linux_gaming.PortProton")
|
||||||
if os.path.isdir(default_dir):
|
if os.path.isdir(default_dir):
|
||||||
_portproton_location = default_dir
|
_portproton_location = default_dir
|
||||||
logger.info(f"Используется директория flatpak PortProton: {default_dir}")
|
logger.info(f"Using flatpak PortProton directory: {default_dir}")
|
||||||
return _portproton_location
|
return _portproton_location
|
||||||
|
|
||||||
logger.warning("Конфигурация и директория flatpak PortProton не найдены")
|
logger.warning("PortProton configuration and flatpak directory not found")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def parse_desktop_entry(file_path):
|
def parse_desktop_entry(file_path):
|
||||||
"""
|
"""Reads and parses a .desktop file using configparser.
|
||||||
Читает и парсит .desktop файл с помощью configparser.
|
Returns None if the [Desktop Entry] section is missing.
|
||||||
Если секция [Desktop Entry] отсутствует, возвращается None.
|
|
||||||
"""
|
"""
|
||||||
cp = configparser.ConfigParser(interpolation=None)
|
cp = configparser.ConfigParser(interpolation=None)
|
||||||
cp.read(file_path, encoding="utf-8")
|
cp.read(file_path, encoding="utf-8")
|
||||||
@@ -159,9 +140,8 @@ def parse_desktop_entry(file_path):
|
|||||||
return cp["Desktop Entry"]
|
return cp["Desktop Entry"]
|
||||||
|
|
||||||
def load_theme_metainfo(theme_name):
|
def load_theme_metainfo(theme_name):
|
||||||
"""
|
"""Loads theme metadata from metainfo.ini in the theme's root directory.
|
||||||
Загружает метаинформацию темы из файла metainfo.ini в корне папки темы.
|
Expected fields: author, author_link, description, name.
|
||||||
Ожидаемые поля: author, author_link, description, name.
|
|
||||||
"""
|
"""
|
||||||
meta = {}
|
meta = {}
|
||||||
for themes_dir in THEMES_DIRS:
|
for themes_dir in THEMES_DIRS:
|
||||||
@@ -179,34 +159,18 @@ def load_theme_metainfo(theme_name):
|
|||||||
return meta
|
return meta
|
||||||
|
|
||||||
def read_card_size():
|
def read_card_size():
|
||||||
|
"""Reads the card size (width) from the [Cards] section.
|
||||||
|
Returns 250 if the parameter is not set.
|
||||||
"""
|
"""
|
||||||
Читает размер карточек (ширину) из секции [Cards],
|
cp = read_config_safely(CONFIG_FILE)
|
||||||
Если параметр не задан, возвращает 250.
|
if cp is None or not cp.has_section("Cards") or not cp.has_option("Cards", "card_width"):
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e:
|
|
||||||
logger.error("Ошибка в конфигурационном файле: %s", e)
|
|
||||||
save_card_size(250)
|
|
||||||
return 250
|
|
||||||
if not cp.has_section("Cards") or not cp.has_option("Cards", "card_width"):
|
|
||||||
save_card_size(250)
|
save_card_size(250)
|
||||||
return 250
|
return 250
|
||||||
return cp.getint("Cards", "card_width", fallback=250)
|
return cp.getint("Cards", "card_width", fallback=250)
|
||||||
return 250
|
|
||||||
|
|
||||||
def save_card_size(card_width):
|
def save_card_size(card_width):
|
||||||
"""
|
"""Saves the card size (width) to the [Cards] section."""
|
||||||
Сохраняет размер карточек (ширину) в секцию [Cards].
|
cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser()
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e:
|
|
||||||
logger.error("Ошибка в конфигурационном файле: %s", e)
|
|
||||||
if "Cards" not in cp:
|
if "Cards" not in cp:
|
||||||
cp["Cards"] = {}
|
cp["Cards"] = {}
|
||||||
cp["Cards"]["card_width"] = str(card_width)
|
cp["Cards"]["card_width"] = str(card_width)
|
||||||
@@ -214,34 +178,18 @@ def save_card_size(card_width):
|
|||||||
cp.write(configfile)
|
cp.write(configfile)
|
||||||
|
|
||||||
def read_sort_method():
|
def read_sort_method():
|
||||||
|
"""Reads the sort method from the [Games] section.
|
||||||
|
Returns 'last_launch' if the parameter is not set.
|
||||||
"""
|
"""
|
||||||
Читает метод сортировки из секции [Games].
|
cp = read_config_safely(CONFIG_FILE)
|
||||||
Если параметр не задан, возвращает last_launch.
|
if cp is None or not cp.has_section("Games") or not cp.has_option("Games", "sort_method"):
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e:
|
|
||||||
logger.error("Ошибка в конфигурационном файле: %s", e)
|
|
||||||
save_sort_method("last_launch")
|
|
||||||
return "last_launch"
|
|
||||||
if not cp.has_section("Games") or not cp.has_option("Games", "sort_method"):
|
|
||||||
save_sort_method("last_launch")
|
save_sort_method("last_launch")
|
||||||
return "last_launch"
|
return "last_launch"
|
||||||
return cp.get("Games", "sort_method", fallback="last_launch").lower()
|
return cp.get("Games", "sort_method", fallback="last_launch").lower()
|
||||||
return "last_launch"
|
|
||||||
|
|
||||||
def save_sort_method(sort_method):
|
def save_sort_method(sort_method):
|
||||||
"""
|
"""Saves the sort method to the [Games] section."""
|
||||||
Сохраняет метод сортировки в секцию [Games].
|
cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser()
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e:
|
|
||||||
logger.error("Ошибка в конфигурационном файле: %s", e)
|
|
||||||
if "Games" not in cp:
|
if "Games" not in cp:
|
||||||
cp["Games"] = {}
|
cp["Games"] = {}
|
||||||
cp["Games"]["sort_method"] = sort_method
|
cp["Games"]["sort_method"] = sort_method
|
||||||
@@ -249,34 +197,18 @@ def save_sort_method(sort_method):
|
|||||||
cp.write(configfile)
|
cp.write(configfile)
|
||||||
|
|
||||||
def read_display_filter():
|
def read_display_filter():
|
||||||
|
"""Reads the display_filter parameter from the [Games] section.
|
||||||
|
Returns 'all' if the parameter is missing.
|
||||||
"""
|
"""
|
||||||
Читает параметр display_filter из секции [Games].
|
cp = read_config_safely(CONFIG_FILE)
|
||||||
Если параметр отсутствует, сохраняет и возвращает значение "all".
|
if cp is None or not cp.has_section("Games") or not cp.has_option("Games", "display_filter"):
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Ошибка чтения конфига: %s", e)
|
|
||||||
save_display_filter("all")
|
|
||||||
return "all"
|
|
||||||
if not cp.has_section("Games") or not cp.has_option("Games", "display_filter"):
|
|
||||||
save_display_filter("all")
|
save_display_filter("all")
|
||||||
return "all"
|
return "all"
|
||||||
return cp.get("Games", "display_filter", fallback="all").lower()
|
return cp.get("Games", "display_filter", fallback="all").lower()
|
||||||
return "all"
|
|
||||||
|
|
||||||
def save_display_filter(filter_value):
|
def save_display_filter(filter_value):
|
||||||
"""
|
"""Saves the display_filter parameter to the [Games] section."""
|
||||||
Сохраняет параметр display_filter в секцию [Games] конфигурационного файла.
|
cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser()
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Ошибка чтения конфига: %s", e)
|
|
||||||
if "Games" not in cp:
|
if "Games" not in cp:
|
||||||
cp["Games"] = {}
|
cp["Games"] = {}
|
||||||
cp["Games"]["display_filter"] = filter_value
|
cp["Games"]["display_filter"] = filter_value
|
||||||
@@ -284,37 +216,23 @@ def save_display_filter(filter_value):
|
|||||||
cp.write(configfile)
|
cp.write(configfile)
|
||||||
|
|
||||||
def read_favorites():
|
def read_favorites():
|
||||||
|
"""Reads the list of favorite games from the [Favorites] section.
|
||||||
|
The list is stored as a quoted string with comma-separated names.
|
||||||
|
Returns an empty list if the section or parameter is missing.
|
||||||
"""
|
"""
|
||||||
Читает список избранных игр из секции [Favorites] конфигурационного файла.
|
cp = read_config_safely(CONFIG_FILE)
|
||||||
Список хранится как строка, заключённая в кавычки, с именами, разделёнными запятыми.
|
if cp is None or not cp.has_section("Favorites") or not cp.has_option("Favorites", "games"):
|
||||||
Если секция или параметр отсутствуют, возвращает пустой список.
|
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Ошибка чтения конфига: %s", e)
|
|
||||||
return []
|
return []
|
||||||
if cp.has_section("Favorites") and cp.has_option("Favorites", "games"):
|
|
||||||
favs = cp.get("Favorites", "games", fallback="").strip()
|
favs = cp.get("Favorites", "games", fallback="").strip()
|
||||||
# Если строка начинается и заканчивается кавычками, удаляем их
|
|
||||||
if favs.startswith('"') and favs.endswith('"'):
|
if favs.startswith('"') and favs.endswith('"'):
|
||||||
favs = favs[1:-1]
|
favs = favs[1:-1]
|
||||||
return [s.strip() for s in favs.split(",") if s.strip()]
|
return [s.strip() for s in favs.split(",") if s.strip()]
|
||||||
return []
|
|
||||||
|
|
||||||
def save_favorites(favorites):
|
def save_favorites(favorites):
|
||||||
|
"""Saves the list of favorite games to the [Favorites] section.
|
||||||
|
The list is stored as a quoted string with comma-separated names.
|
||||||
"""
|
"""
|
||||||
Сохраняет список избранных игр в секцию [Favorites] конфигурационного файла.
|
cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser()
|
||||||
Список сохраняется как строка, заключённая в двойные кавычки, где имена игр разделены запятыми.
|
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Ошибка чтения конфига: %s", e)
|
|
||||||
if "Favorites" not in cp:
|
if "Favorites" not in cp:
|
||||||
cp["Favorites"] = {}
|
cp["Favorites"] = {}
|
||||||
fav_str = ", ".join(favorites)
|
fav_str = ", ".join(favorites)
|
||||||
@@ -323,34 +241,18 @@ def save_favorites(favorites):
|
|||||||
cp.write(configfile)
|
cp.write(configfile)
|
||||||
|
|
||||||
def read_rumble_config():
|
def read_rumble_config():
|
||||||
|
"""Reads the gamepad rumble setting from the [Gamepad] section.
|
||||||
|
Returns False if the parameter is missing.
|
||||||
"""
|
"""
|
||||||
Читает настройку виброотдачи геймпада из секции [Gamepad].
|
cp = read_config_safely(CONFIG_FILE)
|
||||||
Если параметр отсутствует, сохраняет и возвращает False по умолчанию.
|
if cp is None or not cp.has_section("Gamepad") or not cp.has_option("Gamepad", "rumble_enabled"):
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Ошибка чтения конфигурационного файла: %s", e)
|
|
||||||
save_rumble_config(False)
|
|
||||||
return False
|
|
||||||
if not cp.has_section("Gamepad") or not cp.has_option("Gamepad", "rumble_enabled"):
|
|
||||||
save_rumble_config(False)
|
save_rumble_config(False)
|
||||||
return False
|
return False
|
||||||
return cp.getboolean("Gamepad", "rumble_enabled", fallback=False)
|
return cp.getboolean("Gamepad", "rumble_enabled", fallback=False)
|
||||||
return False
|
|
||||||
|
|
||||||
def save_rumble_config(rumble_enabled):
|
def save_rumble_config(rumble_enabled):
|
||||||
"""
|
"""Saves the gamepad rumble setting to the [Gamepad] section."""
|
||||||
Сохраняет настройку виброотдачи геймпада в секцию [Gamepad].
|
cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser()
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e:
|
|
||||||
logger.error("Ошибка чтения конфигурационного файла: %s", e)
|
|
||||||
if "Gamepad" not in cp:
|
if "Gamepad" not in cp:
|
||||||
cp["Gamepad"] = {}
|
cp["Gamepad"] = {}
|
||||||
cp["Gamepad"]["rumble_enabled"] = str(rumble_enabled)
|
cp["Gamepad"]["rumble_enabled"] = str(rumble_enabled)
|
||||||
@@ -358,18 +260,11 @@ def save_rumble_config(rumble_enabled):
|
|||||||
cp.write(configfile)
|
cp.write(configfile)
|
||||||
|
|
||||||
def ensure_default_proxy_config():
|
def ensure_default_proxy_config():
|
||||||
|
"""Ensures the [Proxy] section exists in the configuration file.
|
||||||
|
Creates it with empty values if missing.
|
||||||
"""
|
"""
|
||||||
Проверяет наличие секции [Proxy] в конфигурационном файле.
|
cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser()
|
||||||
Если секция отсутствует, создаёт её с пустыми значениями.
|
if "Proxy" not in cp:
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Ошибка чтения конфигурационного файла: %s", e)
|
|
||||||
return
|
|
||||||
if not cp.has_section("Proxy"):
|
|
||||||
cp.add_section("Proxy")
|
cp.add_section("Proxy")
|
||||||
cp["Proxy"]["proxy_url"] = ""
|
cp["Proxy"]["proxy_url"] = ""
|
||||||
cp["Proxy"]["proxy_user"] = ""
|
cp["Proxy"]["proxy_user"] = ""
|
||||||
@@ -378,21 +273,15 @@ def ensure_default_proxy_config():
|
|||||||
cp.write(configfile)
|
cp.write(configfile)
|
||||||
|
|
||||||
def read_proxy_config():
|
def read_proxy_config():
|
||||||
"""
|
"""Reads proxy settings from the [Proxy] section.
|
||||||
Читает настройки прокси из секции [Proxy] конфигурационного файла.
|
Returns an empty dict if proxy_url is not set or empty.
|
||||||
Если параметр proxy_url не задан или пустой, возвращает пустой словарь.
|
|
||||||
"""
|
"""
|
||||||
ensure_default_proxy_config()
|
ensure_default_proxy_config()
|
||||||
cp = configparser.ConfigParser()
|
cp = read_config_safely(CONFIG_FILE)
|
||||||
try:
|
if cp is None:
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Ошибка чтения конфигурационного файла: %s", e)
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
proxy_url = cp.get("Proxy", "proxy_url", fallback="").strip()
|
proxy_url = cp.get("Proxy", "proxy_url", fallback="").strip()
|
||||||
if proxy_url:
|
if proxy_url:
|
||||||
# Если указаны логин и пароль, добавляем их к URL
|
|
||||||
proxy_user = cp.get("Proxy", "proxy_user", fallback="").strip()
|
proxy_user = cp.get("Proxy", "proxy_user", fallback="").strip()
|
||||||
proxy_password = cp.get("Proxy", "proxy_password", fallback="").strip()
|
proxy_password = cp.get("Proxy", "proxy_password", fallback="").strip()
|
||||||
if "://" in proxy_url and "@" not in proxy_url and proxy_user and proxy_password:
|
if "://" in proxy_url and "@" not in proxy_url and proxy_user and proxy_password:
|
||||||
@@ -402,16 +291,10 @@ def read_proxy_config():
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
def save_proxy_config(proxy_url="", proxy_user="", proxy_password=""):
|
def save_proxy_config(proxy_url="", proxy_user="", proxy_password=""):
|
||||||
|
"""Saves proxy settings to the [Proxy] section.
|
||||||
|
Creates the section if it does not exist.
|
||||||
"""
|
"""
|
||||||
Сохраняет настройки proxy в секцию [Proxy] конфигурационного файла.
|
cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser()
|
||||||
Если секция отсутствует, создаёт её.
|
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e:
|
|
||||||
logger.error("Ошибка чтения конфигурационного файла: %s", e)
|
|
||||||
if "Proxy" not in cp:
|
if "Proxy" not in cp:
|
||||||
cp["Proxy"] = {}
|
cp["Proxy"] = {}
|
||||||
cp["Proxy"]["proxy_url"] = proxy_url
|
cp["Proxy"]["proxy_url"] = proxy_url
|
||||||
@@ -421,34 +304,18 @@ def save_proxy_config(proxy_url="", proxy_user="", proxy_password=""):
|
|||||||
cp.write(configfile)
|
cp.write(configfile)
|
||||||
|
|
||||||
def read_fullscreen_config():
|
def read_fullscreen_config():
|
||||||
|
"""Reads the fullscreen mode setting from the [Display] section.
|
||||||
|
Returns False if the parameter is missing.
|
||||||
"""
|
"""
|
||||||
Читает настройку полноэкранного режима приложения из секции [Display].
|
cp = read_config_safely(CONFIG_FILE)
|
||||||
Если параметр отсутствует, сохраняет и возвращает False по умолчанию.
|
if cp is None or not cp.has_section("Display") or not cp.has_option("Display", "fullscreen"):
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Ошибка чтения конфигурационного файла: %s", e)
|
|
||||||
save_fullscreen_config(False)
|
|
||||||
return False
|
|
||||||
if not cp.has_section("Display") or not cp.has_option("Display", "fullscreen"):
|
|
||||||
save_fullscreen_config(False)
|
save_fullscreen_config(False)
|
||||||
return False
|
return False
|
||||||
return cp.getboolean("Display", "fullscreen", fallback=False)
|
return cp.getboolean("Display", "fullscreen", fallback=False)
|
||||||
return False
|
|
||||||
|
|
||||||
def save_fullscreen_config(fullscreen):
|
def save_fullscreen_config(fullscreen):
|
||||||
"""
|
"""Saves the fullscreen mode setting to the [Display] section."""
|
||||||
Сохраняет настройку полноэкранного режима приложения в секцию [Display].
|
cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser()
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e:
|
|
||||||
logger.error("Ошибка чтения конфигурационного файла: %s", e)
|
|
||||||
if "Display" not in cp:
|
if "Display" not in cp:
|
||||||
cp["Display"] = {}
|
cp["Display"] = {}
|
||||||
cp["Display"]["fullscreen"] = str(fullscreen)
|
cp["Display"]["fullscreen"] = str(fullscreen)
|
||||||
@@ -456,33 +323,19 @@ def save_fullscreen_config(fullscreen):
|
|||||||
cp.write(configfile)
|
cp.write(configfile)
|
||||||
|
|
||||||
def read_window_geometry() -> tuple[int, int]:
|
def read_window_geometry() -> tuple[int, int]:
|
||||||
|
"""Reads the window width and height from the [MainWindow] section.
|
||||||
|
Returns (0, 0) if the parameters are missing.
|
||||||
"""
|
"""
|
||||||
Читает ширину и высоту окна из секции [MainWindow] конфигурационного файла.
|
cp = read_config_safely(CONFIG_FILE)
|
||||||
Возвращает кортеж (width, height). Если данные отсутствуют, возвращает (0, 0).
|
if cp is None or not cp.has_section("MainWindow"):
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e:
|
|
||||||
logger.error("Ошибка в конфигурационном файле: %s", e)
|
|
||||||
return (0, 0)
|
return (0, 0)
|
||||||
if cp.has_section("MainWindow"):
|
|
||||||
width = cp.getint("MainWindow", "width", fallback=0)
|
width = cp.getint("MainWindow", "width", fallback=0)
|
||||||
height = cp.getint("MainWindow", "height", fallback=0)
|
height = cp.getint("MainWindow", "height", fallback=0)
|
||||||
return (width, height)
|
return (width, height)
|
||||||
return (0, 0)
|
|
||||||
|
|
||||||
def save_window_geometry(width: int, height: int):
|
def save_window_geometry(width: int, height: int):
|
||||||
"""
|
"""Saves the window width and height to the [MainWindow] section."""
|
||||||
Сохраняет ширину и высоту окна в секцию [MainWindow] конфигурационного файла.
|
cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser()
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e:
|
|
||||||
logger.error("Ошибка в конфигурационном файле: %s", e)
|
|
||||||
if "MainWindow" not in cp:
|
if "MainWindow" not in cp:
|
||||||
cp["MainWindow"] = {}
|
cp["MainWindow"] = {}
|
||||||
cp["MainWindow"]["width"] = str(width)
|
cp["MainWindow"]["width"] = str(width)
|
||||||
@@ -491,59 +344,40 @@ def save_window_geometry(width: int, height: int):
|
|||||||
cp.write(configfile)
|
cp.write(configfile)
|
||||||
|
|
||||||
def reset_config():
|
def reset_config():
|
||||||
"""
|
"""Resets the configuration file by deleting it.
|
||||||
Сбрасывает конфигурационный файл, удаляя его.
|
Subsequent reads will use default values.
|
||||||
После этого все настройки будут возвращены к значениям по умолчанию при следующем чтении.
|
|
||||||
"""
|
"""
|
||||||
if os.path.exists(CONFIG_FILE):
|
if os.path.exists(CONFIG_FILE):
|
||||||
try:
|
try:
|
||||||
os.remove(CONFIG_FILE)
|
os.remove(CONFIG_FILE)
|
||||||
logger.info("Конфигурационный файл %s удалён", CONFIG_FILE)
|
logger.info("Configuration file %s deleted", CONFIG_FILE)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Ошибка при удалении конфигурационного файла: %s", e)
|
logger.warning(f"Failed to delete configuration file: {e}")
|
||||||
|
|
||||||
def clear_cache():
|
def clear_cache():
|
||||||
"""
|
"""Clears the PortProtonQt cache by deleting the cache directory."""
|
||||||
Очищает кэш PortProtonQt, удаляя папку кэша.
|
|
||||||
"""
|
|
||||||
xdg_cache_home = os.getenv("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache"))
|
xdg_cache_home = os.getenv("XDG_CACHE_HOME", os.path.join(os.path.expanduser("~"), ".cache"))
|
||||||
cache_dir = os.path.join(xdg_cache_home, "PortProtonQt")
|
cache_dir = os.path.join(xdg_cache_home, "PortProtonQt")
|
||||||
if os.path.exists(cache_dir):
|
if os.path.exists(cache_dir):
|
||||||
try:
|
try:
|
||||||
shutil.rmtree(cache_dir)
|
shutil.rmtree(cache_dir)
|
||||||
logger.info("Кэш PortProtonQt удалён: %s", cache_dir)
|
logger.info("PortProtonQt cache deleted: %s", cache_dir)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error("Ошибка при удалении кэша: %s", e)
|
logger.warning(f"Failed to delete cache: {e}")
|
||||||
|
|
||||||
def read_auto_fullscreen_gamepad():
|
def read_auto_fullscreen_gamepad():
|
||||||
|
"""Reads the auto-fullscreen setting for gamepad from the [Display] section.
|
||||||
|
Returns False if the parameter is missing.
|
||||||
"""
|
"""
|
||||||
Читает настройку автоматического полноэкранного режима при подключении геймпада из секции [Display].
|
cp = read_config_safely(CONFIG_FILE)
|
||||||
Если параметр отсутствует, сохраняет и возвращает False по умолчанию.
|
if cp is None or not cp.has_section("Display") or not cp.has_option("Display", "auto_fullscreen_gamepad"):
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Ошибка чтения конфигурационного файла: %s", e)
|
|
||||||
save_auto_fullscreen_gamepad(False)
|
|
||||||
return False
|
|
||||||
if not cp.has_section("Display") or not cp.has_option("Display", "auto_fullscreen_gamepad"):
|
|
||||||
save_auto_fullscreen_gamepad(False)
|
save_auto_fullscreen_gamepad(False)
|
||||||
return False
|
return False
|
||||||
return cp.getboolean("Display", "auto_fullscreen_gamepad", fallback=False)
|
return cp.getboolean("Display", "auto_fullscreen_gamepad", fallback=False)
|
||||||
return False
|
|
||||||
|
|
||||||
def save_auto_fullscreen_gamepad(auto_fullscreen):
|
def save_auto_fullscreen_gamepad(auto_fullscreen):
|
||||||
"""
|
"""Saves the auto-fullscreen setting for gamepad to the [Display] section."""
|
||||||
Сохраняет настройку автоматического полноэкранного режима при подключении геймпада в секцию [Display].
|
cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser()
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except (configparser.DuplicateSectionError, configparser.DuplicateOptionError) as e:
|
|
||||||
logger.error("Ошибка чтения конфигурационного файла: %s", e)
|
|
||||||
if "Display" not in cp:
|
if "Display" not in cp:
|
||||||
cp["Display"] = {}
|
cp["Display"] = {}
|
||||||
cp["Display"]["auto_fullscreen_gamepad"] = str(auto_fullscreen)
|
cp["Display"]["auto_fullscreen_gamepad"] = str(auto_fullscreen)
|
||||||
@@ -551,36 +385,23 @@ def save_auto_fullscreen_gamepad(auto_fullscreen):
|
|||||||
cp.write(configfile)
|
cp.write(configfile)
|
||||||
|
|
||||||
def read_favorite_folders():
|
def read_favorite_folders():
|
||||||
|
"""Reads the list of favorite folders from the [FavoritesFolders] section.
|
||||||
|
The list is stored as a quoted string with comma-separated paths.
|
||||||
|
Returns an empty list if the section or parameter is missing.
|
||||||
"""
|
"""
|
||||||
Читает список избранных папок из секции [FavoritesFolders] конфигурационного файла.
|
cp = read_config_safely(CONFIG_FILE)
|
||||||
Список хранится как строка, заключённая в кавычки, с путями, разделёнными запятыми.
|
if cp is None or not cp.has_section("FavoritesFolders") or not cp.has_option("FavoritesFolders", "folders"):
|
||||||
Если секция или параметр отсутствуют, возвращает пустой список.
|
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Ошибка чтения конфига: %s", e)
|
|
||||||
return []
|
return []
|
||||||
if cp.has_section("FavoritesFolders") and cp.has_option("FavoritesFolders", "folders"):
|
|
||||||
favs = cp.get("FavoritesFolders", "folders", fallback="").strip()
|
favs = cp.get("FavoritesFolders", "folders", fallback="").strip()
|
||||||
if favs.startswith('"') and favs.endswith('"'):
|
if favs.startswith('"') and favs.endswith('"'):
|
||||||
favs = favs[1:-1]
|
favs = favs[1:-1]
|
||||||
return [os.path.normpath(s.strip()) for s in favs.split(",") if s.strip() and os.path.isdir(os.path.normpath(s.strip()))]
|
return [os.path.normpath(s.strip()) for s in favs.split(",") if s.strip() and os.path.isdir(os.path.normpath(s.strip()))]
|
||||||
return []
|
|
||||||
|
|
||||||
def save_favorite_folders(folders):
|
def save_favorite_folders(folders):
|
||||||
|
"""Saves the list of favorite folders to the [FavoritesFolders] section.
|
||||||
|
The list is stored as a quoted string with comma-separated paths.
|
||||||
"""
|
"""
|
||||||
Сохраняет список избранных папок в секцию [FavoritesFolders] конфигурационного файла.
|
cp = read_config_safely(CONFIG_FILE) or configparser.ConfigParser()
|
||||||
Список сохраняется как строка, заключённая в двойные кавычки, где пути разделены запятыми.
|
|
||||||
"""
|
|
||||||
cp = configparser.ConfigParser()
|
|
||||||
if os.path.exists(CONFIG_FILE):
|
|
||||||
try:
|
|
||||||
cp.read(CONFIG_FILE, encoding="utf-8")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error("Ошибка чтения конфига: %s", e)
|
|
||||||
if "FavoritesFolders" not in cp:
|
if "FavoritesFolders" not in cp:
|
||||||
cp["FavoritesFolders"] = {}
|
cp["FavoritesFolders"] = {}
|
||||||
fav_str = ", ".join([os.path.normpath(folder) for folder in folders])
|
fav_str = ", ".join([os.path.normpath(folder) for folder in folders])
|
||||||
|
@@ -4,7 +4,6 @@ import glob
|
|||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
import logging
|
|
||||||
import orjson
|
import orjson
|
||||||
import psutil
|
import psutil
|
||||||
import signal
|
import signal
|
||||||
@@ -17,8 +16,9 @@ from portprotonqt.steam_api import is_game_in_steam, add_to_steam, remove_from_s
|
|||||||
from portprotonqt.egs_api import add_egs_to_steam, get_egs_executable, remove_egs_from_steam
|
from portprotonqt.egs_api import add_egs_to_steam, get_egs_executable, remove_egs_from_steam
|
||||||
from portprotonqt.dialogs import AddGameDialog, FileExplorer, generate_thumbnail
|
from portprotonqt.dialogs import AddGameDialog, FileExplorer, generate_thumbnail
|
||||||
from portprotonqt.theme_manager import ThemeManager
|
from portprotonqt.theme_manager import ThemeManager
|
||||||
|
from portprotonqt.logger import get_logger
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
class ContextMenuSignals(QObject):
|
class ContextMenuSignals(QObject):
|
||||||
"""Signals for thread-safe UI updates from worker threads."""
|
"""Signals for thread-safe UI updates from worker threads."""
|
||||||
|
Reference in New Issue
Block a user