add custom steam game launcher
This commit is contained in:
parent
418c89d375
commit
4aeaa61302
301
gamelaunch.py
Executable file
301
gamelaunch.py
Executable file
|
@ -0,0 +1,301 @@
|
|||
#!/usr/bin/env python3
|
||||
import json, logging, signal, subprocess, sys
|
||||
|
||||
from PyQt5.QtCore import *
|
||||
from PyQt5.QtGui import *
|
||||
from PyQt5.QtWidgets import *
|
||||
from PyQt5 import uic
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from urllib.request import urlopen
|
||||
|
||||
|
||||
if len(sys.argv) < 2:
|
||||
args = ['/home/zoey/.steam/steam/steamapps/common/Proton 5.0/proton', 'waitforexitandrun', '/home/zoey/.steam/steam/steamapps/common/BorderlandsGOTYEnhanced/Binaries/Win64/Launcher.exe']
|
||||
|
||||
else:
|
||||
args = sys.argv[1:]
|
||||
|
||||
|
||||
datapath = Path('~/.config/barkshark/proton-launcher').expanduser()
|
||||
common = Path('~/.steam/steam/steamapps/common').expanduser()
|
||||
configfile = datapath.joinpath('config.json')
|
||||
gamelist = datapath.joinpath('games.json')
|
||||
uifile = Path(__file__).resolve().parent.joinpath('gamelauncher.ui')
|
||||
|
||||
if not datapath.exists():
|
||||
datapath.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
config = json.load(configfile.open()) if configfile.exists() else {'auto_start': True, 'auto_close': True}
|
||||
games = {
|
||||
'default': {
|
||||
'id': '0',
|
||||
'code': 'none',
|
||||
'name': 'n/a',
|
||||
'path': '.'
|
||||
},
|
||||
'BorderlandsGOTYEnhanced/Binaries/Win64/Launcher.exe': {
|
||||
'id': '729040',
|
||||
'code': 'bl1e',
|
||||
'name': 'Borderlands GOTY Enhanced',
|
||||
'path': 'BorderlandsGOTYEnhanced/Binaries/Win64/BorderlandsGOTY.exe'
|
||||
},
|
||||
'BorderlandsPreSequel/Binaries/Win32/Launcher.exe': {
|
||||
'id': '261640',
|
||||
'code': 'bltps',
|
||||
'name': 'Borderlands: The Pre-Sequel',
|
||||
'path': 'BorderlandsGOTYEnhanced/Binaries/Win64/BorderlandsPreSequel.exe'
|
||||
},
|
||||
'Borderlands 2/Binaries/Win32/Launcher.exe': {
|
||||
'id': '49520',
|
||||
'code': 'bl2',
|
||||
'name': 'Borderlands 2',
|
||||
'path': 'Borderlands 2/Binaries/Win32/Borderlands2.exe'
|
||||
},
|
||||
'Borderlands 3/OakGame/Binaries/Win64/Launcher.exe': {
|
||||
'id': '397540',
|
||||
'code': 'bl3',
|
||||
'name': 'Borderlands 3',
|
||||
'path': 'Borderlands 3/OakGame/Binaries/Win64/Borderlands3.exe'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Launcher(QMainWindow):
|
||||
def __init__(self, app):
|
||||
super().__init__()
|
||||
uic.loadUi(str(uifile), self)
|
||||
self.app = app
|
||||
self.game = self._get_game()
|
||||
self.config = config.get(self.game['id'], {})
|
||||
self.opts = {}
|
||||
self.proc = QProcess(self)
|
||||
|
||||
self.env = {
|
||||
'DXVK_HUD': 'fps,api,memory'
|
||||
}
|
||||
|
||||
self.command = ['/usr/bin/env'] + [f'{k}={v}' for k,v in self.env.items()] + args[:-1]
|
||||
self.command.append(str(common.joinpath(self.game['path'])))
|
||||
|
||||
self.game_bl1 = self.game_borderlands
|
||||
self.game_bl1e = self.game_borderlands
|
||||
self.game_bltps = self.game_borderlands
|
||||
self.game_bl2 = self.game_borderlands
|
||||
self.game_bl3 = self.game_borderlands
|
||||
|
||||
setupGame = getattr(self, f'game_{self.game["code"]}', None)
|
||||
|
||||
if setupGame:
|
||||
setupGame()
|
||||
|
||||
for opt in self.opts.values():
|
||||
self.options.addWidget(opt)
|
||||
|
||||
self._print_command()
|
||||
self._setup_buttons()
|
||||
self._center()
|
||||
self.setWindowTitle(f'Game Launcher: {self.game["name"]} [{self.game["id"]}]')
|
||||
|
||||
|
||||
def log(self, *args):
|
||||
scrollbar = self.console.verticalScrollBar()
|
||||
date = datetime.now()
|
||||
line = ' '.join(args)
|
||||
|
||||
if self.console.blockCount() == 1 and self.console.toPlainText() == '':
|
||||
'nothing'
|
||||
|
||||
else:
|
||||
self.console.insertPlainText('\n')
|
||||
|
||||
self.console.insertPlainText(f'{date.strftime("%H:%M:%S")} {line}')
|
||||
scrollbar.setValue(scrollbar.maximum())
|
||||
|
||||
while self.console.blockCount() > 1000:
|
||||
lineno = self.console.document().findBlockByLineNumber(1)
|
||||
cursor = QTextCursor(lineno)
|
||||
cursor.select(QTextCursor.BlockUnderCursor)
|
||||
cursor.removeSelectedText()
|
||||
|
||||
|
||||
def game_borderlands(self):
|
||||
self.opts['-NoStartupMovies'] = QCheckBox()
|
||||
self.opts['-NoStartupMovies'].setText('No Startup Movies')
|
||||
|
||||
if config.get('-NoStartupMovies'):
|
||||
self.opts['-NoStartupMovies'].setCheckState(True)
|
||||
|
||||
|
||||
def save_config(self):
|
||||
config[self.game['id']] = {
|
||||
'options': {k: v.checkState() for k,v in self.opts.items()}
|
||||
}
|
||||
|
||||
|
||||
def _setup_buttons(self):
|
||||
self.kill.setEnabled(False)
|
||||
self.autoClose.setChecked(config.get('auto_close', False))
|
||||
self.autoStart.setChecked(config.get('auto_start', True))
|
||||
|
||||
for name, checkbox in self.opts.items():
|
||||
checkbox.setChecked(self.config.get(name, False))
|
||||
self._setup_checkbox(checkbox, name)
|
||||
|
||||
self.run.clicked.connect(self._run)
|
||||
self.kill.clicked.connect(self._kill)
|
||||
self.save.clicked.connect(self._save)
|
||||
self.autoClose.stateChanged.connect(lambda state: self._checkbox_clicked('close', state))
|
||||
self.autoStart.stateChanged.connect(lambda state: self._checkbox_clicked('start', state))
|
||||
|
||||
self.proc.started.connect(self._started)
|
||||
self.proc.finished.connect(self._finished)
|
||||
self.proc.readyReadStandardOutput.connect(self._console_log)
|
||||
self.proc.readyReadStandardError.connect(self._console_log)
|
||||
|
||||
|
||||
def _setup_checkbox(self, widget, name):
|
||||
widget.stateChanged.connect(lambda state: self._checkbox_clicked(name, state))
|
||||
|
||||
|
||||
def _get_game(self):
|
||||
for arg in args:
|
||||
if arg.endswith('.exe'):
|
||||
self.game_bin = arg.replace(str(common) + '/', '')
|
||||
game = games.get(self.game_bin, games['default'])
|
||||
|
||||
if game['name'] != 'default':
|
||||
return game
|
||||
|
||||
|
||||
def _center(self):
|
||||
geo = self.frameGeometry()
|
||||
center_rect = QDesktopWidget().availableGeometry().center()
|
||||
geo.moveCenter(center_rect)
|
||||
self.move(geo.topLeft())
|
||||
|
||||
|
||||
def _print_command(self):
|
||||
self.commandLine.setText(' '.join(self.command))
|
||||
|
||||
|
||||
def _console_log(self):
|
||||
output = self.proc.readAllStandardOutput().data().decode().split('\n')
|
||||
error = self.proc.readAllStandardError().data().decode().split('\n')
|
||||
|
||||
for line in [*output, *error]:
|
||||
if line != '' and 'gameoverlayrenderer.so' not in line:
|
||||
self.log(line)
|
||||
|
||||
|
||||
def _started(self):
|
||||
self.console.clear()
|
||||
self.log('Starting')
|
||||
self.run.setEnabled(False)
|
||||
self.kill.setEnabled(True)
|
||||
|
||||
|
||||
def _finished(self):
|
||||
if self.autoClose.checkState():
|
||||
self.app.quit()
|
||||
|
||||
self.log('Program exited')
|
||||
self.run.setEnabled(True)
|
||||
self.kill.setEnabled(False)
|
||||
|
||||
|
||||
def _kill(self):
|
||||
self.proc.terminate()
|
||||
self.proc.waitForFinished(5000)
|
||||
|
||||
if self.proc.processId():
|
||||
self.proc.kill()
|
||||
|
||||
|
||||
def _checkbox_clicked(self, name, state):
|
||||
state = True if state else False
|
||||
|
||||
if name == 'close':
|
||||
config['auto_close'] = state
|
||||
|
||||
if name == 'start':
|
||||
config['auto_start'] = state
|
||||
|
||||
else:
|
||||
gameid = str(self.game['id'])
|
||||
|
||||
if not config.get(gameid):
|
||||
config[gameid] = {}
|
||||
|
||||
config[gameid] = {name: state}
|
||||
|
||||
|
||||
def _timer_count(self):
|
||||
if self.proc.state() != 0:
|
||||
self.count = 0
|
||||
self.timer.timeout.disconnect()
|
||||
return
|
||||
|
||||
self.count -= 1
|
||||
self.log(f'Starting in {self.count}s...')
|
||||
|
||||
if self.count == 0:
|
||||
self.timer.timeout.disconnect()
|
||||
self._run()
|
||||
|
||||
|
||||
def _save(self):
|
||||
json.dump(config, configfile.open('w'), indent=4)
|
||||
|
||||
|
||||
def _run(self, *args):
|
||||
command = self.command.copy()
|
||||
for k,v in self.opts.items():
|
||||
if v.checkState():
|
||||
command.append(k)
|
||||
|
||||
self.proc.start(command[0], command[1:])
|
||||
|
||||
try:
|
||||
self.timer.timeout.disconnect()
|
||||
except:
|
||||
'nothing'
|
||||
|
||||
|
||||
def main():
|
||||
## Prevent errors from bringing down the whole thing
|
||||
sys._excepthook = sys.excepthook
|
||||
sys.excepthook = lambda *args: sys._excepthook(*args)
|
||||
|
||||
app = QApplication([])
|
||||
app.setApplicationName('Proton Game Launcher')
|
||||
app.setOrganizationName('Barkshark')
|
||||
app.setOrganizationDomain('barkshark.xyz')
|
||||
#app.setWindowIcon(Icon('icon'))
|
||||
|
||||
window = Launcher(app)
|
||||
app.aboutToQuit.connect(window.close)
|
||||
|
||||
## Run SaveWinState every second. It doesn't do anything if the state hasn't changed
|
||||
## This was originally here to allow python to handle signals, so why not take advantage of it?
|
||||
window.count = 5
|
||||
window.timer = QTimer()
|
||||
window.timer.start(1000)
|
||||
|
||||
if config.get('auto_start', True):
|
||||
window.log('Starting in 5s...')
|
||||
window.timer.timeout.connect(window._timer_count)
|
||||
|
||||
signal.signal(signal.SIGTERM, lambda *x: app.quit())
|
||||
signal.signal(signal.SIGINT, lambda *x: app.quit())
|
||||
|
||||
window.show()
|
||||
|
||||
sys.exit(app.exec())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
'heck'
|
156
gamelaunch.ui
Normal file
156
gamelaunch.ui
Normal file
|
@ -0,0 +1,156 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="windowModality">
|
||||
<enum>Qt::WindowModal</enum>
|
||||
</property>
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>680</width>
|
||||
<height>325</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>480</width>
|
||||
<height>250</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<property name="spacing">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="options"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Console</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPlainTextEdit" name="console">
|
||||
<property name="lineWrapMode">
|
||||
<enum>QPlainTextEdit::NoWrap</enum>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="commandLine"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="bottomBar">
|
||||
<property name="spacing">
|
||||
<number>4</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="autoStart">
|
||||
<property name="text">
|
||||
<string>Auto Start</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="autoClose">
|
||||
<property name="toolTip">
|
||||
<string>Automatically launch the game next time</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Auto Close</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="save">
|
||||
<property name="toolTip">
|
||||
<string>Save the config</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Save</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="kill">
|
||||
<property name="text">
|
||||
<string>Kill</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="run">
|
||||
<property name="toolTip">
|
||||
<string>Run the selected game</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Run</string>
|
||||
</property>
|
||||
<property name="default">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<action name="exit">
|
||||
<property name="text">
|
||||
<string>Exit</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="update">
|
||||
<property name="text">
|
||||
<string>Update Games</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
0
proton-ge-updater.py
Normal file → Executable file
0
proton-ge-updater.py
Normal file → Executable file
Loading…
Reference in a new issue