Add steamvr settings handling and downloading/deletion of ge proton

This commit is contained in:
Carsten Sprung 2024-07-02 00:51:40 +02:00
parent d54253d5f6
commit a0b2db7bde
2 changed files with 292 additions and 40 deletions

329
main.py
View file

@ -1,20 +1,58 @@
import json
import os import os
import re import re
import shutil
import subprocess import subprocess
import tarfile
import tempfile
import traceback import traceback
from pathlib import Path from pathlib import Path
from typing import Callable
import dearpygui.dearpygui as dpg import dearpygui.dearpygui as dpg
import requests
DPG_TAG_GE_PROTON_AVAILABLE = '__ge_proton_available'
DPG_TAG_GE_PROTON_INSTALLED = '__ge_proton_installed'
DPG_TAG_LOADING_POPUP = '__loading_popup'
DPG_TAG_LOADING_POPUP_TITLE = '__loading_popup_title'
DPG_TAG_LOADING_POPUP_MESSAGE = '__loading_popup_message'
DPG_TAG_ALERT_POPUP = '__alert_popup'
DPG_TAG_ALERT_POPUP_TITLE = '__alert_popup_title'
DPG_TAG_ALERT_POPUP_MESSAGE = '__alert_popup_message'
DPG_TAG_ALERT_POPUP_CONFIRM = '__alert_popup_confirm'
DPG_TAG_ALERT_POPUP_DISMISS = '__alert_popup_dismiss'
DPG_TAG_CURRENT_PAGE = '__current_page'
GPU_POWER_PROFILE_MODE_FILE = '/sys/class/drm/card0/device/pp_power_profile_mode'
GPU_PERFORMANCE_LEVEL_FILE = '/sys/class/drm/card0/device/power_dpm_force_performance_level'
STEAM_VR_SETTINGS_FILE = '{base_dir}/config/steamvr.vrsettings'
GE_PROTON_GITHUB = 'GloriousEggroll/proton-ge-custom'
GE_PROTON_INSTALL_DIR = '{base_dir}/compatibilitytools.d'
gpu_options = [] gpu_options = []
steam_vr_options = [] steam_vr_options = []
ge_proton_options = []
page_links = {
'first': '',
'prev': '',
'next': '',
'last': ''
}
max_page = '0'
curr_page = '0'
# region general stuff # region general stuff
def _log(sender, app_data, user_data): def _loaded(option, value=None):
print(f'sender: {sender}, \t app_data: {app_data}, \t user_data: {user_data}') print(f'Loaded {option}' + (f': {value}' if value is not None else ''))
def _store(option, value):
print(f'Store {option}: {value}')
def write_to_file(file, value): def write_to_file(file, value):
@ -38,6 +76,28 @@ def update_options(options):
for (item, value_getter) in options: for (item, value_getter) in options:
dpg.set_value(item, value_getter()) dpg.set_value(item, value_getter())
def _loading_popup(show=True, title='', message=''):
dpg.set_value(DPG_TAG_LOADING_POPUP_TITLE, title)
dpg.set_value(DPG_TAG_LOADING_POPUP_MESSAGE, message)
dpg.configure_item(DPG_TAG_LOADING_POPUP, show=show)
def _alert_popup(
show=True,
title='',
message='',
confirm='OK',
dismiss='Cancel',
on_confirm: Callable = None,
on_dismiss: Callable = None
):
dpg.set_value(DPG_TAG_ALERT_POPUP_TITLE, title)
dpg.set_value(DPG_TAG_ALERT_POPUP_MESSAGE, message)
dpg.configure_item(DPG_TAG_ALERT_POPUP_CONFIRM, label=confirm, callback=on_confirm, show=on_confirm is not None)
dpg.configure_item(DPG_TAG_ALERT_POPUP_DISMISS, label=dismiss, callback=on_dismiss, show=on_dismiss is not None)
dpg.configure_item(DPG_TAG_ALERT_POPUP, show=show)
# endregion # endregion
@ -52,7 +112,6 @@ def get_steam_dir():
for path in possible_paths: for path in possible_paths:
candidate = (Path.home() / Path(path)).resolve() candidate = (Path.home() / Path(path)).resolve()
if candidate.exists(): if candidate.exists():
print(str(candidate))
return str(candidate) return str(candidate)
return 'No Steam installation found!' return 'No Steam installation found!'
@ -63,7 +122,7 @@ def get_steam_dir():
def _raw_gpu_profiles(): def _raw_gpu_profiles():
rex = re.compile('^ *(\\d) +(\\w+)([ *]):$', flags=re.MULTILINE) rex = re.compile('^ *(\\d) +(\\w+)([ *]):$', flags=re.MULTILINE)
with open('/sys/class/drm/card0/device/pp_power_profile_mode', 'r') as f: with open(GPU_POWER_PROFILE_MODE_FILE, 'r') as f:
matches = re.findall(rex, f.read()) matches = re.findall(rex, f.read())
return matches return matches
@ -74,15 +133,15 @@ def get_available_gpu_profiles():
def get_selected_gpu_profile(): def get_selected_gpu_profile():
active_profile = next(filter(lambda profile: profile[2] == '*', _raw_gpu_profiles()), None) active_profile = next(filter(lambda profile: profile[2] == '*', _raw_gpu_profiles()), None)
_loaded('GPU Profile', active_profile)
return active_profile[1] if active_profile is not None else None return active_profile[1] if active_profile is not None else None
def select_gpu_profile(sender, app_data, user_data): def select_gpu_profile(_sender, app_data, _user_data):
_log(sender, app_data, user_data)
selected_profile = next(filter(lambda profile: profile[1] == app_data, _raw_gpu_profiles()), None) selected_profile = next(filter(lambda profile: profile[1] == app_data, _raw_gpu_profiles()), None)
print(f'selecting power profile {selected_profile}') _store('GPU Profile', selected_profile)
if selected_profile is not None: if selected_profile is not None:
write_to_file('/sys/class/drm/card0/device/pp_power_profile_mode', selected_profile[0]) write_to_file(GPU_POWER_PROFILE_MODE_FILE, selected_profile[0])
update_options(gpu_options) update_options(gpu_options)
@ -91,16 +150,16 @@ def get_available_gpu_levels():
def get_selected_gpu_level(): def get_selected_gpu_level():
with open('/sys/class/drm/card0/device/power_dpm_force_performance_level', 'r') as f: with open(GPU_PERFORMANCE_LEVEL_FILE, 'r') as f:
level = f.readline() level = f.readline().strip()
_loaded('GPU Level', level)
return level return level
def select_gpu_level(sender, level, user_data): def select_gpu_level(_sender, level, _user_data):
_log(sender, level, user_data) _store('GPU Level', level)
print(f'selecting power profile {level}')
if level is not None: if level is not None:
write_to_file('/sys/class/drm/card0/device/power_dpm_force_performance_level', level) write_to_file(GPU_PERFORMANCE_LEVEL_FILE, level)
update_options(gpu_options) update_options(gpu_options)
# endregion # endregion
@ -109,31 +168,145 @@ def select_gpu_level(sender, level, user_data):
# region steam vr # region steam vr
def is_vulkan_async_enabled(): def is_vulkan_async_enabled():
return True with open(STEAM_VR_SETTINGS_FILE.format(base_dir=get_steam_dir()), 'r') as f:
data_content = json.load(f)
enabled = data_content['steamvr']['enableLinuxVulkanAsync']
_loaded('SteamVR enableLinuxVulkanAsync', enabled)
return enabled
def enable_vulkan_async(sender, enabled, user_data): def enable_vulkan_async(_sender, enabled, _user_data):
_log(sender, enabled, user_data) _store('SteamVR enableLinuxVulkanAsync', enabled)
file_path = STEAM_VR_SETTINGS_FILE.format(base_dir=get_steam_dir())
with open(file_path, 'r') as f:
data_content = json.load(f)
data_content['steamvr']['enableLinuxVulkanAsync'] = enabled
with open(file_path, 'w') as f:
json.dump(data_content, f, indent=3, separators=(',', ' : '))
update_options(steam_vr_options)
# endregion # endregion
# region ge proton # region ge proton
def update_installed_ge_proton():
dpg.configure_item(DPG_TAG_GE_PROTON_INSTALLED, items=get_installed_ge_protons())
def get_installed_ge_protons(): def get_installed_ge_protons():
return tuple(sorted(['7-55', '8-2', '8-9', '9-7'], reverse=True)) path = GE_PROTON_INSTALL_DIR.format(base_dir=get_steam_dir())
installed = os.listdir(path)
sorted_versions = tuple(sorted(installed, reverse=True))
_loaded('GE Proton installed', sorted_versions)
return sorted_versions
def get_available_ge_protons(): def delete_installed_ge_proton():
return tuple(sorted(['6-42', '7-55', '7-99', '7-6', '7-5', '8-2', '8-6', '8-9', '9-7'], reverse=True)) version = dpg.get_value(DPG_TAG_GE_PROTON_INSTALLED)
install_path = f'{GE_PROTON_INSTALL_DIR.format(base_dir=get_steam_dir())}/{version}'
_alert_popup(
title=f'Delete {version}?',
message=f'Will remove\n{install_path}',
confirm='Delete',
on_confirm=lambda: _confirmed_delete_installed_ge_proton(install_path),
on_dismiss=lambda: _alert_popup(show=False)
)
def install_ge_proton(sender, app_data, user_data): def _confirmed_delete_installed_ge_proton(version):
_log(sender, app_data, user_data) _alert_popup(show=False)
_loading_popup(title='Deleting...', message=version)
print(f'Deleting {version}')
shutil.rmtree(version)
update_installed_ge_proton()
print('Done deleting')
_loading_popup(show=False)
def delete_ge_proton(sender, app_data, user_data): def update_available_ge_protons():
_log(sender, app_data, user_data) global curr_page
global max_page
curr_page = 1
max_page = 1
dpg.configure_item(DPG_TAG_GE_PROTON_AVAILABLE, items=get_available_ge_protons())
def _update_page_links(links):
global curr_page
global max_page
matches = re.findall('<(\\S+)>; rel=\\"(\\w+)\\"', links)
for link in page_links:
page_links[link] = ''
for match in matches:
page_links[match[1]] = match[0]
if page_links['last'] != '':
max_page = page_links['last'].split('=')[1]
dpg.set_value(DPG_TAG_CURRENT_PAGE, f'{curr_page}/{max_page}')
def switch_page(sender, app_data, user_data):
global curr_page
print(f'Switching page {user_data}')
link = page_links[user_data]
if link != '':
curr_page = link.split('=')[1]
dpg.configure_item(DPG_TAG_GE_PROTON_AVAILABLE, items=get_available_ge_protons(link))
def get_available_ge_protons(link=None):
url = link or f'https://api.github.com/repos/{GE_PROTON_GITHUB}/releases'
response = requests.get(url)
_update_page_links(response.headers['Link'])
releases = response.json()
release_names = list(map(lambda release: release['tag_name'], releases))
sorted_releases = tuple(sorted(release_names, reverse=True))
_loaded('GE Proton releases', sorted_releases)
return sorted_releases
def install_ge_proton():
version = dpg.get_value(DPG_TAG_GE_PROTON_AVAILABLE)
url = f'https://github.com/GloriousEggroll/proton-ge-custom/releases/download/{version}/{version}.tar.gz'
tar_file_path = f'{tempfile.gettempdir()}/{version}.tar.gz'
target_extract_path = GE_PROTON_INSTALL_DIR.format(base_dir=get_steam_dir())
if version is None or version == '':
_alert_popup(
title=f'No version selected!',
message='Reload the release list and select a version to install.',
on_confirm=lambda: _alert_popup(show=False)
)
return
if version in get_installed_ge_protons():
_alert_popup(
title=f'Version {version} already installed!',
message='To download again, delete it first',
on_confirm=lambda: _alert_popup(show=False)
)
return
print(f'Loading {url} to {tar_file_path}')
title = f'Installing {version}'
_loading_popup(title=title, message='Downloading...')
response = requests.get(url, stream=True)
if response.status_code == 200:
with open(tar_file_path, 'wb') as temp_file:
temp_file.write(response.raw.read())
print('Download complete, start extracting...')
_loading_popup(title=title, message='Extracting...')
with tarfile.open(tar_file_path) as tar_file:
tar_file.extractall(target_extract_path)
print('Done installing')
else:
print(response.status_code)
print(response.text)
if Path(tar_file_path).exists():
os.remove(tar_file_path)
_loading_popup(show=False)
update_installed_ge_proton()
dpg.set_value(DPG_TAG_GE_PROTON_INSTALLED, version)
# endregion # endregion
@ -144,14 +317,55 @@ def show_window():
dpg.create_context() dpg.create_context()
dpg.create_viewport(title='VRUtil', width=650, height=500) dpg.create_viewport(title='VRUtil', width=650, height=500)
with dpg.window(
tag=DPG_TAG_LOADING_POPUP,
min_size=(200, 10),
pos=(225, 175),
modal=True,
show=False,
no_title_bar=True,
no_resize=True,
no_move=True
):
with dpg.group(horizontal=True):
dpg.add_loading_indicator()
with dpg.group():
dpg.add_text('title', tag=DPG_TAG_LOADING_POPUP_TITLE)
dpg.add_text('message', tag=DPG_TAG_LOADING_POPUP_MESSAGE)
with dpg.window(
tag=DPG_TAG_ALERT_POPUP,
min_size=(200, 30),
pos=(225, 175),
modal=True,
show=False,
no_title_bar=True,
no_resize=True,
no_move=True
):
with dpg.group(horizontal=True):
dpg.add_loading_indicator(
circle_count=3,
color=(240, 140, 0, 255),
secondary_color=(180, 100, 0, 255)
)
with dpg.group():
dpg.add_text('title', tag=DPG_TAG_ALERT_POPUP_TITLE)
dpg.add_text('message', tag=DPG_TAG_ALERT_POPUP_MESSAGE)
dpg.add_spacer(height=10)
with dpg.group(horizontal=True):
dpg.add_button(tag=DPG_TAG_ALERT_POPUP_DISMISS, label='Cancel')
dpg.add_button(tag=DPG_TAG_ALERT_POPUP_CONFIRM, label='OK')
with dpg.window(tag='__main_window'): with dpg.window(tag='__main_window'):
dpg.add_text('Current Steam dir:') dpg.add_text('Current Steam dir:')
dpg.add_text(get_steam_dir()) dpg.add_text(get_steam_dir())
dpg.add_spacer(height=10)
# region GPU # region GPU
dpg.add_spacer(height=10)
with dpg.collapsing_header(label='GPU', default_open=True): with dpg.collapsing_header(label='GPU', default_open=True):
gpu_options.append(( gpu_options.append((
@ -175,11 +389,12 @@ def show_window():
get_selected_gpu_level get_selected_gpu_level
)) ))
dpg.add_spacer(height=10)
# endregion # endregion
# region STEAM VR # region STEAM VR
dpg.add_spacer(height=10)
with dpg.collapsing_header(label='Steam VR', default_open=True): with dpg.collapsing_header(label='Steam VR', default_open=True):
steam_vr_options.append(( steam_vr_options.append((
@ -191,11 +406,12 @@ def show_window():
is_vulkan_async_enabled is_vulkan_async_enabled
)) ))
dpg.add_spacer(height=10)
# endregion # endregion
# region GE PROTON # region GE PROTON
dpg.add_spacer(height=10)
with dpg.collapsing_header(label='GE Proton', default_open=True): with dpg.collapsing_header(label='GE Proton', default_open=True):
with dpg.group(horizontal=True): with dpg.group(horizontal=True):
@ -204,11 +420,13 @@ def show_window():
with dpg.group(horizontal=False): with dpg.group(horizontal=False):
dpg.add_text('Installed versions:') dpg.add_text('Installed versions:')
dpg.add_button(label='Delete', callback=_log) dpg.add_button(label='Delete', callback=delete_installed_ge_proton)
ge_proton_options.append(( dpg.add_listbox(
dpg.add_listbox(items=get_installed_ge_protons(), width=250, num_items=8, callback=_log), tag=DPG_TAG_GE_PROTON_INSTALLED,
get_installed_ge_protons items=get_installed_ge_protons(),
)) width=250,
num_items=8
)
# AVAILABLE # AVAILABLE
@ -216,13 +434,46 @@ def show_window():
dpg.add_text('Available versions:') dpg.add_text('Available versions:')
with dpg.group(horizontal=True): with dpg.group(horizontal=True):
dpg.add_button(label='Reload', callback=_log) dpg.add_button(label='Install', callback=install_ge_proton)
dpg.add_button(label='Install', callback=_log) dpg.add_button(label='Reload', callback=update_available_ge_protons)
dpg.add_button(
label='First',
arrow=True,
direction=dpg.mvDir_Left,
user_data='first',
callback=switch_page
)
dpg.add_button(
label='Prev',
arrow=True,
direction=dpg.mvDir_Left,
user_data='prev',
callback=switch_page
)
dpg.add_text('0/0', tag=DPG_TAG_CURRENT_PAGE)
dpg.add_button(
label='Next',
arrow=True,
direction=dpg.mvDir_Right,
user_data='next',
callback=switch_page
)
dpg.add_button(
label='Last',
arrow=True,
direction=dpg.mvDir_Right,
user_data='last',
callback=switch_page
)
ge_proton_options.append(( dpg.add_listbox(
dpg.add_listbox(items=get_available_ge_protons(), width=250, num_items=8, callback=_log), tag=DPG_TAG_GE_PROTON_AVAILABLE,
get_available_ge_protons items=(),
)) width=250,
num_items=8
),
dpg.add_spacer(height=10)
# endregion # endregion

View file

@ -1 +1,2 @@
dearpygui dearpygui~=1.11.1
requests~=2.32.3