Skip to content

Commit

Permalink
major overhaul, add final server browser, add pygame input detection,…
Browse files Browse the repository at this point in the history
… launch rF config for from graphic options
  • Loading branch information
tappi287 committed Feb 2, 2021
1 parent 81d44fb commit 58d0713
Show file tree
Hide file tree
Showing 29 changed files with 3,051 additions and 525 deletions.
2 changes: 2 additions & 0 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ eel = {version = "==0.14.1", index = "piwigo"} # See fork https://github.com/ta
pyinstaller = "*"
appdirs = "*"
# requests = "*"
pygame = "==2.0.1"
python-a2s = "*"

[requires]
python_version = "3.9"
324 changes: 200 additions & 124 deletions Pipfile.lock

Large diffs are not rendered by default.

11 changes: 7 additions & 4 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from webmethods import expose_methods
from rf2settings.app_settings import AppSettings
from rf2settings.runasadmin import run_as_admin

from rf2settings.utils import print_controllers

logging.basicConfig(stream=sys.stdout, format='%(asctime)s %(levelname)s: %(message)s',
datefmt='%H:%M', level=logging.DEBUG)
Expand All @@ -33,23 +33,26 @@ def start_eel():
if sys.platform == "win32":
import ctypes
ctypes.windll.kernel32.SetDllDirectoryA(None)
print_controllers()
"""
//
"""

host = 'localhost'
port = 8123
eel.init('web')
page = 'index.html'
window_size = (960, 600)

try:
eel.start('index.html', size=(960, 600), host=host, port=port)
eel.start(page, size=window_size, host=host, port=port)
except EnvironmentError:
# If Chrome isn't found, fallback to Microsoft Edge on Win10 or greater
if sys.platform in ['win32', 'win64'] and int(platform.release()) >= 10:
eel.start('index.html', size=(960, 600), mode='edge', host=host, port=port)
eel.start(page, size=window_size, mode='edge', host=host, port=port)
# Fallback to opening a regular browser window
else:
eel.start('index.html', size=(960, 600), mode=None, app_mode=False, host=host, port=port, block=False)
eel.start(page, size=window_size, mode=None, app_mode=False, host=host, port=port, block=False)
# Open system default web browser
webbrowser.open_new(f'http://{host}:{port}')
# Run until window/tab closed
Expand Down
19 changes: 17 additions & 2 deletions app.spec
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
# -*- mode: python ; coding: utf-8 -*-
from types import ModuleType

import pygame
from PyInstaller.utils.hooks import get_package_paths

block_cipher = None
excluded_modules = list()

# ----- define app name
APP_NAME = 'rF2-Settings-Widget'
Expand All @@ -10,15 +14,26 @@ eel_js = get_package_paths('eel')[-1] + '\\eel.js'
# -----
icon_file = './vue/src/assets/app_icon.ico'

# ---- pygame excludes
required_pygame_modules = ('base', 'constants', 'color', 'colordict', 'event', 'version', 'rect', 'compat', 'rwobject',
'surflock', 'bufferproxy', 'math', 'joystick', 'key', 'mouse')
for p in dir(pygame):
if type(getattr(pygame, p)) is ModuleType and p not in required_pygame_modules:
excluded_modules.append(f'pygame.{p}')

# ---- other excludes
excluded_modules += ['_ssl', 'cryptography']


