Skip to content

Commit 8bc47ba

Browse files
authored
Update main_window.py
1 parent d064f9f commit 8bc47ba

File tree

1 file changed

+210
-22
lines changed

1 file changed

+210
-22
lines changed

main_window.py

Lines changed: 210 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,31 @@
99
from game_manager import GameManager
1010
from config_manager import ConfigManager
1111

12+
class CancelledError(Exception):
13+
pass
14+
1215
class InstallThread(QThread):
1316
update_progress = Signal(str, int, int)
1417
finished = Signal(bool, str)
18+
1519
def __init__(self, manager, version, proton_type, custom_path=None, custom_type=None):
1620
super().__init__()
1721
self.manager = manager
1822
self.version = version
1923
self.proton_type = proton_type
2024
self.custom_path = custom_path
2125
self.custom_type = custom_type
26+
self._is_canceled = False
27+
28+
def cancel(self):
29+
self._is_canceled = True
30+
2231
def run(self):
2332
def progress_callback(stage, value, total):
2433
self.update_progress.emit(stage, value, total)
34+
if self._is_canceled:
35+
raise CancelledError("Installation cancelled")
36+
2537
try:
2638
if self.proton_type in ['GE', 'Official', 'Experimental']:
2739
success, message = self.manager.install_proton(self.version, self.proton_type if self.proton_type != 'Experimental' else 'Official', progress_callback)
@@ -31,14 +43,18 @@ def progress_callback(stage, value, total):
3143
else:
3244
success, message = self.manager.install_custom_folder(self.custom_path, self.version)
3345
self.finished.emit(success, message)
46+
except CancelledError as e:
47+
self.finished.emit(False, str(e))
3448
except Exception as e:
3549
self.finished.emit(False, str(e))
3650

