@ -261,19 +261,25 @@ class MainWindow(QMainWindow):
self . update_status_message . emit
)
elif display_filter == " favorites " :
def on_all_games ( portproton_games , steam_games ) :
games = [ game for game in portproton_games + steam_games if game [ 0 ] in favorites ]
def on_all_games ( portproton_games , steam_games , epic_games ):
games = [ game for game in portproton_games + steam_games + epic_games if game [ 0 ] in favorites ]
self . games_loaded . emit ( games )
self . _load_portproton_games_async (
lambda pg : self . _load_steam_games_async (
lambda sg : on_all_games ( pg , sg )
lambda sg : load_egs_games_async (
self . legendary_path ,
lambda eg : on_all_games ( pg , sg , eg ) ,
self . downloader ,
self . update_progress . emit ,
self . update_status_message . emit
)
)
)
else :
def on_all_games ( portproton_games , steam_games ) :
def on_all_games ( portproton_games , steam_games , epic_games ):
seen = set ( )
games = [ ]
for game in portproton_games + steam_games :
for game in portproton_games + steam_games + epic_games :
# Уникальный ключ: имя + exec_line
key = ( game [ 0 ] , game [ 4 ] )
if key not in seen :
@ -282,7 +288,13 @@ class MainWindow(QMainWindow):
self . games_loaded . emit ( games )
self . _load_portproton_games_async (
lambda pg : self . _load_steam_games_async (
lambda sg : on_all_games ( pg , sg )
lambda sg : load_egs_games_async (
self . legendary_path ,
lambda eg : on_all_games ( pg , sg , eg ) ,
self . downloader ,
self . update_progress . emit ,
self . update_status_message . emit
)
)
)
return [ ]
@ -927,7 +939,7 @@ class MainWindow(QMainWindow):
# 3. Games display_filter
self . filter_keys = [ " all " , " steam " , " portproton " , " favorites " , " epic " ]
self . filter_labels = [ _ ( " all " ) , " steam " , " portproton " , _ ( " favorites " ) ]
self . filter_labels = [ _ ( " all " ) , " steam " , " portproton " , _ ( " favorites " ) , " epic games store " ]
self . gamesDisplayCombo = QComboBox ( )
self . gamesDisplayCombo . addItems ( self . filter_labels )
self . gamesDisplayCombo . setStyleSheet ( self . theme . SETTINGS_COMBO_STYLE )
@ -1009,6 +1021,37 @@ class MainWindow(QMainWindow):
self . gamepadRumbleCheckBox . setChecked ( current_rumble_state )
formLayout . addRow ( self . gamepadRumbleTitle , self . gamepadRumbleCheckBox )
# 8. Legendary Authentication
self . legendaryAuthButton = AutoSizeButton (
_ ( " Open Legendary Login " ) ,
icon = self . theme_manager . get_icon ( " login " )
)
self . legendaryAuthButton . setStyleSheet ( self . theme . ACTION_BUTTON_STYLE )
self . legendaryAuthButton . setFocusPolicy ( Qt . FocusPolicy . StrongFocus )
self . legendaryAuthButton . clicked . connect ( self . openLegendaryLogin )
self . legendaryAuthTitle = QLabel ( _ ( " Legendary Authentication: " ) )
self . legendaryAuthTitle . setStyleSheet ( self . theme . PARAMS_TITLE_STYLE )
self . legendaryAuthTitle . setFocusPolicy ( Qt . FocusPolicy . NoFocus )
formLayout . addRow ( self . legendaryAuthTitle , self . legendaryAuthButton )
self . legendaryCodeEdit = QLineEdit ( )
self . legendaryCodeEdit . setPlaceholderText ( _ ( " Enter Legendary Authorization Code " ) )
self . legendaryCodeEdit . setStyleSheet ( self . theme . PROXY_INPUT_STYLE )
self . legendaryCodeEdit . setFocusPolicy ( Qt . FocusPolicy . StrongFocus )
self . legendaryCodeTitle = QLabel ( _ ( " Authorization Code: " ) )
self . legendaryCodeTitle . setStyleSheet ( self . theme . PARAMS_TITLE_STYLE )
self . legendaryCodeTitle . setFocusPolicy ( Qt . FocusPolicy . NoFocus )
formLayout . addRow ( self . legendaryCodeTitle , self . legendaryCodeEdit )
self . submitCodeButton = AutoSizeButton (
_ ( " Submit Code " ) ,
icon = self . theme_manager . get_icon ( " save " )
)
self . submitCodeButton . setStyleSheet ( self . theme . ACTION_BUTTON_STYLE )
self . submitCodeButton . setFocusPolicy ( Qt . FocusPolicy . StrongFocus )
self . submitCodeButton . clicked . connect ( self . submitLegendaryCode )
formLayout . addRow ( QLabel ( " " ) , self . submitCodeButton )
layout . addLayout ( formLayout )
# Кнопки
@ -1059,6 +1102,37 @@ class MainWindow(QMainWindow):
logger . error ( f " Failed to open Legendary login page: { e } " )
self . statusBar ( ) . showMessage ( _ ( " Failed to open Legendary login page " ) , 3000 )
def submitLegendaryCode ( self ) :
""" Submits the Legendary authorization code using the legendary CLI. """
auth_code = self . legendaryCodeEdit . text ( ) . strip ( )
if not auth_code :
QMessageBox . warning ( self , _ ( " Error " ) , _ ( " Please enter an authorization code " ) )
return
try :
# Execute legendary auth command
result = subprocess . run (
[ self . legendary_path , " auth " , " --code " , auth_code ] ,
capture_output = True ,
text = True ,
check = True
)
logger . info ( " Legendary authentication successful: %s " , result . stdout )
self . statusBar ( ) . showMessage ( _ ( " Successfully authenticated with Legendary " ) , 3000 )
self . legendaryCodeEdit . clear ( )
# Reload Epic Games Store games after successful authentication
self . games = self . loadGames ( )
self . updateGameGrid ( )
except subprocess . CalledProcessError as e :
logger . error ( " Legendary authentication failed: %s " , e . stderr )
self . statusBar ( ) . showMessage ( _ ( " Legendary authentication failed: {0} " ) . format ( e . stderr ) , 5000 )
except FileNotFoundError :
logger . error ( " Legendary executable not found at %s " , self . legendary_path )
self . statusBar ( ) . showMessage ( _ ( " Legendary executable not found " ) , 5000 )
except Exception as e :
logger . error ( " Unexpected error during Legendary authentication: %s " , str ( e ) )
self . statusBar ( ) . showMessage ( _ ( " Unexpected error during authentication " ) , 5000 )
def resetSettings ( self ) :
""" Сбрасывает настройки и перезапускает приложение. """
reply = QMessageBox . question (
@ -1454,7 +1528,7 @@ class MainWindow(QMainWindow):
steamLabel . clicked . connect ( lambda : QDesktopServices . openUrl ( QUrl ( f " https://steamcommunity.com/app/ { appid } " ) ) )
# Epic Games Store бейдж
egs_icon = self . theme_manager . get_icon ( " steam " )
egs_icon = self . theme_manager . get_icon ( " epic_games " )
egsLabel = ClickableLabel (
" Epic Games " ,
icon = egs_icon ,
@ -1749,11 +1823,67 @@ class MainWindow(QMainWindow):
self . target_exe = None
def toggleGame ( self , exec_line , button = None ) :
# Обработка Steam-игр
if exec_line . startswith ( " steam:// " ) :
url = QUrl ( exec_line )
QDesktopServices . openUrl ( url )
return
# Обработка EGS-игр
if exec_line . startswith ( " legendary:launch: " ) :
# Извлекаем app_name из exec_line
app_name = exec_line . split ( " legendary:launch: " ) [ 1 ]
legendary_path = self . legendary_path # Путь к legendary
# Формируем переменные окружения
env_vars = os . environ . copy ( )
env_vars [ ' START_FROM_STEAM ' ] = ' 1 '
env_vars [ ' LEGENDARY_CONFIG_PATH ' ] = self . legendary_config_path
wrapper = " flatpak run ru.linux_gaming.PortProton "
if self . portproton_location is not None and " .var " not in self . portproton_location :
start_sh = os . path . join ( self . portproton_location , " data " , " scripts " , " start.sh " )
wrapper = start_sh
# Формируем команду
cmd = [
legendary_path , " launch " , app_name , " --no-wine " , " --wrapper " , wrapper
]
current_exe = os . path . basename ( legendary_path )
if self . game_processes and self . target_exe is not None and self . target_exe != current_exe :
QMessageBox . warning ( self , _ ( " Error " ) , _ ( " Cannot launch game while another game is running " ) )
return
# Обновляем кнопку
update_button = button if button is not None else self . current_play_button
self . current_running_button = update_button
self . target_exe = current_exe
exe_name = app_name # Используем app_name для EGS-игр
# Запускаем процесс
try :
process = subprocess . Popen ( cmd , env = env_vars , shell = False , preexec_fn = os . setsid )
self . game_processes . append ( process )
save_last_launch ( exe_name , datetime . now ( ) )
if update_button :
update_button . setText ( _ ( " Launching " ) )
icon = self . theme_manager . get_icon ( " stop " )
if isinstance ( icon , str ) :
icon = QIcon ( icon )
elif icon is None :
icon = QIcon ( )
update_button . setIcon ( icon )
self . checkProcessTimer = QTimer ( self )
self . checkProcessTimer . timeout . connect ( self . checkTargetExe )
self . checkProcessTimer . start ( 500 )
except Exception as e :
logger . error ( f " Failed to launch EGS game { app_name } : { e } " )
QMessageBox . warning ( self , _ ( " Error " ) , _ ( " Failed to launch game: {0} " ) . format ( str ( e ) ) )
return
# Обработка PortProton-игр
entry_exec_split = shlex . split ( exec_line )
if entry_exec_split [ 0 ] == " env " :
if len ( entry_exec_split ) < 3 :
@ -1767,18 +1897,20 @@ class MainWindow(QMainWindow):
file_to_check = entry_exec_split [ 3 ]
else :
file_to_check = entry_exec_split [ 0 ]
if not os . path . exists ( file_to_check ) :
QMessageBox . warning ( self , _ ( " Error " ) , _ ( " File not found: {0} " ) . format ( file_to_check ) )
return
current_exe = os . path . basename ( file_to_check )
current_exe = os . path . basename ( file_to_check )
if self . game_processes and self . target_exe is not None and self . target_exe != current_exe :
QMessageBox . warning ( self , _ ( " Error " ) , _ ( " Cannot launch game while another game is running " ) )
return
# Обновляем кнопку
update_button = button if button is not None else self . current_play_button
# Если игра уже запущена для этого exe – останавливаем её по нажатию кнопки
# Если игра уже запущена для этого exe – останавливаем её
if self . game_processes and self . target_exe == current_exe :
for proc in self . game_processes :
try :
@ -1812,17 +1944,16 @@ class MainWindow(QMainWindow):
self . current_running_button = None
self . target_exe = None
self . _gameLaunched = False
#self._uninhibit_screensaver()
else :
# Сохраняем ссылку на кнопку для с б р о с а после завершения игры
self . current_running_button = update_button
self . target_exe = current_exe
exe_name = os . path . splitext ( current_exe ) [ 0 ]
env_vars = os . environ . copy ( )
if entry_exec_split [ 0 ] == " env " and len ( entry_exec_split ) > 1 and ' data/scripts/start.sh ' in entry_exec_split [ 1 ] :
env_vars [ ' START_FROM_STEAM ' ] = ' 1 '
elif entry_exec_split [ 0 ] == " flatpak " :
env_vars [ ' START_FROM_STEAM ' ] = ' 1 '
return
# Запускаем игру
self . current_running_button = update_button
self . target_exe = current_exe
exe_name = os . path . splitext ( current_exe ) [ 0 ]
env_vars = os . environ . copy ( )
env_vars [ ' START_FROM_STEAM ' ] = ' 1 '
try :
process = subprocess . Popen ( entry_exec_split , env = env_vars , shell = False , preexec_fn = os . setsid )
self . game_processes . append ( process )
save_last_launch ( exe_name , datetime . now ( ) )
@ -1838,6 +1969,9 @@ class MainWindow(QMainWindow):
self . checkProcessTimer = QTimer ( self )
self . checkProcessTimer . timeout . connect ( self . checkTargetExe )
self . checkProcessTimer . start ( 500 )
except Exception as e :
logger . error ( f " Failed to launch game { exe_name } : { e } " )
QMessageBox . warning ( self , _ ( " Error " ) , _ ( " Failed to launch game: {0} " ) . format ( str ( e ) ) )
def closeEvent ( self , event ) :
for proc in self . game_processes :