a = Analysis(['app.py'],
pathex=['D:\\Docs\\py\\rfvideosettings'],
binaries=[],
datas=[(eel_js, 'eel'), ('web', 'web'), ('rf2settings/default_presets', 'default_presets'),
('build/version.txt', '.'), ('license.txt', '.'), ],
hiddenimports=['bottle_websocket'],
hookspath=[],
hookspath=['hooks'],
runtime_hooks=[],
excludes=[],
excludes=excluded_modules,
win_no_prefer_redirects=False,
win_private_assemblies=False,
cipher=block_cipher,
Expand Down
55 changes: 55 additions & 0 deletions hooks/hook-pygame.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
"""
binaries hook for pygame seems to be required for pygame 2.0 Windows.
Otherwise some essential DLLs will not be transfered to the exe.
And also put hooks for datas, resources that pygame uses, to work
correctly with pyinstaller
"""

import os
import platform

from pygame import __file__ as pygame_main_file

# Get pygame's folder
pygame_folder = os.path.dirname(os.path.abspath(pygame_main_file))

# datas is the variable that pyinstaller looks for while processing hooks
datas = []

# exclude some unneeded binaries
exclude_bin = ('libFLAC-8', 'libfreetype-6', 'libjpeg-9', 'libmodplug-1', 'libmpg123-0', 'libogg-0', 'libopus-0',
'libopusfile-0', 'libpng16-16', 'libtiff-5', 'libvorbis-0', 'libvorbisfile-3', 'libwebp-7', 'portmidi',
'SDL2_image', 'SDL2_mixer', 'SDL2_ttf')

# A helper to append the relative path of a resource to hook variable - datas
def _append_to_datas(file_path):
global datas
res_path = os.path.join(pygame_folder, file_path)
if os.path.exists(res_path):
datas.append((res_path, "pygame"))

# First append the font file, then based on the OS, append pygame icon file
_append_to_datas("freesansbold.ttf")
if platform.system() == "Darwin":
_append_to_datas("pygame_icon.tiff")
else:
_append_to_datas("pygame_icon.bmp")

if platform.system() == "Windows":
from PyInstaller.utils.hooks import collect_dynamic_libs

pre_binaries = collect_dynamic_libs('pygame')
binaries = []

for b in pre_binaries:
binary, location = b

filename = os.path.split(binary)[-1]
if filename.removesuffix('.dll') in exclude_bin:
print('Custom pygame hook excluding binary:', filename)
continue

# settles all the DLLs into the top level folder, which prevents duplication
# with the DLLs already being put there.
binaries.append((binary, "."))
2 changes: 1 addition & 1 deletion rf2_settings_wizard_win64_setup.iss
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES!

#define MyAppName "rF2-Settings-Widget"
#define MyAppVersion "0.7.32"
#define MyAppVersion "0.7.5"
#define MyAppPublisher "Stefan Tapper"
#define MyAppURL "https://ilikeviecher.com"
#define MyAppExeName "rF2-Settings-Widget.exe"
Expand Down
6 changes: 5 additions & 1 deletion rf2settings/app_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import sys
from pathlib import Path, WindowsPath
from shutil import copyfile
from typing import Iterator, Union
from typing import Iterator, Union, List

from .presets_dir import PresetDir, get_user_presets_dir
from .utils import JsonRepr
Expand All @@ -20,6 +20,10 @@ class AppSettings(JsonRepr):
selected_preset = str()
user_presets_dir = str()
deleted_defaults = list() # Default Presets the user deleted
server_favourites: List[list] = list()
server_browser: dict = {'filter_fav': False, 'filter_empty': False, 'filter_pwd': False, 'filter_version': False,
'filter_text': '', 'store_pwd': False}
server_passwords = dict()

def __init__(self):
self.backup_created = AppSettings.backup_created
Expand Down
6 changes: 3 additions & 3 deletions rf2settings/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
from subprocess import Popen
from typing import Optional

from .globals import KNOWN_APPS, get_fpsvr_dir
from .globals import get_fpsvr_dir, FPSVR_APPID
from .process import RunProcess
from .rfactor import RfactorPlayer
from .steam_utils import SteamApps
from .valve.steam_utils import SteamApps


class RunRfactorBenchmark:
Expand Down Expand Up @@ -106,7 +106,7 @@ def _index_fpsvr_csv_dir(self):

def _get_fpsvr_cmd(self):
s = SteamApps()
path = s.find_game_location([k for k in KNOWN_APPS.keys()][1])
path = s.find_game_location(FPSVR_APPID)

if path and path.exists():
self.fps_vr_cmd = path / 'fpsVRcmd.exe'
4 changes: 4 additions & 0 deletions rf2settings/globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
BASE_PATH = getattr(sys, '_MEIPASS', os.path.dirname(os.path.abspath(__file__ + '/..')))
RFACTOR_PLAYER = 'UserData/player/player.JSON'
RFACTOR_DXCONFIG = 'UserData/Config_DX11.ini'
RFACTOR_VERSION_TXT = 'Core/Version.txt'

GIT_RELEASE_URL = 'https://api.github.com/repos/tappi287/rf2_video_settings/releases/latest'

Expand All @@ -46,6 +47,9 @@
}
}

RF2_APPID = [k for k in KNOWN_APPS.keys()][0]
FPSVR_APPID = [k for k in KNOWN_APPS.keys()][1]

# Frozen or Debugger
if getattr(sys, 'frozen', False):
# -- Running in PyInstaller Bundle ---
Expand Down
60 changes: 51 additions & 9 deletions rf2settings/rfactor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
from subprocess import Popen
from typing import Optional

from .globals import RFACTOR_PLAYER, RFACTOR_DXCONFIG, KNOWN_APPS
from .globals import RFACTOR_PLAYER, RFACTOR_DXCONFIG, RF2_APPID, RFACTOR_VERSION_TXT
from .preset import Preset
from .settings_model import GraphicOptions, AdvancedGraphicSettings, VideoSettings, BaseOptions
from .steam_utils import SteamApps
from .valve.steam_utils import SteamApps