3751
class LoadProtonsThread(QThread):
3852
protons_loaded = Signal(list)
53+
3954
def __init__(self, manager):
4055
super().__init__()
4156
self.manager = manager
57+
4258
def run(self):
4359
try:
4460
protons = self.manager.get_installed_protons()
@@ -346,6 +362,13 @@ def add_game(self):
346362
app_id_widget.setVisible(runner_combo.currentText() == 'Steam')
347363
dlg_layout.addWidget(app_id_widget, row, 0, 1, 3)
348364
row += 1
365+
fps_label = QLabel('FPS Limit:')
366+
fps_edit = QLineEdit()
367+
fps_edit.setPlaceholderText("Enter FPS limit (e.g., 60)")
368+
dlg_layout.addWidget(fps_label, row, 0)
369+
dlg_layout.addWidget(fps_edit, row, 1, 1, 2)
370+
row += 1
371+
349372
def update_visibility(text):
350373
proton_widget.setVisible(text == 'Proton')
351374
prefix_widget.setVisible(text in ['Wine', 'Proton'])
@@ -355,6 +378,7 @@ def update_visibility(text):
355378
esync_check.setVisible(text in ['Wine', 'Proton'])
356379
fsync_check.setVisible(text in ['Wine', 'Proton'])
357380
dxvk_async_check.setVisible(text in ['Wine', 'Proton'])
381+
358382
runner_combo.currentTextChanged.connect(update_visibility)
359383
button_layout = QHBoxLayout()
360384
ok_btn = QPushButton(QIcon.fromTheme("dialog-ok"), 'Add Game')
@@ -378,6 +402,7 @@ def update_visibility(text):
378402
enable_dxvk_async = dxvk_async_check.isChecked()
379403
app_id = app_id_edit.text() if runner == 'Steam' else ''
380404
prefix = prefix_edit.text() if runner in ['Wine', 'Proton'] else ''
405+
fps_limit = fps_edit.text()
381406
if runner == 'Proton':
382407
runner = proton_combo.currentText()
383408
if not name or (runner != 'Steam' and not exe) or (runner == 'Steam' and not app_id):
@@ -399,7 +424,8 @@ def update_visibility(text):
399424
'enable_esync': enable_esync,
400425
'enable_fsync': enable_fsync,
401426
'enable_dxvk_async': enable_dxvk_async,
402-
'app_id': app_id
427+
'app_id': app_id,
428+
'fps_limit': fps_limit
403429
}
404430
self.game_manager.add_game(game)
405431
self.load_games()
@@ -414,6 +440,12 @@ def launch_game(self):
414440
game = next((g for g in self.games if g['name'] == name), None)
415441
if game:
416442
try:
443+
# Dodaj fps limit do launch_options jeśli gamescope jest używany
444+
launch_options = game.get('launch_options', '').split()
445+
fps_limit = game.get('fps_limit', '')
446+
if fps_limit and '--gamescope' in launch_options:
447+
launch_options.append(f'--framerate-limit={fps_limit}')
448+
game['launch_options'] = ' '.join(launch_options)
417449
self.game_manager.launch_game(game, '--gamescope' in game.get('launch_options', ''))
418450
except Exception as e:
419451
logging.error(f"Error launching {name}: {e}")
@@ -436,23 +468,107 @@ def configure_game(self):
436468
return
437469
name = self.games_list.item(selected, 0).text()
438470
game = next((g for g in self.games if g['name'] == name), None)
439-
if not game or game['runner'] in ['Native', 'Flatpak', 'Steam']:
440-
QMessageBox.information(self, 'Info', 'No configuration needed for this runner')
471+
if not game:
441472
return
442-
if not shutil.which('winetricks'):
443-
QMessageBox.warning(self, 'Error', 'Winetricks not installed. Please install it.')
444-
return
445-
tricks_dialog = QDialog(self)
446-
tricks_dialog.setWindowTitle("Install Winetricks Libraries")
447-
tricks_layout = QVBoxLayout()
473+
config_dialog = QDialog(self)
474+
config_dialog.setWindowTitle("Configure Game")
475+
dlg_layout = QGridLayout()
476+
row = 0
477+
# Podstawowe pola
478+
name_label = QLabel('Game Name:')
479+
name_edit = QLineEdit(game['name'])
480+
dlg_layout.addWidget(name_label, row, 0)
481+
dlg_layout.addWidget(name_edit, row, 1, 1, 2)
482+
row += 1
483+
exe_label = QLabel('Executable / App ID:')
484+
exe_edit = QLineEdit(game['exe'])
485+
browse_btn = QPushButton(QIcon.fromTheme("folder"), 'Browse')
486+
browse_btn.clicked.connect(lambda: exe_edit.setText(QFileDialog.getOpenFileName(self, 'Select Executable', '/', 'Executables (*.exe *.bat);;All Files (*)')[0]))
487+
dlg_layout.addWidget(exe_label, row, 0)
488+
dlg_layout.addWidget(exe_edit, row, 1)
489+
dlg_layout.addWidget(browse_btn, row, 2)
490+
row += 1
491+
runner_label = QLabel('Runner:')
492+
runner_combo = QComboBox()
493+
runner_combo.addItems(['Native', 'Wine', 'Proton', 'Flatpak', 'Steam'])
494+
runner_combo.setCurrentText('Proton' if 'Proton' in game['runner'] else game['runner'])
495+
dlg_layout.addWidget(runner_label, row, 0)
496+
dlg_layout.addWidget(runner_combo, row, 1, 1, 2)
497+
row += 1
498+
proton_label = QLabel('Proton Version:')
499+
proton_combo = QComboBox()
500+
proton_combo.addItems([p['version'] for p in self.proton_manager.get_installed_protons()])
501+
proton_combo.setCurrentText(game['runner'] if 'Proton' in game['runner'] else '')
502+
proton_widget = QWidget()
503+
proton_layout = QHBoxLayout()
504+
proton_layout.addWidget(proton_label)
505+
proton_layout.addWidget(proton_combo)
506+
proton_widget.setLayout(proton_layout)
507+
proton_widget.setVisible(runner_combo.currentText() == 'Proton')
508+
dlg_layout.addWidget(proton_widget, row, 0, 1, 3)
509+
row += 1
510+
prefix_label = QLabel('Wine/Proton Prefix:')
511+
prefix_edit = QLineEdit(game['prefix'])
512+
prefix_browse_btn = QPushButton(QIcon.fromTheme("folder"), 'Browse')
513+
prefix_browse_btn.clicked.connect(lambda: prefix_edit.setText(QFileDialog.getExistingDirectory(self, 'Select Prefix Directory')))
514+
prefix_widget = QWidget()
515+
prefix_layout = QHBoxLayout()
516+
prefix_layout.addWidget(prefix_label)
517+
prefix_layout.addWidget(prefix_edit)
518+
prefix_layout.addWidget(prefix_browse_btn)
519+
prefix_widget.setLayout(prefix_layout)
520+
prefix_widget.setVisible(runner_combo.currentText() in ['Wine', 'Proton'])
521+
dlg_layout.addWidget(prefix_widget, row, 0, 1, 3)
522+
row += 1
523+
launch_label = QLabel('Launch Options:')
524+
launch_edit = QLineEdit(game.get('launch_options', ''))
525+
dlg_layout.addWidget(launch_label, row, 0)
526+
dlg_layout.addWidget(launch_edit, row, 1, 1, 2)
527+
row += 1
528+
dxvk_check = QCheckBox("Enable DXVK/VKD3D")
529+
dxvk_check.setChecked(game.get('enable_dxvk', False))
530+
dxvk_check.setVisible(runner_combo.currentText() in ['Wine', 'Proton'])
531+
dlg_layout.addWidget(dxvk_check, row, 0)
532+
row += 1
533+
esync_check = QCheckBox("Enable Esync (Override)")
534+
esync_check.setChecked(game.get('enable_esync', False))
535+
esync_check.setVisible(runner_combo.currentText() in ['Wine', 'Proton'])
536+
dlg_layout.addWidget(esync_check, row, 0)
537+
row += 1
538+
fsync_check = QCheckBox("Enable Fsync (Override)")
539+
fsync_check.setChecked(game.get('enable_fsync', False))
540+
fsync_check.setVisible(runner_combo.currentText() in ['Wine', 'Proton'])
541+
dlg_layout.addWidget(fsync_check, row, 0)
542+
row += 1
543+
dxvk_async_check = QCheckBox("Enable DXVK Async (Override)")
544+
dxvk_async_check.setChecked(game.get('enable_dxvk_async', False))
545+
dxvk_async_check.setVisible(runner_combo.currentText() in ['Wine', 'Proton'])
546+
dlg_layout.addWidget(dxvk_async_check, row, 0)
547+
row += 1
548+
app_id_widget = QWidget()
549+
app_id_layout = QHBoxLayout()
550+
app_id_label = QLabel('Steam App ID:')
551+
app_id_edit = QLineEdit(game.get('app_id', ''))
552+
app_id_layout.addWidget(app_id_label)
553+
app_id_layout.addWidget(app_id_edit)
554+
app_id_widget.setLayout(app_id_layout)
555+
app_id_widget.setVisible(runner_combo.currentText() == 'Steam')
556+
dlg_layout.addWidget(app_id_widget, row, 0, 1, 3)
557+
row += 1
558+
fps_label = QLabel('FPS Limit:')
559+
fps_edit = QLineEdit(game.get('fps_limit', ''))
560+
dlg_layout.addWidget(fps_label, row, 0)
561+
dlg_layout.addWidget(fps_edit, row, 1, 1, 2)
562+
row += 1
563+
# Sekcja winetricks
448564
tricks_label = QLabel("Select libraries to install:")
449565
tricks_list = QComboBox()
450566
popular_tricks = ['dotnet48', 'vcrun2019', 'dxvk', 'vkd3d', 'corefonts', 'd3dcompiler_47', 'physx', 'msls31']
451567
tricks_list.addItems(popular_tricks)
452568
install_btn = QPushButton('Install Selected')
453569
def install_trick():
454570
trick = tricks_list.currentText()
455-
prefix = game['prefix']
571+
prefix = prefix_edit.text()
456572
env = os.environ.copy()
457573
env['WINEPREFIX'] = prefix
458574
try:
@@ -461,14 +577,80 @@ def install_trick():
461577
except Exception as e:
462578
QMessageBox.warning(self, 'Error', str(e))
463579
install_btn.clicked.connect(install_trick)
464-
tricks_layout.addWidget(tricks_label)
465-
tricks_layout.addWidget(tricks_list)
466-
tricks_layout.addWidget(install_btn)
467-
tricks_dialog.setLayout(tricks_layout)
468-
tricks_dialog.exec()
469-
env = os.environ.copy()
470-
env['WINEPREFIX'] = game['prefix']
471-
subprocess.Popen(['winetricks'], env=env)
580+
dlg_layout.addWidget(tricks_label, row, 0)
581+
dlg_layout.addWidget(tricks_list, row, 1)
582+
dlg_layout.addWidget(install_btn, row, 2)
583+
row += 1
584+
winetricks_btn = QPushButton('Open Winetricks')
585+
def open_winetricks():
586+
prefix = prefix_edit.text()
587+
env = os.environ.copy()
588+
env['WINEPREFIX'] = prefix
589+
subprocess.Popen(['winetricks'], env=env)
590+
winetricks_btn.clicked.connect(open_winetricks)
591+
dlg_layout.addWidget(winetricks_btn, row, 0, 1, 3)
592+
row += 1
593+
594+
def update_visibility(text):
595+
proton_widget.setVisible(text == 'Proton')
596+
prefix_widget.setVisible(text in ['Wine', 'Proton'])
597+
app_id_widget.setVisible(text == 'Steam')
598+
browse_btn.setVisible(text != 'Steam')
599+
dxvk_check.setVisible(text in ['Wine', 'Proton'])
600+
esync_check.setVisible(text in ['Wine', 'Proton'])
601+
fsync_check.setVisible(text in ['Wine', 'Proton'])
602+
dxvk_async_check.setVisible(text in ['Wine', 'Proton'])
603+
604+
runner_combo.currentTextChanged.connect(update_visibility)
605+
button_layout = QHBoxLayout()
606+
ok_btn = QPushButton(QIcon.fromTheme("dialog-ok"), 'Save Changes')
607+
cancel_btn = QPushButton(QIcon.fromTheme("dialog-cancel"), 'Cancel')
608+
ok_btn.clicked.connect(config_dialog.accept)
609+
cancel_btn.clicked.connect(config_dialog.reject)
610+
button_layout.addStretch()
611+
button_layout.addWidget(ok_btn)
612+
button_layout.addWidget(cancel_btn)
613+
dlg_layout.addLayout(button_layout, row, 0, 1, 3)
614+
config_dialog.setLayout(dlg_layout)
615+
config_dialog.resize(600, 600)
616+
if config_dialog.exec() == QDialog.Accepted:
617+
new_name = name_edit.text()
618+
exe = exe_edit.text()
619+
runner = runner_combo.currentText()
620+
launch_options = launch_edit.text()
621+
enable_dxvk = dxvk_check.isChecked()
622+
enable_esync = esync_check.isChecked()
623+
enable_fsync = fsync_check.isChecked()
624+
enable_dxvk_async = dxvk_async_check.isChecked()
625+
app_id = app_id_edit.text() if runner == 'Steam' else ''
626+
prefix = prefix_edit.text() if runner in ['Wine', 'Proton'] else ''
627+
fps_limit = fps_edit.text()
628+
if runner == 'Proton':
629+
runner = proton_combo.currentText()
630+
if not new_name or (runner != 'Steam' and not exe) or (runner == 'Steam' and not app_id):
631+
QMessageBox.warning(self, 'Error', 'Name and Executable/App ID required')
632+
return
633+
if runner in ['Wine', 'Proton'] and not prefix:
634+
prefix = os.path.join(self.config_manager.prefixes_dir, new_name.replace(' ', '_'))
635+
if prefix:
636+
os.makedirs(prefix, exist_ok=True)
637+
if os.name == 'posix' and ':' in exe:
638+
exe = exe.replace('\\', '/').replace('C:', '/drive_c')
639+
# Aktualizuj game
640+
game['name'] = new_name
641+
game['exe'] = exe
642+
game['runner'] = runner
643+
game['prefix'] = prefix
644+
game['launch_options'] = launch_options
645+
game['enable_dxvk'] = enable_dxvk
646+
game['enable_esync'] = enable_esync
647+
game['enable_fsync'] = enable_fsync
648+
game['enable_dxvk_async'] = enable_dxvk_async
649+
game['app_id'] = app_id
650+
game['fps_limit'] = fps_limit
651+
self.config_manager.save_games(self.games)
652+
self.load_games()
653+
QMessageBox.information(self, 'Success', 'Game configured successfully!')
472654

