Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b60a79ae3
|
No files matched your search
@@ -2428,8 +2428,9 @@ pw_specialk_set_ini_value () {
|
||||
[[ -f "$ini_file" ]] && sed -i '/./,$!d' "$ini_file"
|
||||
|
||||
if grep -q "^\[$section\]" "$ini_file" ; then
|
||||
if sed -n "/^\[$section\]/,/^\[/{p}" "$ini_file" | grep -q "^$key=" ; then
|
||||
sed -i "/^\[$section\]/,/^\[/{s/^$key=.*/$key=$value/}" "$ini_file"
|
||||
if sed -n "/^\[$section\]/,/^\[/{p}" "$ini_file" \
|
||||
| grep -q "^[[:space:]]*$key[[:space:]]*=" ; then
|
||||
sed -i "/^\[$section\]/,/^\[/{s/^[[:space:]]*$key[[:space:]]*=.*/$key=$value/}" "$ini_file"
|
||||
else
|
||||
sed -i "/^\[$section\]/a $key=$value" "$ini_file"
|
||||
fi
|
||||
@@ -2522,6 +2523,171 @@ pw_specialk_sync_files () {
|
||||
|
||||
}
|
||||
|
||||
pw_reshade_sync_files () {
|
||||
local root="${PW_PLUGINS_PATH}/reshade_windows" runtime detect arch dll highest ogl vulkan source
|
||||
local manifest_reg package installed installed_arch installed_version extract_dir reshade_exe reshade_font font_win screenshot_path backup
|
||||
local PATH_TO_GAME="${PATH_TO_GAME:-}"
|
||||
runtime="$root/reshade/${PW_RESHADE_VER}_Addon"
|
||||
reshade_exe="${PW_RESHADE_EXE:-$PW_EXE_FILE}"
|
||||
if [[ ! -f "$reshade_exe" ]] ; then
|
||||
print_warning "ReShade executable not found: $reshade_exe. Using $PW_EXE_FILE."
|
||||
reshade_exe="$PW_EXE_FILE"
|
||||
fi
|
||||
if [[ "$PW_USE_RESHADE" == 1 ]] ; then
|
||||
detect=$(python_module graphics_detector "$reshade_exe")
|
||||
file "$reshade_exe" | grep -qE 'x86-64|PE32\+' && arch=64 || arch=32
|
||||
highest=$(echo "$detect" | grep '^HIGHEST_DX=' | cut -d= -f2)
|
||||
ogl=$(echo "$detect" | grep '^USES_OGL=' | cut -d= -f2)
|
||||
vulkan=$(echo "$detect" | grep '^USES_VULKAN=' | cut -d= -f2)
|
||||
source=$(echo "$detect" | grep '^SOURCE=' | cut -d= -f2-)
|
||||
[[ "${source##*/}" == UnityPlayer.dll && "$highest" != None ]] && vulkan=false
|
||||
fi
|
||||
if [[ -n "$PW_RESHADE_EXE" ]] ; then
|
||||
PATH_TO_GAME="$(dirname "$(realpath "$reshade_exe")")"
|
||||
fi
|
||||
if [[ "$PW_USE_RESHADE" == 1 ]] \
|
||||
&& [[ ! -f "$runtime/ReShade32.dll" || ! -f "$runtime/ReShade32.json" \
|
||||
|| ! -f "$runtime/ReShade64.dll" || ! -f "$runtime/ReShade64.json" ]] ; then
|
||||
try_download "https://reshade.me/downloads/ReShade_Setup_${PW_RESHADE_VER}_Addon.exe" \
|
||||
"${PORT_DATA_PATH}/data/tmp/ReShade_Setup_${PW_RESHADE_VER}_Addon.exe"
|
||||
create_new_dir "$runtime"
|
||||
unpack "${PORT_DATA_PATH}/data/tmp/ReShade_Setup_${PW_RESHADE_VER}_Addon.exe" \
|
||||
"$runtime" silent || fatal "Failed to extract ReShade."
|
||||
try_remove_file "${PORT_DATA_PATH}/data/tmp/ReShade_Setup_${PW_RESHADE_VER}_Addon.exe"
|
||||
fi
|
||||
if [[ "$PW_USE_RESHADE" == 1 && ! -f "$root/d3dcompiler_47.dll" ]] ; then
|
||||
try_download "https://github.com/itsOwen/LetMeReShade/releases/download/LetMeReShade_Binaries/d3dcompiler_47.dll" "$root/d3dcompiler_47.dll"
|
||||
fi
|
||||
if [[ "$PW_USE_RESHADE" == 1 && ! -f "$root/d3d8.dll" ]] ; then
|
||||
try_download "https://github.com/crosire/d3d8to9/releases/download/v${PW_D3D8TO9_VER}/d3d8.dll" "$root/d3d8.dll"
|
||||
fi
|
||||
if [[ "$PW_USE_RESHADE" == 1 && ! -d "$root/shaders/Shaders" ]] ; then
|
||||
try_remove_dir "$root/shaders"
|
||||
create_new_dir "$root/shaders/Shaders"
|
||||
create_new_dir "$root/shaders/Textures"
|
||||
for package in reshade_shaders sweetfx_shaders immerse_shaders astrayfx_shaders prod80_shaders retroarch_shaders ; do
|
||||
extract_dir="${PORT_DATA_PATH}/data/tmp/$package"
|
||||
if [[ "$package" == immerse_shaders ]] ; then
|
||||
try_download "https://github.com/martymcmodding/iMMERSE/archive/refs/heads/main.tar.gz" \
|
||||
"${PORT_DATA_PATH}/data/tmp/${package}.tar.gz"
|
||||
else
|
||||
try_download "https://github.com/itsOwen/LetMeReShade/releases/download/LetMeReShade_Binaries/${package}.tar.gz" \
|
||||
"${PORT_DATA_PATH}/data/tmp/${package}.tar.gz"
|
||||
fi
|
||||
try_remove_dir "$extract_dir"
|
||||
create_new_dir "$extract_dir"
|
||||
unpack "${PORT_DATA_PATH}/data/tmp/${package}.tar.gz" "$extract_dir" \
|
||||
silent || fatal "Failed to extract $package."
|
||||
find "$extract_dir" -type d -iname Shaders -exec cp -rf '{}/.' "$root/shaders/Shaders/" \;
|
||||
find "$extract_dir" -type d -iname Textures -exec cp -rf '{}/.' "$root/shaders/Textures/" \;
|
||||
try_remove_dir "$extract_dir"
|
||||
try_remove_file "${PORT_DATA_PATH}/data/tmp/${package}.tar.gz"
|
||||
done
|
||||
fi
|
||||
if [[ "$PW_USE_RESHADE" == 1 && "$vulkan" == true ]] ; then
|
||||
dll="Vulkan${arch}"
|
||||
elif [[ "$PW_USE_RESHADE" == 1 ]] ; then
|
||||
case "$highest" in
|
||||
"DirectX 10"|"DirectX 11"|"DirectX 12") dll=dxgi.dll ;;
|
||||
"DirectX 8"|"DirectX 9") dll=d3d9.dll ;;
|
||||
"DirectDraw") return 0 ;;
|
||||
*) [[ "$ogl" == true ]] && dll=opengl32.dll || dll=dxgi.dll ;;
|
||||
esac
|
||||
fi
|
||||
case "$PW_RESHADE_API" in
|
||||
vulkan) vulkan=true; dll="Vulkan${arch}" ;;
|
||||
dxgi.dll|d3d9.dll|opengl32.dll) vulkan=false; dll="$PW_RESHADE_API" ;;
|
||||
esac
|
||||
if [[ -f "$PATH_TO_GAME/reshade_version" ]] \
|
||||
&& { [[ "$PW_USE_RESHADE" != 1 ]] \
|
||||
|| [[ "$(<"$PATH_TO_GAME/reshade_version")" != "${PW_RESHADE_VER}_Addon"$'\n'"$dll" ]]; } ; then
|
||||
installed_version=$(sed -n '1p' "$PATH_TO_GAME/reshade_version")
|
||||
installed=$(sed -n '2p' "$PATH_TO_GAME/reshade_version")
|
||||
if [[ "$installed" == Vulkan32 || "$installed" == Vulkan64 ]] ; then
|
||||
installed_arch=${installed#Vulkan}
|
||||
manifest_reg="Z:${root//\//\\}\\reshade\\${installed_version}\\ReShade${installed_arch}.json"
|
||||
manifest_reg=${manifest_reg//\\/\\\\\\\\}
|
||||
get_and_set_reg_file --delete 'Software\Khronos\Vulkan\ImplicitLayers' \
|
||||
"$manifest_reg" "" "" user
|
||||
if [[ "$installed_arch" == 32 ]] ; then
|
||||
get_and_set_reg_file --delete 'Software\Wow6432Node\Khronos\Vulkan\ImplicitLayers' \
|
||||
"$manifest_reg" "" "" system
|
||||
else
|
||||
get_and_set_reg_file --delete 'Software\Khronos\Vulkan\ImplicitLayers' \
|
||||
"$manifest_reg" "" "" system
|
||||
fi
|
||||
fi
|
||||
try_remove_file "$PATH_TO_GAME/$installed"
|
||||
if [[ -L "$PATH_TO_GAME/${installed}.b" \
|
||||
&& $(readlink -f "$PATH_TO_GAME/${installed}.b") == "$root/reshade/"* ]] ; then
|
||||
unlink "$PATH_TO_GAME/${installed}.b"
|
||||
fi
|
||||
[[ -e "$PATH_TO_GAME/${installed}.b" ]] \
|
||||
&& mv -f "$PATH_TO_GAME/${installed}.b" "$PATH_TO_GAME/$installed"
|
||||
[[ -L "$PATH_TO_GAME/d3dcompiler_47.dll" ]] && unlink "$PATH_TO_GAME/d3dcompiler_47.dll"
|
||||
[[ -L "$PATH_TO_GAME/d3d8.dll" ]] && unlink "$PATH_TO_GAME/d3d8.dll"
|
||||
[[ -e "$PATH_TO_GAME/d3d8.dll.b" ]] \
|
||||
&& mv -f "$PATH_TO_GAME/d3d8.dll.b" "$PATH_TO_GAME/d3d8.dll"
|
||||
[[ -L "$PATH_TO_GAME/ReShade_shaders" ]] && unlink "$PATH_TO_GAME/ReShade_shaders"
|
||||
try_remove_file "$PATH_TO_GAME/ReShade.ini"
|
||||
try_remove_file "$PATH_TO_GAME/ReShadePreset.ini"
|
||||
try_remove_file "$PATH_TO_GAME/ReShade.log"
|
||||
try_remove_file "$PATH_TO_GAME/reshade_version"
|
||||
fi
|
||||
[[ "$PW_USE_RESHADE" != 1 ]] && return 0
|
||||
[[ -L "$PATH_TO_GAME/ReShade_shaders" ]] && unlink "$PATH_TO_GAME/ReShade_shaders"
|
||||
try_force_link_dir "$root/shaders" "$PATH_TO_GAME/ReShade_shaders"
|
||||
pw_specialk_set_ini_value "$PATH_TO_GAME/ReShade.ini" "GENERAL" "EffectSearchPaths" '.\\ReShade_shaders\\Shaders'
|
||||
pw_specialk_set_ini_value "$PATH_TO_GAME/ReShade.ini" "GENERAL" "TextureSearchPaths" '.\\ReShade_shaders\\Textures'
|
||||
if [[ -n "$PW_RESHADE_FONT" ]] ; then
|
||||
reshade_font=$(fc-match --format='%{file}' "$PW_RESHADE_FONT")
|
||||
else
|
||||
reshade_font=/usr/share/fonts/ttf/dejavu/DejaVuSans.ttf
|
||||
fi
|
||||
if [[ -f "$reshade_font" ]] ; then
|
||||
font_win="Z:${reshade_font//\//\\\\}"
|
||||
pw_specialk_set_ini_value "$PATH_TO_GAME/ReShade.ini" "STYLE" "Font" "$font_win"
|
||||
pw_specialk_set_ini_value "$PATH_TO_GAME/ReShade.ini" "STYLE" "EditorFont" "$font_win"
|
||||
elif [[ -n "$PW_RESHADE_FONT" ]] ; then
|
||||
print_warning "ReShade font not found: $PW_RESHADE_FONT"
|
||||
fi
|
||||
screenshot_path='.\\'
|
||||
if [[ -n "$PW_RESHADE_SCREENSHOT_PATH" ]] ; then
|
||||
create_new_dir "$PW_RESHADE_SCREENSHOT_PATH"
|
||||
screenshot_path="Z:${PW_RESHADE_SCREENSHOT_PATH//\//\\\\}"
|
||||
fi
|
||||
pw_specialk_set_ini_value "$PATH_TO_GAME/ReShade.ini" "INPUT" "KeyScreenshot" "$PW_RESHADE_SCREENSHOT_KEY"
|
||||
pw_specialk_set_ini_value "$PATH_TO_GAME/ReShade.ini" "INPUT" "KeyOverlay" "$PW_RESHADE_OVERLAY_KEY"
|
||||
pw_specialk_set_ini_value "$PATH_TO_GAME/ReShade.ini" "SCREENSHOT" "SavePath" "$screenshot_path"
|
||||
if [[ "$vulkan" == true ]] ; then
|
||||
manifest_reg="Z:${runtime//\//\\}\\ReShade${arch}.json"
|
||||
manifest_reg=${manifest_reg//\\/\\\\\\\\}
|
||||
get_and_set_reg_file --add 'Software\Khronos\Vulkan\ImplicitLayers' "$manifest_reg" REG_DWORD 0 user
|
||||
if [[ "$arch" == 32 ]] ; then
|
||||
get_and_set_reg_file --add 'Software\Wow6432Node\Khronos\Vulkan\ImplicitLayers' "$manifest_reg" REG_DWORD 0 system
|
||||
else
|
||||
get_and_set_reg_file --add 'Software\Khronos\Vulkan\ImplicitLayers' "$manifest_reg" REG_DWORD 0 system
|
||||
fi
|
||||
var_winedlloverride_update "vulkan-1=n,b"
|
||||
printf '%s\n%s\n' "${PW_RESHADE_VER}_Addon" "$dll" > "$PATH_TO_GAME/reshade_version"
|
||||
return 0
|
||||
fi
|
||||
[[ ! -f "$PATH_TO_GAME/reshade_version" \
|
||||
&& -e "$PATH_TO_GAME/$dll" && ! -e "$PATH_TO_GAME/${dll}.b" \
|
||||
&& ( ! -L "$PATH_TO_GAME/$dll" \
|
||||
|| $(readlink -f "$PATH_TO_GAME/$dll") != "$root/reshade/"* ) ]] \
|
||||
&& mv -f "$PATH_TO_GAME/$dll" "$PATH_TO_GAME/${dll}.b"
|
||||
try_force_link_file "$runtime/ReShade${arch}.dll" "$PATH_TO_GAME/$dll"
|
||||
try_force_link_file "$root/d3dcompiler_47.dll" "$PATH_TO_GAME/d3dcompiler_47.dll"
|
||||
var_winedlloverride_update "d3dcompiler_47=n;${dll%.*}=n,b"
|
||||
if [[ "$highest" == "DirectX 8" && "$dll" == d3d9.dll ]] ; then
|
||||
[[ -e "$PATH_TO_GAME/d3d8.dll" && ! -e "$PATH_TO_GAME/d3d8.dll.b" ]] && mv -f "$PATH_TO_GAME/d3d8.dll" "$PATH_TO_GAME/d3d8.dll.b"
|
||||
try_force_link_file "$root/d3d8.dll" "$PATH_TO_GAME/d3d8.dll"
|
||||
var_winedlloverride_update "d3d8=n,b"
|
||||
fi
|
||||
printf '%s\n%s\n' "${PW_RESHADE_VER}_Addon" "$dll" > "$PATH_TO_GAME/reshade_version"
|
||||
}
|
||||
|
||||
pw_prepare_gamescope_launch () {
|
||||
[[ "$PW_USE_NATIVE_WAYLAND" == "1" ]] && export PW_GAMESCOPE="0"
|
||||
|
||||
@@ -2957,6 +3123,7 @@ start_portproton () {
|
||||
|
||||
pw_optiscaler_sync_files
|
||||
pw_specialk_sync_files
|
||||
pw_reshade_sync_files
|
||||
|
||||
if [[ "${PW_USE_RAY_TRACING}" == "1" ]] ; then
|
||||
var_vkd3d_config_update dxr
|
||||
@@ -4052,7 +4219,6 @@ steamplay_launch () {
|
||||
|
||||
pw_optiscaler_sync_files
|
||||
pw_specialk_sync_files
|
||||
|
||||
PORT_WINE_PREFIX="$STEAM_COMPAT_DATA_PATH/pfx"
|
||||
|
||||
pw_prepare_gamescope_launch
|
||||
@@ -4118,6 +4284,8 @@ steamplay_launch () {
|
||||
fi
|
||||
fi
|
||||
|
||||
pw_reshade_sync_files
|
||||
|
||||
# Winetricks
|
||||
if [[ -n "${PW_DLL_INSTALL:-}" ]] ; then
|
||||
update_winetricks
|
||||
@@ -4331,7 +4499,9 @@ gui_edit_db () {
|
||||
KEY_EDIT_DB_GUI=$RANDOM
|
||||
DISABLE_EDIT_DB_LIST=""
|
||||
PW_EDIT_DB_LIST=(PW_MANGOHUD PW_MANGOHUD_USER_CONF PW_VKBASALT PW_VKBASALT_USER_CONF
|
||||
PW_VKBASALT_EFFECTS PW_VKBASALT_FFX_CAS PW_DGVOODOO2 PW_USE_SPECIALK
|
||||
PW_VKBASALT_EFFECTS PW_VKBASALT_FFX_CAS PW_DGVOODOO2 PW_USE_SPECIALK PW_USE_RESHADE
|
||||
PW_RESHADE_EXE PW_RESHADE_API PW_RESHADE_FONT PW_RESHADE_SCREENSHOT_PATH
|
||||
PW_RESHADE_SCREENSHOT_KEY PW_RESHADE_OVERLAY_KEY
|
||||
PW_USE_ESYNC PW_USE_FSYNC PW_USE_NTSYNC PW_USE_RAY_TRACING PW_USE_NVAPI_AND_DLSS PW_USE_OPTISCALER
|
||||
PW_USE_LS_FRAME_GEN PW_WINE_FULLSCREEN_FSR PW_HIDE_NVIDIA_GPU PW_VIRTUAL_DESKTOP PW_USE_TERMINAL
|
||||
PW_USE_GAMEMODE PW_USE_INHIBIT_SLEEP PW_USE_D3D_EXTRAS PW_FIX_VIDEO_IN_GAME PW_REDUCE_PULSE_LATENCY
|
||||
@@ -4365,6 +4535,10 @@ gui_edit_db () {
|
||||
DISABLE_EDIT_DB_LIST+=" PW_VKBASALT PW_VKBASALT_USER_CONF PW_USE_RAY_TRACING PW_USE_OBS_VKCAPTURE PW_USE_GALLIUM_ZINK PW_USE_WINED3D_VULKAN"
|
||||
fi
|
||||
[[ "${PW_USE_SPECIALK}" == "1" ]] && DISABLE_EDIT_DB_LIST+=" PW_DGVOODOO2"
|
||||
[[ "${PW_USE_RESHADE}" == "1" ]] && DISABLE_EDIT_DB_LIST+=" PW_USE_OPTISCALER PW_USE_SPECIALK"
|
||||
if [[ "${PW_USE_OPTISCALER}" == "1" || "${PW_USE_SPECIALK}" == "1" ]] ; then
|
||||
DISABLE_EDIT_DB_LIST+=" PW_USE_RESHADE"
|
||||
fi
|
||||
|
||||
[[ ! -e "/dev/ntsync" ]] && DISABLE_EDIT_DB_LIST+=" PW_USE_NTSYNC"
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@ export VKD3D_SAREK_VER="sarek-2.6.0"
|
||||
export PW_LSFG_VK_VER="2.0.0-dev25-p11"
|
||||
export PW_OPTISCALER_VER="v03-0.9.0-pre8"
|
||||
export PW_SPECIALK_VER="SK_26_5_15"
|
||||
export PW_RESHADE_VER="6.8.0"
|
||||
export PW_D3D8TO9_VER="1.15.1"
|
||||
export PW_GALLIUM_NINE_VER="0.10"
|
||||
export DOTPFX_VER="48v9"
|
||||
export DEFPFX_VER="v1"
|
||||
@@ -33,6 +35,13 @@ check_variables PROTON_USE_XALIA "0"
|
||||
check_variables PW_USE_RUNTIME "1"
|
||||
check_variables PW_USE_D7VK "0"
|
||||
check_variables PW_USE_SPECIALK "0"
|
||||
check_variables PW_USE_RESHADE "0"
|
||||
check_variables PW_RESHADE_API "auto"
|
||||
check_variables PW_RESHADE_EXE ""
|
||||
check_variables PW_RESHADE_FONT ""
|
||||
check_variables PW_RESHADE_SCREENSHOT_PATH ""
|
||||
check_variables PW_RESHADE_SCREENSHOT_KEY "44,0,0,0"
|
||||
check_variables PW_RESHADE_OVERLAY_KEY "36,0,0,0"
|
||||
check_variables PW_USE_ESYNC "0"
|
||||
check_variables PW_USE_FSYNC "0"
|
||||
check_variables PW_USE_NTSYNC "0"
|
||||
|
||||
@@ -55,6 +55,7 @@ from portprotonqt.dialogs.dialog_utils import create_dialog_hints_widget, update
|
||||
from portprotonqt.dialogs.proton_manager import show_proton_manager
|
||||
from portprotonqt.dialogs.settings_mangohud import MANGOHUD_ENV_KEYS, MangoHudSettingsMixin
|
||||
from portprotonqt.dialogs.settings_gamescope import GAMESCOPE_ENV_KEYS, GamescopeSettingsMixin
|
||||
from portprotonqt.dialogs.settings_reshade import RESHADE_ENV_KEYS, ReShadeSettingsMixin
|
||||
from portprotonqt.dialogs.settings_vkbasalt import VKBASALT_ENV_KEYS, VkBasaltSettingsMixin
|
||||
from portprotonqt.localization import _, format_setting_name_for_display
|
||||
from portprotonqt.logger import get_logger
|
||||
@@ -85,6 +86,8 @@ DEFAULT_WINE_SETTING_KEYS = {
|
||||
'PW_PREFIX_NAME': 'PW_DEFAULT_PREFIX_NAME',
|
||||
'PW_VULKAN_USE': 'PW_DEFAULT_VULKAN_USE',
|
||||
}
|
||||
|
||||
|
||||
def _normalize_prefix_directories(prefixes_dir):
|
||||
if not os.path.isdir(prefixes_dir):
|
||||
return
|
||||
@@ -149,6 +152,7 @@ class ExeSettingsDialog(
|
||||
DraggableDialog,
|
||||
MangoHudSettingsMixin,
|
||||
GamescopeSettingsMixin,
|
||||
ReShadeSettingsMixin,
|
||||
VkBasaltSettingsMixin,
|
||||
):
|
||||
"""Dialog for configuring executable-specific settings."""
|
||||
@@ -207,6 +211,7 @@ class ExeSettingsDialog(
|
||||
self.advanced_settings_by_key = {}
|
||||
self.init_mangohud_state()
|
||||
self.init_gamescope_state()
|
||||
self.init_reshade_state()
|
||||
self.init_vkbasalt_state()
|
||||
self.blocked_keys = set()
|
||||
self.numa_nodes = {}
|
||||
@@ -332,6 +337,8 @@ class ExeSettingsDialog(
|
||||
self.mangohud_tab_layout = QVBoxLayout(self.mangohud_tab)
|
||||
self.vkbasalt_tab = QWidget()
|
||||
self.vkbasalt_tab_layout = QVBoxLayout(self.vkbasalt_tab)
|
||||
self.reshade_tab = QWidget()
|
||||
self.reshade_tab_layout = QVBoxLayout(self.reshade_tab)
|
||||
self.gamescope_tab = QWidget()
|
||||
self.gamescope_tab_layout = QVBoxLayout(self.gamescope_tab)
|
||||
|
||||
@@ -339,6 +346,7 @@ class ExeSettingsDialog(
|
||||
self.tab_widget.addTab(self.advanced_tab, _("Advanced"))
|
||||
self.tab_widget.addTab(self.mangohud_tab, "MangoHud")
|
||||
self.tab_widget.addTab(self.vkbasalt_tab, "vkBasalt")
|
||||
self.tab_widget.addTab(self.reshade_tab, "ReShade")
|
||||
if self.gamescope_available:
|
||||
self.tab_widget.addTab(self.gamescope_tab, "Gamescope")
|
||||
|
||||
@@ -462,6 +470,7 @@ class ExeSettingsDialog(
|
||||
|
||||
self.setup_mangohud_tab()
|
||||
self.setup_vkbasalt_tab()
|
||||
self.setup_reshade_tab()
|
||||
if self.gamescope_available:
|
||||
self.setup_gamescope_tab()
|
||||
|
||||
@@ -557,6 +566,7 @@ class ExeSettingsDialog(
|
||||
or key in MANGOHUD_ENV_KEYS
|
||||
or key in GAMESCOPE_ENV_KEYS
|
||||
or key in VKBASALT_ENV_KEYS
|
||||
or key in RESHADE_ENV_KEYS
|
||||
or key in TOGGLE_BOOL_KEYS
|
||||
):
|
||||
if val.startswith('"') and val.endswith('"') and len(val) >= 2:
|
||||
@@ -591,6 +601,8 @@ class ExeSettingsDialog(
|
||||
self.current_settings[key] = ''
|
||||
for key in VKBASALT_ENV_KEYS:
|
||||
self.current_settings[key] = ''
|
||||
for key in RESHADE_ENV_KEYS:
|
||||
self.current_settings[key] = ''
|
||||
elif not self.user_conf:
|
||||
self.current_settings.setdefault('PW_MANGOHUD', '0')
|
||||
self.current_settings.setdefault('PW_VKBASALT', '0')
|
||||
@@ -619,6 +631,7 @@ class ExeSettingsDialog(
|
||||
self.populate_advanced()
|
||||
self.populate_mangohud()
|
||||
self.populate_vkbasalt()
|
||||
self.populate_reshade()
|
||||
if self.gamescope_available:
|
||||
self.populate_gamescope()
|
||||
self.populate_favorites(select_tab=True)
|
||||
@@ -1391,6 +1404,7 @@ class ExeSettingsDialog(
|
||||
|
||||
self._filter_mangohud_settings(search_text)
|
||||
self._filter_vkbasalt_settings(search_text)
|
||||
self._filter_reshade_settings(search_text)
|
||||
self._filter_gamescope_settings(search_text)
|
||||
|
||||
def apply_changes(self):
|
||||
@@ -1465,6 +1479,7 @@ class ExeSettingsDialog(
|
||||
if self.gamescope_available:
|
||||
gamescope_changes = self._collect_gamescope_changes()
|
||||
vkbasalt_changes = self._collect_vkbasalt_changes()
|
||||
reshade_changes = self._collect_reshade_changes()
|
||||
if self.user_conf:
|
||||
if self.current_settings.get('PW_MANGOHUD') != '1':
|
||||
mangohud_changes = [
|
||||
@@ -1481,7 +1496,14 @@ class ExeSettingsDialog(
|
||||
change for change in gamescope_changes
|
||||
if change.startswith('PW_GAMESCOPE=')
|
||||
]
|
||||
specialized_changes = mangohud_changes + vkbasalt_changes + gamescope_changes
|
||||
if self.current_settings.get('PW_USE_RESHADE') != '1':
|
||||
reshade_changes = [
|
||||
change for change in reshade_changes
|
||||
if change.startswith('PW_USE_RESHADE=')
|
||||
]
|
||||
specialized_changes = (
|
||||
mangohud_changes + vkbasalt_changes + reshade_changes + gamescope_changes
|
||||
)
|
||||
if self.user_conf:
|
||||
defaults = ('', '0', '0.00', 'Home')
|
||||
specialized_changes = [
|
||||
@@ -1677,6 +1699,8 @@ class ExeSettingsDialog(
|
||||
)
|
||||
return False
|
||||
|
||||
if isinstance(obj, QWidget) and self._handle_reshade_key_button_event(obj, event):
|
||||
return True
|
||||
if isinstance(obj, QWidget) and self._handle_vkbasalt_key_button_event(obj, event):
|
||||
return True
|
||||
|
||||
|
||||
@@ -0,0 +1,446 @@
|
||||
"""ReShade settings support for executable settings dialog."""
|
||||
|
||||
import os
|
||||
from typing import Any, cast
|
||||
|
||||
from PySide6.QtCore import QEvent, Qt
|
||||
from PySide6.QtGui import QKeyEvent, QKeySequence
|
||||
from PySide6.QtWidgets import (
|
||||
QGridLayout,
|
||||
QGroupBox,
|
||||
QHBoxLayout,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QPushButton,
|
||||
QScrollArea,
|
||||
QSizePolicy,
|
||||
QVBoxLayout,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from portprotonqt.custom_widgets import AutoSizeButton, CustomComboBox
|
||||
from portprotonqt.localization import _
|
||||
from portprotonqt.theme_manager import ThemeManager
|
||||
|
||||
RESHADE_ENV_KEYS = [
|
||||
'PW_RESHADE_EXE',
|
||||
'PW_RESHADE_API',
|
||||
'PW_RESHADE_FONT',
|
||||
'PW_RESHADE_SCREENSHOT_PATH',
|
||||
'PW_RESHADE_SCREENSHOT_KEY',
|
||||
'PW_RESHADE_OVERLAY_KEY',
|
||||
]
|
||||
RESHADE_API_OPTIONS = {
|
||||
"Auto": "auto",
|
||||
"DXGI": "dxgi.dll",
|
||||
"DirectX 8/9": "d3d9.dll",
|
||||
"OpenGL": "opengl32.dll",
|
||||
"Vulkan": "vulkan",
|
||||
}
|
||||
RESHADE_SPECIAL_KEYS = {
|
||||
Qt.Key.Key_Cancel: 3,
|
||||
Qt.Key.Key_Backspace: 8,
|
||||
Qt.Key.Key_Tab: 9,
|
||||
Qt.Key.Key_Clear: 12,
|
||||
Qt.Key.Key_Return: 13,
|
||||
Qt.Key.Key_Enter: 13,
|
||||
Qt.Key.Key_Pause: 19,
|
||||
Qt.Key.Key_CapsLock: 20,
|
||||
Qt.Key.Key_Escape: 27,
|
||||
Qt.Key.Key_Space: 32,
|
||||
Qt.Key.Key_PageUp: 33,
|
||||
Qt.Key.Key_PageDown: 34,
|
||||
Qt.Key.Key_End: 35,
|
||||
Qt.Key.Key_Home: 36,
|
||||
Qt.Key.Key_Left: 37,
|
||||
Qt.Key.Key_Up: 38,
|
||||
Qt.Key.Key_Right: 39,
|
||||
Qt.Key.Key_Down: 40,
|
||||
Qt.Key.Key_Print: 44,
|
||||
Qt.Key.Key_Insert: 45,
|
||||
Qt.Key.Key_Delete: 46,
|
||||
Qt.Key.Key_Help: 47,
|
||||
Qt.Key.Key_Meta: 91,
|
||||
Qt.Key.Key_Menu: 93,
|
||||
Qt.Key.Key_Sleep: 95,
|
||||
Qt.Key.Key_NumLock: 144,
|
||||
Qt.Key.Key_ScrollLock: 145,
|
||||
Qt.Key.Key_Semicolon: 186,
|
||||
Qt.Key.Key_Equal: 187,
|
||||
Qt.Key.Key_Comma: 188,
|
||||
Qt.Key.Key_Minus: 189,
|
||||
Qt.Key.Key_Period: 190,
|
||||
Qt.Key.Key_Slash: 191,
|
||||
Qt.Key.Key_QuoteLeft: 192,
|
||||
Qt.Key.Key_BracketLeft: 219,
|
||||
Qt.Key.Key_Backslash: 220,
|
||||
Qt.Key.Key_BracketRight: 221,
|
||||
Qt.Key.Key_Apostrophe: 222,
|
||||
Qt.Key.Key_Back: 166,
|
||||
Qt.Key.Key_Forward: 167,
|
||||
Qt.Key.Key_Refresh: 168,
|
||||
Qt.Key.Key_Stop: 169,
|
||||
Qt.Key.Key_Search: 170,
|
||||
Qt.Key.Key_Favorites: 171,
|
||||
Qt.Key.Key_HomePage: 172,
|
||||
Qt.Key.Key_VolumeMute: 173,
|
||||
Qt.Key.Key_VolumeDown: 174,
|
||||
Qt.Key.Key_VolumeUp: 175,
|
||||
Qt.Key.Key_MediaNext: 176,
|
||||
Qt.Key.Key_MediaPrevious: 177,
|
||||
Qt.Key.Key_MediaStop: 178,
|
||||
Qt.Key.Key_MediaPlay: 179,
|
||||
Qt.Key.Key_LaunchMail: 180,
|
||||
Qt.Key.Key_LaunchMedia: 181,
|
||||
Qt.Key.Key_Launch0: 182,
|
||||
Qt.Key.Key_Launch1: 183,
|
||||
}
|
||||
RESHADE_KEYPAD_KEYS = {
|
||||
Qt.Key.Key_Asterisk: 106,
|
||||
Qt.Key.Key_Plus: 107,
|
||||
Qt.Key.Key_Comma: 108,
|
||||
Qt.Key.Key_Minus: 109,
|
||||
Qt.Key.Key_Period: 110,
|
||||
Qt.Key.Key_Slash: 111,
|
||||
}
|
||||
RESHADE_FIRST_FUNCTION_KEY = 112
|
||||
RESHADE_LAST_FUNCTION_KEY = 135
|
||||
RESHADE_FIRST_NUMPAD_KEY = 96
|
||||
|
||||
|
||||
class ReShadeSettingsMixin:
|
||||
"""Mixin with ReShade settings UI and serialization logic."""
|
||||
|
||||
theme: Any
|
||||
exe_path: str | None
|
||||
current_settings: dict[str, str]
|
||||
original_values: dict[str, str]
|
||||
reshade_tab: QWidget
|
||||
reshade_tab_layout: QVBoxLayout
|
||||
|
||||
def init_reshade_state(self) -> None:
|
||||
self.reshade_original_values = {}
|
||||
self.reshade_key_target = None
|
||||
|
||||
def setup_reshade_tab(self) -> None:
|
||||
"""Create ReShade tab widgets."""
|
||||
scroll = QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
scroll.setFocusPolicy(Qt.FocusPolicy.NoFocus)
|
||||
scroll.setStyleSheet(self.theme.SCROLL_STYLE + self.theme.TRANSPARENT_BACKGROUND_STYLE)
|
||||
container = QWidget()
|
||||
layout = QVBoxLayout(container)
|
||||
layout.setSpacing(self.theme.exeSettingsGroupBoxBlockSpacing)
|
||||
|
||||
actions = QGroupBox(_("Actions"))
|
||||
actions.setStyleSheet(self.theme.QGROUP_BOX_STYLE)
|
||||
actions_layout = QGridLayout(actions)
|
||||
self.reshade_enable_button = self._create_reshade_button("")
|
||||
self.reshade_enable_button.clicked.connect(self.toggle_reshade_enable)
|
||||
actions_layout.addWidget(self.reshade_enable_button, 0, 0)
|
||||
layout.addWidget(actions)
|
||||
|
||||
self._add_reshade_settings_group(layout)
|
||||
layout.addStretch()
|
||||
|
||||
scroll.setWidget(container)
|
||||
self.reshade_tab_layout.addWidget(scroll)
|
||||
|
||||
def _add_reshade_settings_group(self, parent_layout: QVBoxLayout) -> None:
|
||||
self.reshade_settings_group = QGroupBox(_("Settings"))
|
||||
self.reshade_settings_group.setStyleSheet(self.theme.QGROUP_BOX_STYLE)
|
||||
settings_layout = QGridLayout(self.reshade_settings_group)
|
||||
self.reshade_exe_edit = self._add_reshade_file_row(
|
||||
settings_layout, 0, "EXE", ".exe"
|
||||
)
|
||||
self.reshade_api_combo = CustomComboBox(theme=self.theme)
|
||||
self.reshade_api_combo.view().window().setWindowFlags(
|
||||
Qt.WindowType.Popup | Qt.WindowType.FramelessWindowHint
|
||||
)
|
||||
self.reshade_api_combo.view().window().setAttribute(
|
||||
Qt.WidgetAttribute.WA_TranslucentBackground
|
||||
)
|
||||
self.reshade_api_combo.setStyleSheet(
|
||||
self.theme.COMBOBOX_STYLE + self.theme.SCROLL_STYLE
|
||||
)
|
||||
self.reshade_api_combo.addItems(list(RESHADE_API_OPTIONS))
|
||||
settings_layout.addWidget(QLabel("API"), 1, 0)
|
||||
settings_layout.addWidget(self.reshade_api_combo, 1, 1)
|
||||
self.reshade_font_combo = CustomComboBox(theme=self.theme)
|
||||
self.reshade_font_combo.view().window().setWindowFlags(
|
||||
Qt.WindowType.Popup | Qt.WindowType.FramelessWindowHint
|
||||
)
|
||||
self.reshade_font_combo.view().window().setAttribute(
|
||||
Qt.WidgetAttribute.WA_TranslucentBackground
|
||||
)
|
||||
self.reshade_font_combo.setStyleSheet(
|
||||
self.theme.COMBOBOX_STYLE + self.theme.SCROLL_STYLE
|
||||
)
|
||||
self.reshade_font_combo.addItem(_("Default"), "")
|
||||
settings_layout.addWidget(QLabel(_("Font")), 2, 0)
|
||||
settings_layout.addWidget(self.reshade_font_combo, 2, 1)
|
||||
self.reshade_screenshot_edit = self._add_reshade_directory_row(
|
||||
settings_layout, 3, _("Screenshots")
|
||||
)
|
||||
self.reshade_screenshot_key_button = self._create_reshade_key_button(
|
||||
"screenshot"
|
||||
)
|
||||
settings_layout.addWidget(QLabel(_("Screenshot key")), 4, 0)
|
||||
settings_layout.addWidget(self.reshade_screenshot_key_button, 4, 1)
|
||||
self.reshade_overlay_key_button = self._create_reshade_key_button("overlay")
|
||||
settings_layout.addWidget(QLabel(_("Overlay key")), 5, 0)
|
||||
settings_layout.addWidget(self.reshade_overlay_key_button, 5, 1)
|
||||
parent_layout.addWidget(self.reshade_settings_group)
|
||||
self._load_reshade_fonts()
|
||||
|
||||
def _create_reshade_button(self, label: str) -> QPushButton:
|
||||
button = QPushButton(label)
|
||||
button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
|
||||
button.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
||||
button.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed)
|
||||
return button
|
||||
|
||||
def _add_reshade_file_row(
|
||||
self, layout: QGridLayout, row: int, label: str, file_filter: str
|
||||
) -> QLineEdit:
|
||||
line_edit = QLineEdit()
|
||||
line_edit.setStyleSheet(self.theme.ADDGAME_INPUT_STYLE)
|
||||
button = AutoSizeButton(
|
||||
_("Browse…"), icon=ThemeManager().get_icon("folder", as_path=True)
|
||||
)
|
||||
button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
|
||||
button.clicked.connect(
|
||||
lambda _checked=False: self._select_reshade_file(line_edit, file_filter)
|
||||
)
|
||||
field_layout = QHBoxLayout()
|
||||
field_layout.addWidget(line_edit)
|
||||
field_layout.addWidget(button)
|
||||
layout.addWidget(QLabel(label), row, 0)
|
||||
layout.addLayout(field_layout, row, 1)
|
||||
return line_edit
|
||||
|
||||
def _select_reshade_file(self, line_edit: QLineEdit, file_filter: str) -> None:
|
||||
from portprotonqt.dialogs.file_explorer import FileExplorer
|
||||
|
||||
current_path = line_edit.text().strip()
|
||||
initial_path = os.path.dirname(current_path) if os.path.isfile(current_path) else None
|
||||
explorer = FileExplorer(
|
||||
self, theme=self.theme, file_filter=file_filter, initial_path=initial_path
|
||||
)
|
||||
explorer.file_signal.file_selected.connect(line_edit.setText)
|
||||
explorer.exec()
|
||||
|
||||
def _add_reshade_directory_row(
|
||||
self, layout: QGridLayout, row: int, label: str
|
||||
) -> QLineEdit:
|
||||
line_edit = QLineEdit()
|
||||
line_edit.setStyleSheet(self.theme.ADDGAME_INPUT_STYLE)
|
||||
button = AutoSizeButton(
|
||||
_("Browse…"), icon=ThemeManager().get_icon("folder", as_path=True)
|
||||
)
|
||||
button.setStyleSheet(self.theme.ACTION_BUTTON_STYLE)
|
||||
button.clicked.connect(
|
||||
lambda _checked=False: self._select_reshade_directory(line_edit)
|
||||
)
|
||||
field_layout = QHBoxLayout()
|
||||
field_layout.addWidget(line_edit)
|
||||
field_layout.addWidget(button)
|
||||
layout.addWidget(QLabel(label), row, 0)
|
||||
layout.addLayout(field_layout, row, 1)
|
||||
return line_edit
|
||||
|
||||
def _select_reshade_directory(self, line_edit: QLineEdit) -> None:
|
||||
from portprotonqt.dialogs.file_explorer import FileExplorer
|
||||
|
||||
initial_path = line_edit.text().strip() or os.path.dirname(self.exe_path or "")
|
||||
explorer = FileExplorer(
|
||||
self, theme=self.theme, initial_path=initial_path, directory_only=True
|
||||
)
|
||||
explorer.file_signal.file_selected.connect(line_edit.setText)
|
||||
explorer.exec()
|
||||
|
||||
def _create_reshade_key_button(self, target: str) -> QPushButton:
|
||||
button = self._create_reshade_button("")
|
||||
button.clicked.connect(lambda _checked=False: self._capture_reshade_key(target))
|
||||
button.installEventFilter(cast(QWidget, self))
|
||||
return button
|
||||
|
||||
def _capture_reshade_key(self, target: str) -> None:
|
||||
self.reshade_key_target = target
|
||||
button = self._get_reshade_key_button(target)
|
||||
button.setText(_("Press a button to choose"))
|
||||
button.grabKeyboard()
|
||||
|
||||
def _get_reshade_key_button(self, target: str) -> QPushButton:
|
||||
if target == "screenshot":
|
||||
return self.reshade_screenshot_key_button
|
||||
return self.reshade_overlay_key_button
|
||||
|
||||
def _handle_reshade_key_button_event(self, obj: QWidget, event: QEvent) -> bool:
|
||||
target = self.reshade_key_target
|
||||
if target is None or obj is not self._get_reshade_key_button(target):
|
||||
return False
|
||||
if event.type() != QEvent.Type.KeyPress:
|
||||
return False
|
||||
key_event = cast(QKeyEvent, event)
|
||||
key_value = self._reshade_virtual_key(
|
||||
key_event.key(), key_event.modifiers()
|
||||
)
|
||||
if key_value is None:
|
||||
return True
|
||||
modifiers = key_event.modifiers()
|
||||
value = ",".join(str(item) for item in (
|
||||
key_value,
|
||||
int(bool(modifiers & Qt.KeyboardModifier.ControlModifier)),
|
||||
int(bool(modifiers & Qt.KeyboardModifier.ShiftModifier)),
|
||||
int(bool(modifiers & Qt.KeyboardModifier.AltModifier)),
|
||||
))
|
||||
self.current_settings[f'PW_RESHADE_{target.upper()}_KEY'] = value
|
||||
button = self._get_reshade_key_button(target)
|
||||
button.setText(QKeySequence(key_event.keyCombination()).toString())
|
||||
button.releaseKeyboard()
|
||||
self.reshade_key_target = None
|
||||
return True
|
||||
|
||||
def _reshade_virtual_key(
|
||||
self, key: int, modifiers: Qt.KeyboardModifier
|
||||
) -> int | None:
|
||||
is_keypad = bool(modifiers & Qt.KeyboardModifier.KeypadModifier)
|
||||
if is_keypad and Qt.Key.Key_0.value <= key <= Qt.Key.Key_9.value:
|
||||
return RESHADE_FIRST_NUMPAD_KEY + key - Qt.Key.Key_0.value
|
||||
if is_keypad:
|
||||
try:
|
||||
keypad_key = RESHADE_KEYPAD_KEYS.get(Qt.Key(key))
|
||||
except ValueError:
|
||||
keypad_key = None
|
||||
if keypad_key is not None:
|
||||
return keypad_key
|
||||
if Qt.Key.Key_0.value <= key <= Qt.Key.Key_9.value:
|
||||
return key
|
||||
if Qt.Key.Key_A.value <= key <= Qt.Key.Key_Z.value:
|
||||
return key
|
||||
if Qt.Key.Key_F1.value <= key <= Qt.Key.Key_F24.value:
|
||||
return RESHADE_FIRST_FUNCTION_KEY + key - Qt.Key.Key_F1.value
|
||||
try:
|
||||
return RESHADE_SPECIAL_KEYS.get(Qt.Key(key))
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def _format_reshade_key(self, value: str) -> str:
|
||||
try:
|
||||
key, control, shift, alt = (int(item) for item in value.split(","))
|
||||
except ValueError:
|
||||
return value
|
||||
qt_key = next(
|
||||
(item for item, code in RESHADE_SPECIAL_KEYS.items() if code == key), None
|
||||
)
|
||||
if qt_key is not None:
|
||||
name = QKeySequence(qt_key).toString()
|
||||
elif RESHADE_FIRST_FUNCTION_KEY <= key <= RESHADE_LAST_FUNCTION_KEY:
|
||||
name = f"F{key - RESHADE_FIRST_FUNCTION_KEY + 1}"
|
||||
else:
|
||||
name = chr(key)
|
||||
modifiers = [
|
||||
label for active, label in (
|
||||
(control, "Ctrl"), (shift, "Shift"), (alt, "Alt")
|
||||
) if active
|
||||
]
|
||||
return "+".join([*modifiers, name])
|
||||
|
||||
def _load_reshade_fonts(self) -> None:
|
||||
selected_family = self.current_settings.get('PW_RESHADE_FONT', '')
|
||||
font_options = cast(Any, self)._get_mangohud_font_options()
|
||||
families = {name for name, _path in font_options}
|
||||
self.reshade_font_combo.clear()
|
||||
self.reshade_font_combo.addItem(_("Default"), "")
|
||||
for family in sorted(families, key=str.casefold):
|
||||
self.reshade_font_combo.addItem(family, family)
|
||||
self._select_reshade_font(selected_family)
|
||||
|
||||
def _select_reshade_font(self, family: str) -> None:
|
||||
for index in range(self.reshade_font_combo.count()):
|
||||
if self.reshade_font_combo.itemData(index) == family:
|
||||
self.reshade_font_combo.setCurrentIndex(index)
|
||||
return
|
||||
if family:
|
||||
self.reshade_font_combo.addItem(family, family)
|
||||
self.reshade_font_combo.setCurrentIndex(self.reshade_font_combo.count() - 1)
|
||||
|
||||
def populate_reshade(self) -> None:
|
||||
"""Populate ReShade tab from current settings."""
|
||||
reshade_exe = self.current_settings.get('PW_RESHADE_EXE', '') or self.exe_path or ''
|
||||
self.reshade_exe_edit.setText(reshade_exe)
|
||||
api_value = self.current_settings.get('PW_RESHADE_API', 'auto')
|
||||
api_label = next(
|
||||
(label for label, value in RESHADE_API_OPTIONS.items() if value == api_value),
|
||||
"Auto",
|
||||
)
|
||||
self.reshade_api_combo.setCurrentText(api_label)
|
||||
font_family = self.current_settings.get('PW_RESHADE_FONT', '')
|
||||
self._select_reshade_font(font_family)
|
||||
screenshot_path = self.current_settings.get('PW_RESHADE_SCREENSHOT_PATH', '')
|
||||
screenshot_key = self.current_settings.get(
|
||||
'PW_RESHADE_SCREENSHOT_KEY', '44,0,0,0'
|
||||
)
|
||||
overlay_key = self.current_settings.get(
|
||||
'PW_RESHADE_OVERLAY_KEY', '36,0,0,0'
|
||||
)
|
||||
self.reshade_screenshot_edit.setText(screenshot_path)
|
||||
self.reshade_screenshot_key_button.setText(
|
||||
self._format_reshade_key(screenshot_key)
|
||||
)
|
||||
self.reshade_overlay_key_button.setText(self._format_reshade_key(overlay_key))
|
||||
self.current_settings['PW_RESHADE_SCREENSHOT_KEY'] = screenshot_key
|
||||
self.current_settings['PW_RESHADE_OVERLAY_KEY'] = overlay_key
|
||||
self.reshade_original_values = {
|
||||
key: self.current_settings.get(key, '') for key in RESHADE_ENV_KEYS
|
||||
}
|
||||
self.reshade_original_values['PW_RESHADE_EXE'] = reshade_exe
|
||||
self.reshade_original_values['PW_RESHADE_SCREENSHOT_KEY'] = screenshot_key
|
||||
self.reshade_original_values['PW_RESHADE_OVERLAY_KEY'] = overlay_key
|
||||
self._update_reshade_widgets()
|
||||
|
||||
def toggle_reshade_enable(self) -> None:
|
||||
enabled = self.current_settings.get('PW_USE_RESHADE') == '1'
|
||||
self.current_settings['PW_USE_RESHADE'] = '0' if enabled else '1'
|
||||
self._update_reshade_widgets()
|
||||
|
||||
def _update_reshade_widgets(self) -> None:
|
||||
enabled = self.current_settings.get('PW_USE_RESHADE') == '1'
|
||||
self.reshade_enable_button.setText(
|
||||
_("Disable {0}").format("ReShade")
|
||||
if enabled else _("Enable {0}").format("ReShade")
|
||||
)
|
||||
self.reshade_enable_button.setStyleSheet(
|
||||
self.theme.ACTION_BUTTON_ACTIVE_STYLE if enabled else self.theme.ACTION_BUTTON_STYLE
|
||||
)
|
||||
self.reshade_settings_group.setVisible(enabled)
|
||||
|
||||
def _collect_reshade_changes(self) -> list[str]:
|
||||
changes = []
|
||||
enabled = '1' if self.current_settings.get('PW_USE_RESHADE') == '1' else '0'
|
||||
original_enabled = '1' if self.original_values.get('PW_USE_RESHADE') == '1' else '0'
|
||||
if enabled != original_enabled:
|
||||
changes.append(f"PW_USE_RESHADE={enabled}")
|
||||
values = {
|
||||
'PW_RESHADE_EXE': self.reshade_exe_edit.text().strip(),
|
||||
'PW_RESHADE_API': RESHADE_API_OPTIONS[self.reshade_api_combo.currentText()],
|
||||
'PW_RESHADE_FONT': str(self.reshade_font_combo.currentData() or ''),
|
||||
'PW_RESHADE_SCREENSHOT_PATH': self.reshade_screenshot_edit.text().strip(),
|
||||
'PW_RESHADE_SCREENSHOT_KEY': self.current_settings['PW_RESHADE_SCREENSHOT_KEY'],
|
||||
'PW_RESHADE_OVERLAY_KEY': self.current_settings['PW_RESHADE_OVERLAY_KEY'],
|
||||
}
|
||||
for key, value in values.items():
|
||||
if value != self.reshade_original_values.get(key, ''):
|
||||
changes.append(f"{key}={value}")
|
||||
return changes
|
||||
|
||||
def _filter_reshade_settings(self, search_text: str) -> None:
|
||||
enabled = self.current_settings.get('PW_USE_RESHADE') == '1'
|
||||
self.reshade_settings_group.setVisible(
|
||||
enabled and (
|
||||
not search_text
|
||||
or search_text in "exe api font screenshot overlay"
|
||||
or search_text in _("Settings").lower()
|
||||
)
|
||||
)
|
||||
@@ -118,6 +118,7 @@ install_data(
|
||||
'dialogs/settings_dialog.py',
|
||||
'dialogs/settings_mangohud.py',
|
||||
'dialogs/settings_gamescope.py',
|
||||
'dialogs/settings_reshade.py',
|
||||
'dialogs/settings_vkbasalt.py',
|
||||
'dialogs/proton_manager.py',
|
||||
'dialogs/prefix_backup.py',
|
||||
|
||||
@@ -149,6 +149,7 @@ def get_toggle_settings():
|
||||
'PW_USE_SPECIALK': _("Enable SpecialK (injection library for fixing graphics, latency and more)"),
|
||||
# Upscalers and Frame Generation
|
||||
'PW_USE_OPTISCALER': _("Enable OptiScaler (replacement upscaler / frame generator)"),
|
||||
'PW_USE_RESHADE': _("Enable ReShade post-processing"),
|
||||
'PW_USE_LS_FRAME_GEN': _("Enable Lossless Scaling frame generation (experimental)"),
|
||||
'PW_WINE_FULLSCREEN_FSR': _("FSR upscaling in fullscreen with ProtonGE below native resolution"),
|
||||
# Ray Tracing
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
"""Tests for config/portproton.py — exec_line parsing, icon sanitization, launcher tail extraction."""
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from pytest import MonkeyPatch
|
||||
from pytest import MonkeyPatch, mark
|
||||
|
||||
from portprotonqt.config.portproton import (
|
||||
extract_exec_target_path,
|
||||
@@ -192,3 +193,180 @@ class TestThemedLaunchIconNames:
|
||||
|
||||
def test_reg_has_icon(self):
|
||||
assert "reg" in THEMED_LAUNCH_ICON_NAMES[".reg"]
|
||||
|
||||
|
||||
def test_run_after_batch_is_created_next_to_exe() -> None:
|
||||
helper = Path("build-aux/share/portproton/scripts/functions_helper").read_text(
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
assert 'run_after_dir="$(dirname "${PW_EXE_FILE}")"' in helper
|
||||
assert 'pw_exe_file_win="$("${WINELOADER}" winepath -w "${PW_EXE_FILE}"' in helper
|
||||
assert "chcp 65001 >nul" in helper
|
||||
assert 'start "" "${pw_exe_file_win}" ${LAUNCH_PARAMETERS}' in helper
|
||||
assert 'start "" /unix "${PW_RUN_AFTER_EXE}"' in helper
|
||||
assert 'LAUNCH_PARAMETERS="" proxy_launch_parameters="" \\' in helper
|
||||
assert 'pw_run "${PW_VD_TMP[@]}" "${run_after_bat}"' in helper
|
||||
|
||||
|
||||
def test_game_launch_marker_is_emitted_for_wine_and_proton() -> None:
|
||||
helper = Path("build-aux/share/portproton/scripts/functions_helper").read_text(
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
marker = "printf '%s\\n' 'PORTPROTONQT_GAME_LAUNCH_STARTED'"
|
||||
assert helper.count(marker) == 2
|
||||
|
||||
|
||||
def test_user_conf_get_without_name_lists_active_values(tmp_path: Path) -> None:
|
||||
helper = Path("build-aux/share/portproton/scripts/functions_helper").read_text()
|
||||
definition = helper.split("manage_user_conf_value () {", 1)[1].split(
|
||||
"use_exiftool () {", 1
|
||||
)[0]
|
||||
user_conf = tmp_path / "user.conf"
|
||||
user_conf.write_text(
|
||||
'# comment\nexport PW_USE_ESYNC="1"\n# export PW_USE_FSYNC="1"\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
script = "manage_user_conf_value () {" + definition + '\nmanage_user_conf_value get\n'
|
||||
|
||||
result = subprocess.run(
|
||||
["bash", "-c", script], capture_output=True, text=True,
|
||||
env={"PATH": os.defpath, "USER_CONF": str(user_conf)}, check=False,
|
||||
)
|
||||
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert result.stdout == 'PW_USE_ESYNC="1"\n'
|
||||
|
||||
|
||||
@mark.parametrize("runtime,logging", [("0", "0"), ("0", "1"), ("1", "0"), ("1", "1")])
|
||||
def test_wine_exit_code_survives_log_output_and_wineserver_wait(
|
||||
tmp_path: Path, runtime: str, logging: str,
|
||||
) -> None:
|
||||
helper = Path("build-aux/share/portproton/scripts/functions_helper").read_text()
|
||||
definition = helper.split("pw_run () {", 1)[1].split("export -f pw_run", 1)[0]
|
||||
script = "pw_run () {" + definition + '''
|
||||
check_variables () { :; }
|
||||
pw_launch_wrapper () { return 37; }
|
||||
wait_wineserver () { return 0; }
|
||||
print_info () { :; }
|
||||
PATH_TO_GAME="$1"
|
||||
PW_TMPFS_PATH="$1"
|
||||
PW_LOG_FILE="$1/wine.log"
|
||||
PW_EXE_FILE="$1/game.exe"
|
||||
PW_USE_RUNTIME="$2"
|
||||
PW_LOG="$3"
|
||||
pw_run "$PW_EXE_FILE"
|
||||
exit $?
|
||||
'''
|
||||
(tmp_path / "game.exe").touch()
|
||||
|
||||
result = subprocess.run(
|
||||
["bash", "-c", script, "bash", str(tmp_path), runtime, logging],
|
||||
capture_output=True, text=True, env={"PATH": os.defpath}, check=False,
|
||||
)
|
||||
|
||||
assert result.returncode == 37, result.stderr
|
||||
assert result.stdout.count("PORTPROTONQT_GAME_EXIT_CODE=37") == 1
|
||||
|
||||
|
||||
def test_vk_gpu_info_uses_build_aux_binary() -> None:
|
||||
helper = Path("build-aux/share/portproton/scripts/functions_helper").read_text(
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
assert '/../../../bin/vk_gpu_info"' in helper
|
||||
assert "dev-scripts/vk_gpu_info" not in helper
|
||||
|
||||
|
||||
def test_reshade_prefers_directx_for_unity() -> None:
|
||||
helper = Path("build-aux/share/portproton/scripts/functions_helper").read_text(
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
assert "source=$(echo \"$detect\" | grep '^SOURCE=' | cut -d= -f2-)" in helper
|
||||
assert (
|
||||
'[[ "${source##*/}" == UnityPlayer.dll && "$highest" != None ]] '
|
||||
"&& vulkan=false"
|
||||
) in helper
|
||||
|
||||
|
||||
def test_reshade_and_optiscaler_are_mutually_exclusive() -> None:
|
||||
helper = Path("build-aux/share/portproton/scripts/functions_helper").read_text(
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
assert 'DISABLE_EDIT_DB_LIST+=" PW_USE_OPTISCALER PW_USE_SPECIALK"' in helper
|
||||
assert 'DISABLE_EDIT_DB_LIST+=" PW_USE_RESHADE"' in helper
|
||||
|
||||
|
||||
def test_reshade_repeated_link_does_not_create_nested_shaders(tmp_path: Path) -> None:
|
||||
helper = Path("build-aux/share/portproton/scripts/functions_helper").read_text(
|
||||
encoding="utf-8",
|
||||
)
|
||||
link_function = helper.split("try_force_link_dir () {", 1)[1].split("\n}", 1)[0]
|
||||
shaders = tmp_path / "shaders"
|
||||
shaders.mkdir()
|
||||
game = tmp_path / "game"
|
||||
game.mkdir()
|
||||
script = f'try_force_link_dir () {{{link_function}\n}}\n'
|
||||
link_call = helper.split(' [[ "$PW_USE_RESHADE" != 1 ]] && return 0', 1)[1]
|
||||
link_call = link_call.split(' pw_specialk_set_ini_value', 1)[0]
|
||||
script += f'root="$1"\nPATH_TO_GAME="$2"\n{link_call * 2}'
|
||||
|
||||
subprocess.run(
|
||||
["bash", "-c", script, "bash", str(tmp_path), str(game)],
|
||||
check=True,
|
||||
)
|
||||
|
||||
assert (game / "ReShade_shaders").resolve() == shaders
|
||||
assert not (shaders / "shaders").exists()
|
||||
|
||||
|
||||
def test_reshade_disable_cleans_unreal_shipping_directory(tmp_path: Path) -> None:
|
||||
helper = Path("build-aux/share/portproton/scripts/functions_helper").read_text(
|
||||
encoding="utf-8",
|
||||
)
|
||||
sync_function = helper.split("pw_reshade_sync_files () {", 1)[1].split("\n}", 1)[0]
|
||||
game_root = tmp_path / "game"
|
||||
game_root.mkdir()
|
||||
game_exe = game_root / "Praest.exe"
|
||||
game_exe.touch()
|
||||
game = game_root / "Praest" / "Binaries" / "Win64"
|
||||
game.mkdir(parents=True)
|
||||
runtime = tmp_path / "plugins" / "reshade_windows" / "reshade" / "6.8.0_Addon"
|
||||
runtime.mkdir(parents=True)
|
||||
reshade_dll = runtime / "ReShade32.dll"
|
||||
reshade_dll.touch()
|
||||
(game / "opengl32.dll").symlink_to(reshade_dll)
|
||||
(game / "opengl32.dll.b").symlink_to(reshade_dll)
|
||||
(game / "ReShadePreset.ini").write_text("[GENERAL]\n")
|
||||
(game / "reshade_version").write_text("6.8.0_Addon\nopengl32.dll\n")
|
||||
script = f'pw_reshade_sync_files () {{{sync_function}\n}}\n'
|
||||
script += 'try_remove_file () { rm -f "$1"; }\npw_reshade_sync_files\n'
|
||||
|
||||
subprocess.run(
|
||||
["bash", "-c", script],
|
||||
check=True,
|
||||
env={
|
||||
**os.environ,
|
||||
"PW_EXE_FILE": str(game_exe),
|
||||
"PW_PLUGINS_PATH": str(tmp_path / "plugins"),
|
||||
"PW_USE_RESHADE": "0",
|
||||
"PW_RESHADE_API": "",
|
||||
"PW_RESHADE_EXE": "",
|
||||
"PATH_TO_GAME": str(game),
|
||||
},
|
||||
)
|
||||
|
||||
assert not (game / "opengl32.dll").exists()
|
||||
assert not (game / "opengl32.dll.b").exists()
|
||||
assert not (game / "ReShadePreset.ini").exists()
|
||||
|
||||
|
||||
def test_ini_writer_matches_spaces_around_equals() -> None:
|
||||
helper = Path("build-aux/share/portproton/scripts/functions_helper").read_text(
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
assert '^[[:space:]]*$key[[:space:]]*=' in helper
|
||||
@@ -37,6 +37,7 @@ from portprotonqt.dialogs.settings_mangohud import (
|
||||
MANGOHUD_TOGGLE_DESCRIPTIONS,
|
||||
MangoHudSettingsMixin,
|
||||
)
|
||||
from portprotonqt.dialogs.settings_reshade import ReShadeSettingsMixin
|
||||
from portprotonqt.dialogs.settings_vkbasalt import VkBasaltSettingsMixin
|
||||
|
||||
|
||||
@@ -90,6 +91,45 @@ def test_settings_dialog_search_accepts_typing_without_search_focus() -> None:
|
||||
QApplication.sendPostedEvents(None, QEvent.Type.DeferredDelete)
|
||||
|
||||
|
||||
def test_reshade_collects_enable_exe_api_and_font() -> None:
|
||||
settings = ReShadeSettingsMixin()
|
||||
settings.current_settings = {
|
||||
"PW_USE_RESHADE": "1",
|
||||
"PW_RESHADE_SCREENSHOT_KEY": "44,0,0,0",
|
||||
"PW_RESHADE_OVERLAY_KEY": "36,0,0,0",
|
||||
}
|
||||
settings.original_values = {"PW_USE_RESHADE": "0"}
|
||||
settings.reshade_original_values = {
|
||||
"PW_RESHADE_EXE": "",
|
||||
"PW_RESHADE_API": "auto",
|
||||
"PW_RESHADE_FONT": "",
|
||||
"PW_RESHADE_SCREENSHOT_PATH": "",
|
||||
"PW_RESHADE_SCREENSHOT_KEY": "44,0,0,0",
|
||||
"PW_RESHADE_OVERLAY_KEY": "36,0,0,0",
|
||||
}
|
||||
settings.reshade_exe_edit = QLineEdit("/games/game.exe")
|
||||
settings.reshade_api_combo = CustomComboBox()
|
||||
settings.reshade_api_combo.addItems(["Auto", "Vulkan"])
|
||||
settings.reshade_api_combo.setCurrentText("Vulkan")
|
||||
settings.reshade_font_combo = CustomComboBox()
|
||||
settings.reshade_font_combo.addItem("DejaVu Sans", "DejaVu Sans")
|
||||
settings.reshade_screenshot_edit = QLineEdit("/screenshots")
|
||||
|
||||
assert settings._collect_reshade_changes() == [
|
||||
"PW_USE_RESHADE=1",
|
||||
"PW_RESHADE_EXE=/games/game.exe",
|
||||
"PW_RESHADE_API=vulkan",
|
||||
"PW_RESHADE_FONT=DejaVu Sans",
|
||||
"PW_RESHADE_SCREENSHOT_PATH=/screenshots",
|
||||
]
|
||||
assert settings._reshade_virtual_key(
|
||||
Qt.Key.Key_Semicolon.value, Qt.KeyboardModifier.NoModifier
|
||||
) == 186
|
||||
assert settings._reshade_virtual_key(
|
||||
Qt.Key.Key_1.value, Qt.KeyboardModifier.KeypadModifier
|
||||
) == 97
|
||||
|
||||
|
||||
def test_mangohud_search_ignores_unavailable_categories() -> None:
|
||||
settings = MangoHudSettingsMixin()
|
||||
settings.mangohud_category_groups = {}
|
||||
|
||||
Reference in new issue
Block a user