@@ -220,236 +220,172 @@ def setup_ui(self):
220220 dxvk_async_check .stateChanged .connect (self .update_settings )
221221 settings_layout .addWidget (dxvk_async_check , row , 0 )
222222 row += 1
223- prefix_label = QLabel ("Prefixes Location:" )
224- prefix_value = QLabel (self .config_manager .prefixes_dir )
225- prefix_value .setStyleSheet ("color: #888888;" )
226- settings_layout .addWidget (prefix_label , row , 0 )
227- settings_layout .addWidget (prefix_value , row , 1 )
228- row += 1
229- protons_label = QLabel ("Protons Location:" )
230- protons_value = QLabel (self .config_manager .protons_dir )
231- protons_value .setStyleSheet ("color: #888888;" )
232- settings_layout .addWidget (protons_label , row , 0 )
233- settings_layout .addWidget (protons_value , row , 1 )
234- settings_layout .setRowStretch (row , 1 )
235223 tabs .addTab (settings_widget , 'Settings' )
236- # About tab
237- about_widget = QWidget ()
238- about_layout = QVBoxLayout (about_widget )
239- about_text = QTextEdit ()
240- about_text .setReadOnly (True )
241- about_text .setText ("Hacker Launcher v1.0\n GitHub: https://github.com/HackerOS-Linux-System/Hacker-Mode\n A launcher for running games with Proton/Wine easily." )
242- about_layout .addWidget (about_text )
243- tabs .addTab (about_widget , 'About' )
244224 layout .addWidget (tabs )
245225 self .setCentralWidget (central )
246226
247- def switch_to_plasma (self ):
248- try :
249- subprocess .run (['/usr/share/HackerOS/Scripts/Bin/revert_to_plasma.sh' ], check = True )
250- except Exception as e :
251- QMessageBox .warning (self , 'Error' , f'Failed to switch to Plasma: { str (e )} ' )
252-
253- def shutdown_system (self ):
254- try :
255- subprocess .run (['shutdown' , '0' ], check = True )
256- except Exception as e :
257- QMessageBox .warning (self , 'Error' , f'Failed to shutdown: { str (e )} ' )
258-
259- def reboot_system (self ):
260- try :
261- subprocess .run (['reboot' ], check = True )
262- except Exception as e :
263- QMessageBox .warning (self , 'Error' , f'Failed to reboot: { str (e )} ' )
264-
265- def log_out (self ):
266- try :
267- subprocess .run (['qdbus' , 'org.kde.ksmserver' , '/KSMServer' , 'org.kde.KSMServerInterface.logout' , '0' , '0' , '0' ], check = True )
268- except Exception as e :
269- QMessageBox .warning (self , 'Error' , f'Failed to log out: { str (e )} ' )
270-
271- def start_proton_loading (self ):
272- self .protons_table .setRowCount (0 )
273- self .proton_thread = LoadProtonsThread (self .proton_manager )
274- self .proton_thread .protons_loaded .connect (self .load_protons )
275- self .proton_thread .start ()
276-
277227 def update_settings (self ):
278228 sender = self .sender ()
279- obj_name = sender .objectName ()
280- if obj_name == 'theme_combo' :
229+ if sender .objectName () == 'theme_combo' :
281230 self .settings ['theme' ] = sender .currentText ()
282- elif obj_name == 'runner_combo' :
231+ elif sender . objectName () == 'runner_combo' :
283232 self .settings ['default_runner' ] = sender .currentText ()
284- elif obj_name == 'update_combo' :
233+ elif sender . objectName () == 'update_combo' :
285234 self .settings ['auto_update' ] = sender .currentText ()
286- elif obj_name == 'esync_check' :
235+ elif sender . objectName () == 'esync_check' :
287236 self .settings ['enable_esync' ] = sender .isChecked ()
288- elif obj_name == 'fsync_check' :
237+ elif sender . objectName () == 'fsync_check' :
289238 self .settings ['enable_fsync' ] = sender .isChecked ()
290- elif obj_name == 'dxvk_async_check' :
239+ elif sender . objectName () == 'dxvk_async_check' :
291240 self .settings ['enable_dxvk_async' ] = sender .isChecked ()
292241 self .config_manager .save_settings (self .settings )
293242
294243 def load_games (self ):
295- self .games_list .setRowCount (0 )
296244 self .games = self .config_manager .load_games ()
297245 self .games_list .setRowCount (len (self .games ))
298246 for i , game in enumerate (self .games ):
299247 self .games_list .setItem (i , 0 , QTableWidgetItem (game ['name' ]))
300248 self .games_list .setItem (i , 1 , QTableWidgetItem (game ['runner' ]))
301249 self .games_list .setItem (i , 2 , QTableWidgetItem (game .get ('launch_options' , '' )))
302- self .games_list .resizeColumnsToContents ()
303250
304- def load_protons (self , protons ):
251+ def start_proton_loading (self ):
252+ self .protons_table .clearContents ()
305253 self .protons_table .setRowCount (0 )
254+ thread = LoadProtonsThread (self .proton_manager )
255+ thread .protons_loaded .connect (self .load_protons )
256+ thread .start ()
257+
258+ def load_protons (self , protons ):
306259 self .protons_table .setRowCount (len (protons ))
307- for i , proton in enumerate (protons ):
308- self .protons_table .setItem (i , 0 , QTableWidgetItem (proton ['version' ]))
309- self .protons_table .setItem (i , 1 , QTableWidgetItem (proton ['type' ]))
310- self .protons_table .setItem (i , 2 , QTableWidgetItem (proton ['date' ]))
311- self .protons_table .setItem (i , 3 , QTableWidgetItem (proton ['status' ]))
312- self .protons_table .resizeColumnsToContents ()
260+ for i , p in enumerate (protons ):
261+ self .protons_table .setItem (i , 0 , QTableWidgetItem (p ['version' ]))
262+ self .protons_table .setItem (i , 1 , QTableWidgetItem (p ['type' ]))
263+ self .protons_table .setItem (i , 2 , QTableWidgetItem (p ['date' ]))
264+ self .protons_table .setItem (i , 3 , QTableWidgetItem (p ['status' ]))
265+
266+ def switch_to_plasma (self ):
267+ try :
268+ subprocess .run (['startplasma-x11' ], check = True )
269+ self .close ()
270+ except Exception as e :
271+ QMessageBox .warning (self , 'Error' , str (e ))
272+
273+ def shutdown_system (self ):
274+ subprocess .run (['systemctl' , 'poweroff' ])
275+
276+ def reboot_system (self ):
277+ subprocess .run (['systemctl' , 'reboot' ])
278+
279+ def log_out (self ):
280+ subprocess .run (['loginctl' , 'terminate-user' , os .getlogin ()])
313281
314282 def add_game (self ):
315283 add_dialog = QDialog (self )
316- add_dialog .setWindowTitle ("Add New Game" )
317- dlg_layout = QGridLayout ()
318- row = 0
319- name_label = QLabel ('Game Name:' )
284+ add_dialog .setWindowTitle ("Add Game" )
285+ layout = QGridLayout ()
286+ name_label = QLabel ("Game Name:" )
320287 name_edit = QLineEdit ()
321- name_edit .setPlaceholderText ("Enter game name" )
322- dlg_layout .addWidget (name_label , row , 0 )
323- dlg_layout .addWidget (name_edit , row , 1 , 1 , 2 )
324- row += 1
325- exe_label = QLabel ('Executable / App ID:' )
326- exe_edit = QLineEdit ()
327- exe_edit .setPlaceholderText ("Select game executable or enter Steam App ID" )
328- browse_btn = QPushButton (QIcon .fromTheme ("folder" ), 'Browse' )
329- browse_btn .clicked .connect (lambda : exe_edit .setText (QFileDialog .getOpenFileName (self , 'Select Executable' , '/' , 'Executables (*.exe *.bat);;All Files (*)' )[0 ]))
330- dlg_layout .addWidget (exe_label , row , 0 )
331- dlg_layout .addWidget (exe_edit , row , 1 )
332- dlg_layout .addWidget (browse_btn , row , 2 )
333- row += 1
334- runner_label = QLabel ('Runner:' )
288+ layout .addWidget (name_label , 0 , 0 )
289+ layout .addWidget (name_edit , 0 , 1 )
290+ runner_label = QLabel ("Runner:" )
335291 runner_combo = QComboBox ()
336292 runner_combo .addItems (['Native' , 'Wine' , 'Proton' , 'Flatpak' , 'Steam' ])
337293 runner_combo .setCurrentText (self .settings ['default_runner' ])
338- dlg_layout .addWidget (runner_label , row , 0 )
339- dlg_layout .addWidget (runner_combo , row , 1 , 1 , 2 )
340- row += 1
341- proton_label = QLabel ('Proton Version:' )
342- proton_combo = QComboBox ()
343- proton_combo .addItems ([p ['version' ] for p in self .proton_manager .get_installed_protons ()])
344- proton_widget = QWidget ()
345- proton_layout = QHBoxLayout ()
346- proton_layout .addWidget (proton_label )
347- proton_layout .addWidget (proton_combo )
348- proton_widget .setLayout (proton_layout )
349- proton_widget .setVisible (runner_combo .currentText () == 'Proton' )
350- dlg_layout .addWidget (proton_widget , row , 0 , 1 , 3 )
351- row += 1
352- prefix_label = QLabel ('Wine/Proton Prefix:' )
294+ layout .addWidget (runner_label , 1 , 0 )
295+ layout .addWidget (runner_combo , 1 , 1 )
296+ exe_label = QLabel ("Executable/App ID:" )
297+ exe_edit = QLineEdit ()
298+ browse_btn = QPushButton (QIcon .fromTheme ("folder" ), 'Browse' )
299+ exe_hbox = QHBoxLayout ()
300+ exe_hbox .addWidget (exe_edit )
301+ exe_hbox .addWidget (browse_btn )
302+ layout .addWidget (exe_label , 2 , 0 )
303+ layout .addLayout (exe_hbox , 2 , 1 )
304+ prefix_label = QLabel ("Prefix:" )
353305 prefix_edit = QLineEdit ()
354- prefix_edit .setPlaceholderText ("Select or enter prefix path" )
355- prefix_browse_btn = QPushButton (QIcon .fromTheme ("folder" ), 'Browse' )
356- prefix_browse_btn .clicked .connect (lambda : prefix_edit .setText (QFileDialog .getExistingDirectory (self , 'Select Prefix Directory' )))
357- prefix_widget = QWidget ()
358- prefix_layout = QHBoxLayout ()
359- prefix_layout .addWidget (prefix_label )
360- prefix_layout .addWidget (prefix_edit )
361- prefix_layout .addWidget (prefix_browse_btn )
362- prefix_widget .setLayout (prefix_layout )
363- prefix_widget .setVisible (runner_combo .currentText () in ['Wine' , 'Proton' ])
364- dlg_layout .addWidget (prefix_widget , row , 0 , 1 , 3 )
365- row += 1
366- launch_label = QLabel ('Launch Options:' )
367- launch_edit = QLineEdit ()
368- launch_edit .setPlaceholderText ("e.g., --fullscreen --bigpicture --gamescope --adaptive-sync --width=1920 --height=1080" )
369- dlg_layout .addWidget (launch_label , row , 0 )
370- dlg_layout .addWidget (launch_edit , row , 1 , 1 , 2 )
371- row += 1
372- dxvk_check = QCheckBox ("Enable DXVK/VKD3D" )
373- dxvk_check .setVisible (runner_combo .currentText () in ['Wine' , 'Proton' ])
374- dlg_layout .addWidget (dxvk_check , row , 0 )
375- row += 1
376- esync_check = QCheckBox ("Enable Esync (Override)" )
377- esync_check .setVisible (runner_combo .currentText () in ['Wine' , 'Proton' ])
378- dlg_layout .addWidget (esync_check , row , 0 )
379- row += 1
380- fsync_check = QCheckBox ("Enable Fsync (Override)" )
381- fsync_check .setVisible (runner_combo .currentText () in ['Wine' , 'Proton' ])
382- dlg_layout .addWidget (fsync_check , row , 0 )
383- row += 1
384- dxvk_async_check = QCheckBox ("Enable DXVK Async (Override)" )
385- dxvk_async_check .setVisible (runner_combo .currentText () in ['Wine' , 'Proton' ])
386- dlg_layout .addWidget (dxvk_async_check , row , 0 )
387- row += 1
388- app_id_widget = QWidget ()
389- app_id_layout = QHBoxLayout ()
390- app_id_label = QLabel ('Steam App ID:' )
391- app_id_edit = QLineEdit ()
392- app_id_layout .addWidget (app_id_label )
393- app_id_layout .addWidget (app_id_edit )
394- app_id_widget .setLayout (app_id_layout )
395- app_id_widget .setVisible (runner_combo .currentText () == 'Steam' )
396- dlg_layout .addWidget (app_id_widget , row , 0 , 1 , 3 )
397- row += 1
398- def update_visibility (text ):
399- proton_widget .setVisible (text == 'Proton' )
400- prefix_widget .setVisible (text in ['Wine' , 'Proton' ])
401- app_id_widget .setVisible (text == 'Steam' )
402- browse_btn .setVisible (text != 'Steam' )
403- dxvk_check .setVisible (text in ['Wine' , 'Proton' ])
404- esync_check .setVisible (text in ['Wine' , 'Proton' ])
405- fsync_check .setVisible (text in ['Wine' , 'Proton' ])
406- dxvk_async_check .setVisible (text in ['Wine' , 'Proton' ])
407- runner_combo .currentTextChanged .connect (update_visibility )
306+ prefix_browse = QPushButton (QIcon .fromTheme ("folder" ), 'Browse' )
307+ prefix_hbox = QHBoxLayout ()
308+ prefix_hbox .addWidget (prefix_edit )
309+ prefix_hbox .addWidget (prefix_browse )
310+ layout .addWidget (prefix_label , 3 , 0 )
311+ layout .addLayout (prefix_hbox , 3 , 1 )
312+ options_label = QLabel ("Launch Options:" )
313+ options_edit = QLineEdit ()
314+ layout .addWidget (options_label , 4 , 0 )
315+ layout .addWidget (options_edit , 4 , 1 )
316+ esync_check = QCheckBox ("Enable Esync" )
317+ esync_check .setChecked (self .settings ['enable_esync' ])
318+ layout .addWidget (esync_check , 5 , 0 )
319+ fsync_check = QCheckBox ("Enable Fsync" )
320+ fsync_check .setChecked (self .settings ['enable_fsync' ])
321+ layout .addWidget (fsync_check , 6 , 0 )
322+ dxvk_check = QCheckBox ("Enable DXVK" )
323+ layout .addWidget (dxvk_check , 7 , 0 )
324+ dxvk_async_check = QCheckBox ("Enable DXVK Async" )
325+ dxvk_async_check .setChecked (self .settings ['enable_dxvk_async' ])
326+ layout .addWidget (dxvk_async_check , 8 , 0 )
327+ 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 )
338+ runner_combo .currentTextChanged .connect (update_ui )
339+ 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 )
408351 button_layout = QHBoxLayout ()
409- ok_btn = QPushButton (QIcon .fromTheme ("dialog-ok" ), 'Add Game ' )
352+ ok_btn = QPushButton (QIcon .fromTheme ("dialog-ok" ), 'Add' )
410353 cancel_btn = QPushButton (QIcon .fromTheme ("dialog-cancel" ), 'Cancel' )
411354 ok_btn .clicked .connect (add_dialog .accept )
412355 cancel_btn .clicked .connect (add_dialog .reject )
413356 button_layout .addStretch ()
414357 button_layout .addWidget (ok_btn )
415358 button_layout .addWidget (cancel_btn )
416- dlg_layout .addLayout (button_layout , row , 0 , 1 , 3 )
417- add_dialog .setLayout (dlg_layout )
418- add_dialog .resize (600 , 400 )
359+ layout .addLayout (button_layout , 9 , 0 , 1 , 2 )
360+ add_dialog .setLayout (layout )
361+ add_dialog .resize (500 , 400 )
419362 if add_dialog .exec () == QDialog .Accepted :
420363 name = name_edit .text ()
421- exe = exe_edit .text ()
422364 runner = runner_combo .currentText ()
423- launch_options = launch_edit .text ()
424- enable_dxvk = dxvk_check .isChecked ()
365+ exe_or_id = exe_edit .text ()
366+ prefix = prefix_edit .text () if runner in ['Wine' , 'Proton' ] else ''
367+ options = options_edit .text ()
425368 enable_esync = esync_check .isChecked ()
426369 enable_fsync = fsync_check .isChecked ()
370+ enable_dxvk = dxvk_check .isChecked ()
427371 enable_dxvk_async = dxvk_async_check .isChecked ()
428- app_id = app_id_edit .text () if runner == 'Steam' else ''
429- prefix = prefix_edit .text () if runner in ['Wine' , 'Proton' ] else ''
430- if runner == 'Proton' :
431- runner = proton_combo .currentText ()
432- if not name or (runner != 'Steam' and not exe ) or (runner == 'Steam' and not app_id ):
372+ if not name or not exe_or_id :
433373 QMessageBox .warning (self , 'Error' , 'Name and Executable/App ID required' )
434374 return
435- if runner in ['Wine' , 'Proton' ] and not prefix :
436- prefix = os .path .join (self .config_manager .prefixes_dir , name .replace (' ' , '_' ))
437- if prefix :
438- os .makedirs (prefix , exist_ok = True )
439- if os .name == 'posix' and ':' in exe :
440- exe = exe .replace ('\\ ' , '/' ).replace ('C:' , '/drive_c' )
441375 game = {
442376 'name' : name ,
443- 'exe' : exe ,
444377 'runner' : runner ,
445378 'prefix' : prefix ,
446- 'launch_options' : launch_options ,
447- 'enable_dxvk' : enable_dxvk ,
379+ 'launch_options' : options ,
448380 'enable_esync' : enable_esync ,
449381 'enable_fsync' : enable_fsync ,
450- 'enable_dxvk_async ' : enable_dxvk_async ,
451- 'app_id ' : app_id
382+ 'enable_dxvk ' : enable_dxvk ,
383+ 'enable_dxvk_async ' : enable_dxvk_async
452384 }
385+ if runner == 'Steam' :
386+ game ['app_id' ] = exe_or_id
387+ else :
388+ game ['exe' ] = exe_or_id
453389 self .game_manager .add_game (game )
454390 self .load_games ()
455391 QMessageBox .information (self , 'Success' , 'Game added successfully!' )
@@ -463,7 +399,8 @@ def launch_game(self):
463399 game = next ((g for g in self .games if g ['name' ] == name ), None )
464400 if game :
465401 try :
466- self .game_manager .launch_game (game , '--gamescope' in game .get ('launch_options' , '' ))
402+ gamescope = '--gamescope' in game .get ('launch_options' , '' ).split ()
403+ self .game_manager .launch_game (game , gamescope )
467404 except Exception as e :
468405 logging .error (f"Error launching { name } : { e } " )
469406 print (f"Error launching { name } : { e } " )
0 commit comments