473655
def install_proton(self):
474656
install_dialog = QDialog(self)
@@ -510,6 +692,7 @@ def install_proton(self):
510692
custom_widget.setLayout(custom_layout)
511693
custom_widget.setVisible(False)
512694
layout.addWidget(custom_widget)
695+
513696
def update_ui(text):
514697
if text == 'Custom':
515698
version_widget.setVisible(False)
@@ -525,8 +708,10 @@ def update_ui(text):
525708
available = self.proton_manager.get_available_official(stable=False)
526709
version_combo.clear()
527710
version_combo.addItems(available or ["No versions available"])
711+
528712
type_combo.currentTextChanged.connect(update_ui)
529713
update_ui(type_combo.currentText())
714+
530715
def browse_custom():
531716
if custom_type_combo.currentText() == 'Tar.gz File':
532717
path = QFileDialog.getOpenFileName(self, 'Select Tar.gz', '', 'Tar.gz (*.tar.gz)')[0]
@@ -536,6 +721,7 @@ def browse_custom():
536721
path_edit.setText(path)
537722
if not name_edit.text():
538723
name_edit.setText(os.path.basename(path).replace('.tar.gz', ''))
724+
539725
browse_btn.clicked.connect(browse_custom)
540726
button_layout = QHBoxLayout()
541727
ok_btn = QPushButton(QIcon.fromTheme("dialog-ok"), 'Install')
@@ -552,7 +738,7 @@ def browse_custom():
552738
proton_type = type_combo.currentText()
553739
progress = QProgressDialog(f"Installing {proton_type} Proton...", "Cancel", 0, 100, self)
554740
progress.setWindowModality(Qt.WindowModal)
555-
progress.setAutoClose(True)
741+
progress.setAutoClose(False) # Zmiana na False, aby ręcznie zamykać
556742
version = version_combo.currentText() if proton_type != 'Custom' else name_edit.text()
557743
custom_path = path_edit.text() if proton_type == 'Custom' else None
558744
custom_type = custom_type_combo.currentText() if proton_type == 'Custom' else None
@@ -563,14 +749,15 @@ def browse_custom():
563749
thread.update_progress.connect(lambda stage, value, total: progress.setLabelText(stage) or progress.setValue(int(value * 100 / total) if total else 0))
564750
thread.finished.connect(lambda success, message: self.install_finished(success, message, version, progress))
565751
thread.start()
566-
progress.canceled.connect(thread.terminate)
752+
progress.canceled.connect(thread.cancel)
567753

