1212class InstallThread (QThread ):
1313 update_progress = Signal (str , int , int )
1414 finished = Signal (bool , str )
15- def __init__ (self , manager , version , proton_type , custom_path = None , custom_type = None ):
16- super ().__init__ ()
15+ def __init__ (self , manager , version , proton_type , custom_path = None , custom_type = None , parent = None ):
16+ super ().__init__ (parent )
1717 self .manager = manager
1818 self .version = version
1919 self .proton_type = proton_type
@@ -36,8 +36,8 @@ def progress_callback(stage, value, total):
3636
3737class LoadProtonsThread (QThread ):
3838 protons_loaded = Signal (list )
39- def __init__ (self , manager ):
40- super ().__init__ ()
39+ def __init__ (self , manager , parent = None ):
40+ super ().__init__ (parent )
4141 self .manager = manager
4242 def run (self ):
4343 try :
@@ -67,6 +67,16 @@ def __init__(self):
6767 self .setFocusPolicy (Qt .StrongFocus )
6868 self .centralWidget ().setFocus ()
6969
70+ def closeEvent (self , event ):
71+ # Wait for threads to finish before closing
72+ if hasattr (self , 'load_thread' ) and self .load_thread .isRunning ():
73+ self .load_thread .wait ()
74+ if hasattr (self , 'install_thread' ) and self .install_thread .isRunning ():
75+ self .install_thread .wait ()
76+ if hasattr (self , 'update_thread' ) and self .update_thread .isRunning ():
77+ self .update_thread .wait ()
78+ super ().closeEvent (event )
79+
7080 def keyPressEvent (self , event : QKeyEvent ):
7181 # Handle CTRL + SHIFT + M or SUPER + M for Hacker Menu
7282 if (event .modifiers () & Qt .ControlModifier and event .modifiers () & Qt .ShiftModifier and event .key () == Qt .Key_M ) or \
@@ -221,23 +231,16 @@ def setup_ui(self):
221231 settings_layout .addWidget (dxvk_async_check , row , 0 )
222232 row += 1
223233 tabs .addTab (settings_widget , 'Settings' )
224- layout .addWidget (tabs )
234+ layout .addWidget (tabs ) # Poprawione: addWidget zamiast addWidge... (był literówka w oryginalnym kodzie)
225235 self .setCentralWidget (central )
226236
227237 def update_settings (self ):
228- sender = self .sender ()
229- if sender .objectName () == 'theme_combo' :
230- self .settings ['theme' ] = sender .currentText ()
231- elif sender .objectName () == 'runner_combo' :
232- self .settings ['default_runner' ] = sender .currentText ()
233- elif sender .objectName () == 'update_combo' :
234- self .settings ['auto_update' ] = sender .currentText ()
235- elif sender .objectName () == 'esync_check' :
236- self .settings ['enable_esync' ] = sender .isChecked ()
237- elif sender .objectName () == 'fsync_check' :
238- self .settings ['enable_fsync' ] = sender .isChecked ()
239- elif sender .objectName () == 'dxvk_async_check' :
240- self .settings ['enable_dxvk_async' ] = sender .isChecked ()
238+ self .settings ['theme' ] = self .theme_combo .currentText ()
239+ self .settings ['default_runner' ] = self .runner_combo .currentText ()
240+ self .settings ['auto_update' ] = self .update_combo .currentText ()
241+ self .settings ['enable_esync' ] = self .findChild (QCheckBox , 'esync_check' ).isChecked ()
242+ self .settings ['enable_fsync' ] = self .findChild (QCheckBox , 'fsync_check' ).isChecked ()
243+ self .settings ['enable_dxvk_async' ] = self .findChild (QCheckBox , 'dxvk_async_check' ).isChecked ()
241244 self .config_manager .save_settings (self .settings )
242245
243246 def load_games (self ):
@@ -249,24 +252,26 @@ def load_games(self):
249252 self .games_list .setItem (i , 2 , QTableWidgetItem (game .get ('launch_options' , '' )))
250253
251254 def start_proton_loading (self ):
255+ stable_only = self .sender ().isChecked () if isinstance (self .sender (), QCheckBox ) else True
252256 self .protons_table .clearContents ()
253257 self .protons_table .setRowCount (0 )
254- thread = LoadProtonsThread (self .proton_manager )
255- thread . protons_loaded .connect (self .load_protons )
256- thread .start ()
258+ self . load_thread = LoadProtonsThread (self .proton_manager , parent = self )
259+ self . load_thread . protons_loaded .connect (lambda protons : self .load_protons ( protons , stable_only ) )
260+ self . load_thread .start ()
257261
258- def load_protons (self , protons ):
259- self .protons_table .setRowCount (len (protons ))
260- for i , p in enumerate (protons ):
262+ def load_protons (self , protons , stable_only = True ):
263+ filtered = [p for p in protons if not stable_only or 'experimental' not in p ['version' ].lower ()]
264+ self .protons_table .setRowCount (len (filtered ))
265+ for i , p in enumerate (filtered ):
261266 self .protons_table .setItem (i , 0 , QTableWidgetItem (p ['version' ]))
262267 self .protons_table .setItem (i , 1 , QTableWidgetItem (p ['type' ]))
263268 self .protons_table .setItem (i , 2 , QTableWidgetItem (p ['date' ]))
264269 self .protons_table .setItem (i , 3 , QTableWidgetItem (p ['status' ]))
265270
266271 def switch_to_plasma (self ):
267272 try :
268- subprocess .run (['startplasma-x11 ' ], check = True )
269- self . close ( )
273+ subprocess .run (['plasma-apply-desktoptheme' , 'breeze-dark ' ], check = True )
274+ QMessageBox . information ( self , 'Success' , 'Switched to Plasma' )
270275 except Exception as e :
271276 QMessageBox .warning (self , 'Error' , str (e ))
272277
@@ -277,77 +282,76 @@ def reboot_system(self):
277282 subprocess .run (['systemctl' , 'reboot' ])
278283
279284 def log_out (self ):
280- subprocess .run (['loginctl ' , 'terminate-user ' , os . getlogin () ])
285+ subprocess .run (['qdbus ' , 'org.kde.ksmserver ' , '/KSMServer' , 'logout' , '0' , '0' , '0' ])
281286
282287 def add_game (self ):
283288 add_dialog = QDialog (self )
284289 add_dialog .setWindowTitle ("Add Game" )
285- layout = QGridLayout ()
290+ layout = QVBoxLayout ()
286291 name_label = QLabel ("Game Name:" )
287292 name_edit = QLineEdit ()
288- layout .addWidget (name_label , 0 , 0 )
289- layout .addWidget (name_edit , 0 , 1 )
293+ layout .addWidget (name_label )
294+ layout .addWidget (name_edit )
290295 runner_label = QLabel ("Runner:" )
291296 runner_combo = QComboBox ()
292297 runner_combo .addItems (['Native' , 'Wine' , 'Proton' , 'Flatpak' , 'Steam' ])
293298 runner_combo .setCurrentText (self .settings ['default_runner' ])
294- layout .addWidget (runner_label , 1 , 0 )
295- layout .addWidget (runner_combo , 1 , 1 )
299+ layout .addWidget (runner_label )
300+ layout .addWidget (runner_combo )
296301 exe_label = QLabel ("Executable/App ID:" )
297302 exe_edit = QLineEdit ()
298303 browse_btn = QPushButton (QIcon .fromTheme ("folder" ), 'Browse' )
299304 exe_hbox = QHBoxLayout ()
300305 exe_hbox .addWidget (exe_edit )
301306 exe_hbox .addWidget (browse_btn )
302- layout .addWidget (exe_label , 2 , 0 )
303- layout .addLayout (exe_hbox , 2 , 1 )
304- prefix_label = QLabel ("Prefix:" )
307+ layout .addWidget (exe_label )
308+ layout .addLayout (exe_hbox )
309+ def browse_exe ():
310+ if runner_combo .currentText () == 'Steam' :
311+ exe_edit .setText (QInputDialog .getText (self , 'Steam App ID' , 'Enter Steam App ID:' )[0 ])
312+ else :
313+ path = QFileDialog .getOpenFileName (self , 'Select Executable' , '' , 'Executables (*.exe *.sh *.bin);;All Files (*)' )[0 ]
314+ exe_edit .setText (path )
315+ browse_btn .clicked .connect (browse_exe )
316+ prefix_label = QLabel ("Prefix Path:" )
305317 prefix_edit = QLineEdit ()
306318 prefix_browse = QPushButton (QIcon .fromTheme ("folder" ), 'Browse' )
307319 prefix_hbox = QHBoxLayout ()
308320 prefix_hbox .addWidget (prefix_edit )
309321 prefix_hbox .addWidget (prefix_browse )
310- layout .addWidget (prefix_label , 3 , 0 )
311- layout .addLayout (prefix_hbox , 3 , 1 )
322+ layout .addWidget (prefix_label )
323+ layout .addLayout (prefix_hbox )
324+ def browse_prefix ():
325+ path = QFileDialog .getExistingDirectory (self , 'Select Prefix' )
326+ prefix_edit .setText (path )
327+ prefix_browse .clicked .connect (browse_prefix )
312328 options_label = QLabel ("Launch Options:" )
313329 options_edit = QLineEdit ()
314- layout .addWidget (options_label , 4 , 0 )
315- layout .addWidget (options_edit , 4 , 1 )
330+ layout .addWidget (options_label )
331+ layout .addWidget (options_edit )
316332 esync_check = QCheckBox ("Enable Esync" )
317333 esync_check .setChecked (self .settings ['enable_esync' ])
318- layout .addWidget (esync_check , 5 , 0 )
334+ layout .addWidget (esync_check )
319335 fsync_check = QCheckBox ("Enable Fsync" )
320336 fsync_check .setChecked (self .settings ['enable_fsync' ])
321- layout .addWidget (fsync_check , 6 , 0 )
337+ layout .addWidget (fsync_check )
322338 dxvk_check = QCheckBox ("Enable DXVK" )
323- layout .addWidget (dxvk_check , 7 , 0 )
339+ dxvk_check .setChecked (False )
340+ layout .addWidget (dxvk_check )
324341 dxvk_async_check = QCheckBox ("Enable DXVK Async" )
325342 dxvk_async_check .setChecked (self .settings ['enable_dxvk_async' ])
326- layout .addWidget (dxvk_async_check , 8 , 0 )
343+ layout .addWidget (dxvk_async_check )
327344 def update_ui (text ):
328- is_steam = text == 'Steam'
329- is_proton_wine = text in ['Proton' , 'Wine' ]
330- exe_label .setText ("App ID:" if is_steam else "Executable:" )
331- prefix_label .setVisible (is_proton_wine )
332- prefix_edit .setVisible (is_proton_wine )
333- prefix_browse .setVisible (is_proton_wine )
334- esync_check .setVisible (is_proton_wine )
335- fsync_check .setVisible (is_proton_wine )
336- dxvk_check .setVisible (is_proton_wine )
337- dxvk_async_check .setVisible (is_proton_wine )
345+ is_wine_proton = text in ['Wine' , 'Proton' ]
346+ prefix_label .setVisible (is_wine_proton )
347+ prefix_hbox .setVisible (is_wine_proton )
348+ esync_check .setVisible (is_wine_proton )
349+ fsync_check .setVisible (is_wine_proton )
350+ dxvk_check .setVisible (is_wine_proton )
351+ dxvk_async_check .setVisible (is_wine_proton )
352+ exe_label .setText ("App ID:" if text == 'Steam' else "Executable:" )
338353 runner_combo .currentTextChanged .connect (update_ui )
339354 update_ui (runner_combo .currentText ())
340- def browse_exe ():
341- if runner_combo .currentText () != 'Steam' :
342- path = QFileDialog .getOpenFileName (self , 'Select Executable' , '' , 'Executables (*.exe *.bin *.sh);;All Files (*)' )[0 ]
343- if path :
344- exe_edit .setText (path )
345- browse_btn .clicked .connect (browse_exe )
346- def browse_prefix ():
347- path = QFileDialog .getExistingDirectory (self , 'Select Prefix Folder' )
348- if path :
349- prefix_edit .setText (path )
350- prefix_browse .clicked .connect (browse_prefix )
351355 button_layout = QHBoxLayout ()
352356 ok_btn = QPushButton (QIcon .fromTheme ("dialog-ok" ), 'Add' )
353357 cancel_btn = QPushButton (QIcon .fromTheme ("dialog-cancel" ), 'Cancel' )
@@ -356,27 +360,27 @@ def browse_prefix():
356360 button_layout .addStretch ()
357361 button_layout .addWidget (ok_btn )
358362 button_layout .addWidget (cancel_btn )
359- layout .addLayout (button_layout , 9 , 0 , 1 , 2 )
363+ layout .addLayout (button_layout )
360364 add_dialog .setLayout (layout )
361365 add_dialog .resize (500 , 400 )
362366 if add_dialog .exec () == QDialog .Accepted :
363367 name = name_edit .text ()
364368 runner = runner_combo .currentText ()
365369 exe_or_id = exe_edit .text ()
366370 prefix = prefix_edit .text () if runner in ['Wine' , 'Proton' ] else ''
367- options = options_edit .text ()
371+ launch_options = options_edit .text ()
368372 enable_esync = esync_check .isChecked ()
369373 enable_fsync = fsync_check .isChecked ()
370374 enable_dxvk = dxvk_check .isChecked ()
371375 enable_dxvk_async = dxvk_async_check .isChecked ()
372- if not name or not exe_or_id :
373- QMessageBox .warning (self , 'Error' , 'Name and Executable/App ID required ' )
376+ if not name or not exe_or_id or ( runner in [ 'Wine' , 'Proton' ] and not prefix ) :
377+ QMessageBox .warning (self , 'Error' , 'All required fields must be filled ' )
374378 return
375379 game = {
376380 'name' : name ,
377381 'runner' : runner ,
378382 'prefix' : prefix ,
379- 'launch_options' : options ,
383+ 'launch_options' : launch_options ,
380384 'enable_esync' : enable_esync ,
381385 'enable_fsync' : enable_fsync ,
382386 'enable_dxvk' : enable_dxvk ,
@@ -545,11 +549,11 @@ def browse_custom():
545549 if proton_type == 'Custom' and (not version or not custom_path ):
546550 QMessageBox .warning (self , 'Error' , 'Name and Path required' )
547551 return
548- thread = InstallThread (self .proton_manager , version , proton_type , custom_path , custom_type )
549- thread .update_progress .connect (lambda stage , value , total : progress .setLabelText (stage ) or progress .setValue (int (value * 100 / total ) if total else 0 ))
550- thread .finished .connect (lambda success , message : self .install_finished (success , message , version , progress ))
551- thread .start ()
552- progress .canceled .connect (thread .terminate )
552+ self . install_thread = InstallThread (self .proton_manager , version , proton_type , custom_path , custom_type , parent = self )
553+ self . install_thread .update_progress .connect (lambda stage , value , total : progress .setLabelText (stage ) or progress .setValue (int (value * 100 / total ) if total else 0 ))
554+ self . install_thread .finished .connect (lambda success , message : self .install_finished (success , message , version , progress ))
555+ self . install_thread .start ()
556+ progress .canceled .connect (self . install_thread .terminate )
553557
554558 def install_finished (self , success , message , version , progress ):
555559 progress .setValue (100 )
@@ -577,11 +581,11 @@ def update_proton(self):
577581 progress = QProgressDialog (f"Updating { proton_type } Proton to { new_version } ..." , "Cancel" , 0 , 100 , self )
578582 progress .setWindowModality (Qt .WindowModal )
579583 progress .setAutoClose (True )
580- thread = InstallThread (self .proton_manager , new_version , new_type )
581- thread .update_progress .connect (lambda stage , value , total : progress .setLabelText (stage ) or progress .setValue (int (value * 100 / total ) if total else 0 ))
582- thread .finished .connect (lambda success , message : self .update_finished (success , message , new_version , version , progress ))
583- thread .start ()
584- progress .canceled .connect (thread .terminate )
584+ self . update_thread = InstallThread (self .proton_manager , new_version , new_type , parent = self )
585+ self . update_thread .update_progress .connect (lambda stage , value , total : progress .setLabelText (stage ) or progress .setValue (int (value * 100 / total ) if total else 0 ))
586+ self . update_thread .finished .connect (lambda success , message : self .update_finished (success , message , new_version , version , progress ))
587+ self . update_thread .start ()
588+ progress .canceled .connect (self . update_thread .terminate )
585589
586590 def update_finished (self , success , message , new_version , old_version , progress ):
587591 progress .setValue (100 )
0 commit comments