logging.basicConfig(stream=sys.stdout, format='%(asctime)s %(levelname)s: %(message)s',
datefmt='%H:%M', level=logging.DEBUG)
Expand All @@ -19,7 +19,8 @@ class RfactorLocation:
path: Path = None
player_json = Path()
dx_config = Path()
_app_id = [k for k in KNOWN_APPS.keys()][0]
version_txt = Path()
_app_id = RF2_APPID
is_valid = False

@classmethod
Expand All @@ -34,6 +35,7 @@ def get_location(cls, dev: Optional[bool] = False):
if path and path.exists():
player_json = path / RFACTOR_PLAYER
dx_config = path / RFACTOR_DXCONFIG
version_txt = path / RFACTOR_VERSION_TXT

if dev:
player_json = path / 'ModDev' / RFACTOR_PLAYER
Expand All @@ -44,18 +46,21 @@ def get_location(cls, dev: Optional[bool] = False):
cls.path = path
cls.player_json = player_json
cls.dx_config = dx_config
cls.version_txt = version_txt


class RfactorPlayer:
config_parser_args = {'inline_comment_prefixes': '//', 'default_section': 'COMPONENTS'}

def __init__(self, dev: Optional[bool] = None):
def __init__(self, dev: Optional[bool] = None, only_version: bool = False):
self.dev = dev or False
self.player_file = Path()
self.ini_file = Path()
self.ini_first_line = str()
self.ini_config = self._create_ini_config_parser()
self.location = Path('../modules')
self.version_file = Path()
self.version = ''

self.graphic_options = GraphicOptions()
self.advanced_graphic_options = AdvancedGraphicSettings()
Expand All @@ -64,17 +69,22 @@ def __init__(self, dev: Optional[bool] = None):
self.is_valid = False
self.error = ''

self.get_current_rfactor_settings()
self.get_current_rfactor_settings(only_version)

def _create_ini_config_parser(self):
config_parser = ConfigParser(**self.config_parser_args)
config_parser.optionxform = str
return config_parser

def get_current_rfactor_settings(self):
def get_current_rfactor_settings(self, only_version: bool = True):
""" Read all settings from the current rFactor 2 installation """
self._get_location()

if not self._update_version():
self.error += 'Could not read rFactor 2 version'
if only_version:
return

for preset_options in (self.graphic_options, self.advanced_graphic_options):
if not self._update_settings_from_player_json(preset_options):
self.error = 'Could not read rFactor2 player.JSON'
Expand Down Expand Up @@ -216,6 +226,18 @@ def _update_settings_from_player_json(self, preset_options: BaseOptions) -> bool

return settings_updated

def _update_version(self) -> bool:
if not self.version_file.exists():
return False
try:
with open(self.version_file, 'r') as f:
self.version = f.readline()
except Exception as e:
logging.error('Error reading version file: %s', e)
return False

return True

def _get_location(self):
if not RfactorLocation.is_valid:
RfactorLocation.get_location(self.dev)
Expand All @@ -226,16 +248,36 @@ def _get_location(self):
self.location = RfactorLocation.path
self.player_file = RfactorLocation.player_json
self.ini_file = RfactorLocation.dx_config
self.version_file = RfactorLocation.version_txt

def run_rfactor(self) -> bool:
if not self.location or not Path(self.location / 'Bin64').exists():
def _check_bin_dir(self) -> bool:
return self.location and Path(self.location / 'Bin64').exists()

def run_rfactor(self, server_info: Optional[dict] = None) -> bool:
if not self._check_bin_dir():
return False

# Solution for non loading rF2 plugins in PyInstaller executable:
# ctypes.windll.kernel32.SetDllDirectoryA(None)
# See https://github.com/pyinstaller/pyinstaller/wiki/Recipe-subprocess#windows-dll-loading-order

executable = self.location / "Bin64" / "rFactor2.exe"
Popen(executable, cwd=self.location)
cmd = [executable]

if server_info:
ip, port = server_info.get('address', ('localhost',))[0], server_info.get('port', '64297')
p = server_info.get('password')
cmd += ['+multiplayer', f'+connect={":" if p else ""}{p}{"@" if p else ""}{ip}:{port}']

logging.info('Launching %s', cmd)

Popen(cmd, cwd=self.location)

return True

def run_config(self) -> bool:
if not self._check_bin_dir():
return False
executable = self.location / "Bin64" / "rF Config.exe"
Popen(executable, cwd=self.location)
return True
Loading

0 comments on commit 58d0713

Please sign in to comment.