568754
def install_finished(self, success, message, version, progress):
569755
progress.setValue(100)
570756
if success:
571757
QMessageBox.information(self, 'Success', f'Proton {version} installed')
572758
else:
573759
QMessageBox.warning(self, 'Error', f'Failed to install Proton {version}: {message}')
760+
progress.close()
574761
self.start_proton_loading()
575762

576763
def update_proton(self):
@@ -590,12 +777,12 @@ def update_proton(self):
590777
return
591778
progress = QProgressDialog(f"Updating {proton_type} Proton to {new_version}...", "Cancel", 0, 100, self)
592779
progress.setWindowModality(Qt.WindowModal)
593-
progress.setAutoClose(True)
780+
progress.setAutoClose(False)
594781
thread = InstallThread(self.proton_manager, new_version, new_type)
595782
thread.update_progress.connect(lambda stage, value, total: progress.setLabelText(stage) or progress.setValue(int(value * 100 / total) if total else 0))
596783
thread.finished.connect(lambda success, message: self.update_finished(success, message, new_version, version, progress))
597784
thread.start()
598-
progress.canceled.connect(thread.terminate)
785+
progress.canceled.connect(thread.cancel)
599786

600787
def update_finished(self, success, message, new_version, old_version, progress):
601788
progress.setValue(100)
@@ -604,6 +791,7 @@ def update_finished(self, success, message, new_version, old_version, progress):
604791
QMessageBox.information(self, 'Success', f'Updated to {new_version}')
605792
else:
606793
QMessageBox.warning(self, 'Error', f'Failed to update to {new_version}: {message}')
794+
progress.close()
607795
self.start_proton_loading()
608796

609797
def remove_proton(self):

0 commit comments

Comments
 (0)