Compare commits

...

26 Commits

Author SHA1 Message Date
Mikhail Tergoev
d5f337e6b4 Merge branch 'minergenon-devel' 2025-09-29 23:54:59 +03:00
Sergey Palcheh
904c9c9895 revision of the sub-tab Authors 2025-09-29 21:24:37 +06:00
Sergey Palcheh
1d4ee1fd70 the prefix control display is enabled by default 2025-09-29 20:40:07 +06:00
Sergey Palcheh
02a2256c8c fixed the character input in the name of the prefix being created 2025-09-29 20:27:33 +06:00
Mikhail Tergoev
cbcdba204e TODO: system menu directory 2025-09-29 16:05:26 +03:00
Mikhail Tergoev
66c56f6ecf removed broken README.MD 2025-09-29 15:52:21 +03:00
Mikhail Tergoev
221b59eda7 added README.MD 2025-09-29 15:50:46 +03:00
Mikhail Tergoev
adf5f78360 kill_wine worked only with WH 2025-09-29 14:40:57 +03:00
Mikhail Tergoev
01f19cd94d first print_license_agreement before run_autoinstall 2025-09-29 14:23:31 +03:00
Mikhail Tergoev
117e497f94 Merge branch 'minergenon-devel' 2025-09-29 14:06:36 +03:00
Sergey Palcheh
3527846c6c added to the tray show/hide 2025-09-29 11:33:23 +06:00
Sergey Palcheh
553d427d66 added a gui tray 2025-09-28 21:26:39 +06:00
Sergey Palcheh
0f8f192634 added a prefix template creation button 2025-09-27 14:13:56 +06:00
Sergey Palcheh
7f64378670 improved the button for adding associations 2025-09-27 12:19:11 +06:00
Sergey Palcheh
165c4ee110 the agreement acceptance window has been removed when selecting the dxvk/vkd3d versions 2025-09-27 11:32:46 +06:00
Sergey Palcheh
843b90c1c2 the agreement acceptance window has been removed when selecting wine versions 2025-09-27 11:19:14 +06:00
Sergey Palcheh
e3ac6dd967 fixed closing applications when closing the gui 2025-09-27 11:08:26 +06:00
Mikhail Tergoev
5763749aa0 updated init dxvk/vkd3d and fixed download from tty 2025-09-26 14:44:14 +03:00
Mikhail Tergoev
b1f192b2ff fixed file associacion and always read last.conf 2025-09-25 15:04:04 +03:00
Mikhail Tergoev
42aa29d208 Merge branch 'minergenon-devel' 2025-09-25 13:10:01 +03:00
Mikhail Tergoev
3ad737e27d fixed nettest icon for GUI and added unpack tests 2025-09-25 12:42:19 +03:00
Mikhail Tergoev
151b6d6e30 added NetTest (demo) to manualinstall 2025-09-25 01:05:34 +03:00
Mikhail Tergoev
c8049efd37 fixed create symlink from xuser to user 2025-09-23 17:31:51 +03:00
Mikhail Tergoev
8eb5a0aa86 rever wine_x_tkg_10-0_amd64 for scadoffice 2025-09-23 17:30:53 +03:00
Mikhail Tergoev
7edc205b66 updated wine and pfx for scadoffice 2025-09-23 16:40:54 +03:00
Mikhail Tergoev
b36cadd54d fixed copy user dir in create_base_pfx 2025-09-23 16:34:39 +03:00
7 changed files with 351 additions and 163 deletions

View File

@@ -6,10 +6,10 @@ export WH_WINE_USE="wine_x_tkg_10-0_amd64"
export WINEPREFIX="scadoffice" export WINEPREFIX="scadoffice"
export PROG_NAME="SCAD Office" export PROG_NAME="SCAD Office"
export PROG_ICON="scadoffice" export PROG_ICON="scadoffice"
export BASE_PFX="scadaoffice_pfx_x64_v03" export BASE_PFX="scadaoffice_pfx_x64_v04"
export WH_WINDOWS_VER="10" export WH_WINDOWS_VER="10"
export WINEARCH="win64" export WINEARCH="win64"
export INSTALL_DLL="dotnet20 dotnet472 dotnet48 gdiplus vcrun6sp6 vcrun2005 vcrun2019 d3dx11_42 d3dx11_43 d3dx9 d3dcompiler_42 d3dcompiler_43 d3dcompiler_46 d3dcompiler_47 richtx32 riched30 riched20 msxml6" export INSTALL_DLL="dotnet20 dotnet48 gdiplus vcrun6sp6 vcrun2005 vcrun2019 d3dx11_42 d3dx11_43 d3dx9 d3dcompiler_42 d3dcompiler_43 d3dcompiler_46 d3dcompiler_47 richtx32 riched30 riched20 msxml6"
export WH_XDG_OPEN="rtf" export WH_XDG_OPEN="rtf"
AUTOINSTALL_EXE="${WH_TMP_DIR}/SCADOffice_installer.exe" AUTOINSTALL_EXE="${WH_TMP_DIR}/SCADOffice_installer.exe"
SCADOFFICE_ADDONS_URL="https://cloud.linux-gaming.ru/portproton/scadoffice_addons_v02.tar.xz" SCADOFFICE_ADDONS_URL="https://cloud.linux-gaming.ru/portproton/scadoffice_addons_v02.tar.xz"

BIN
image/nettest_client.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
image/nettest_server.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

39
manualinstall/nettest Normal file
View File

@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# info_ru: Установщик программного комплекса NetTest (демо-версия)
########################################################################
export PROG_URL="https://www.kpolyakov.spb.ru/prog/nettest/nettget.htm"
export WH_WINE_USE="wine_x_tkg_10-0_amd64"
export WINEPREFIX="nettest"
export PROG_NAME="NetTest"
export BASE_PFX="none"
export WINEARCH="win64"
export INSTALL_DLL=""
export WH_WINDOWS_VER="10"
ZIP_FILE="$2"
if [[ -f "$ZIP_FILE" ]] \
&& [[ $ZIP_FILE =~ ".zip" ]]
then
prepair_wine
PROG_PATH="$DRIVE_C/nettest"
if [[ $ZIP_FILE =~ "tests" ]] ; then
unpack "$2" "$PROG_PATH/tests"
print_info "Тесты $(basename "$ZIP_FILE") установлены."
exit 0
else
unpack "$2" "$PROG_PATH"
fi
cp -fr "$PROG_PATH/fonts/"* "$DRIVE_C/windows/Fonts/"
create_desktop "$PROG_NAME (Сервер)" "$PROG_PATH/testser.exe" "nettest_server"
create_desktop "$PROG_NAME (Клиент)" "$PROG_PATH/testcli.exe" "nettest_client"
else
fatal "Не найден файл архива для $PROG_NAME. Перезапустите по примеру:
winehelper install $1 \"/путь/до/архива\""
fi

View File

@@ -5,6 +5,7 @@
fb7fdfde96de10a1b3b051bdf2727b6a7c1768b878483726454dd6726e9e0193 wine-9.0.14-alt1-i586-spravkibk.tar.xz fb7fdfde96de10a1b3b051bdf2727b6a7c1768b878483726454dd6726e9e0193 wine-9.0.14-alt1-i586-spravkibk.tar.xz
e0a84bb4908c3927954d7eef6b8ac7212e442b8c107d000c6890fec340f96183 wine-9.0.14-alt1-amd64.tar.xz e0a84bb4908c3927954d7eef6b8ac7212e442b8c107d000c6890fec340f96183 wine-9.0.14-alt1-amd64.tar.xz
6f86d2220b65b709bf88c6f829a4998de3b929cc2091cd1333a51c32e1491b79 wine-9.0.9-alt1-i586.tar.xz 6f86d2220b65b709bf88c6f829a4998de3b929cc2091cd1333a51c32e1491b79 wine-9.0.9-alt1-i586.tar.xz
f1bf1261550ca2928cefacdb724926d3d6d103433d0ff6882ee9783a50d8f4e4 wine-8.8-staging-amd64.tar.xz
61bec1230b37b8fcc69fd45f848b44fd88cc41fcdd5dc3080336d7da63660f40 wine-7.16.1-alt1-amd64.tar.xz 61bec1230b37b8fcc69fd45f848b44fd88cc41fcdd5dc3080336d7da63660f40 wine-7.16.1-alt1-amd64.tar.xz
6fea17fd131f57c2ebf7ca4c60d3c5a9e819afe16e5d0b77ecb750da99ae0e38 wine-7.16.1-alt1-i586.tar.xz 6fea17fd131f57c2ebf7ca4c60d3c5a9e819afe16e5d0b77ecb750da99ae0e38 wine-7.16.1-alt1-i586.tar.xz
@@ -211,8 +212,8 @@ dfb44ce5e5af7dba1686932c63d6b05e5dd6919a21c78130a7d1d0271b93958e audiorecstatio
# create with wine_x_tkg_10-0_i586 (universal user: xuser) # create with wine_x_tkg_10-0_i586 (universal user: xuser)
# winetricks arial dotnet7 dotnetdesktop7 renderer=gdi # winetricks arial dotnet7 dotnetdesktop7 renderer=gdi
25e277c7afa4a9afc5f013cb05f872c12a7f381c4f0503a423dcacccca9a14c6 scadaoffice_pfx_x64_v03.tar.xz 4fa93434c5c15440014357323257ddcee7d28b94ad6a56bd6f5a08b33ae4c3cb scadaoffice_pfx_x64_v04.tar.xz
# create with wine_x_tkg_10-0_i586 (universal user: xuser) # create with wine-8.8-staging-amd64
# winetricks dotnet48 gdiplus vcrun6sp6 vcrun2005 vcrun2019 d3dx11_42 d3dx11_43 d3dx9 d3dcompiler_42 d3dcompiler_43 d3dcompiler_46 d3dcompiler_47 richtx32 riched30 riched20 msxml6 dotnet20 # winetricks dotnet48 gdiplus vcrun6sp6 vcrun2005 vcrun2019 d3dx11_42 d3dx11_43 d3dx9 d3dcompiler_42 d3dcompiler_43 d3dcompiler_46 d3dcompiler_47 richtx32 riched30 riched20 msxml6 dotnet20
# + addons with ODBC, SSH, *.reg # + addons with ODBC, SSH, *.reg
0f4ef434df07bc338ae308af44330590eaa1d9c94b64850514e55b960642d0eb scadoffice_addons_v02.tar.xz 0f4ef434df07bc338ae308af44330590eaa1d9c94b64850514e55b960642d0eb scadoffice_addons_v02.tar.xz

View File

@@ -126,6 +126,12 @@ WH_TESTINSTALL_DIR="$DATA_PATH/testinstall"
WH_WINETRICKS="$DATA_PATH/winetricks_$WINETRICKS_VERSION" WH_WINETRICKS="$DATA_PATH/winetricks_$WINETRICKS_VERSION"
WH_MENU_DIR="$HOME/.local/share/applications/WineHelper" WH_MENU_DIR="$HOME/.local/share/applications/WineHelper"
# TODO: system menu directory
# /usr/share/desktop-directories/WineHelper.directory
# /etc/xdg/menus/applications-merged/WineHelper.menu
# user menu directory
WH_MENU_CATEGORY="$HOME/.local/share/desktop-directories/WineHelper.directory" WH_MENU_CATEGORY="$HOME/.local/share/desktop-directories/WineHelper.directory"
WH_MENU_CONFIG="$HOME/.config/menus/applications-merged/WineHelper.menu" WH_MENU_CONFIG="$HOME/.config/menus/applications-merged/WineHelper.menu"
@@ -162,12 +168,10 @@ check_variables WINE_WIN_START "start /wait /high /unix"
check_variables WINE_CPU_TOPOLOGY "8" check_variables WINE_CPU_TOPOLOGY "8"
check_variables USE_RENDERER "opengl" # opengl, damavand, proton check_variables DXVK_VER "none"
check_variables DXVK_VER "1.10.3-28"
# check_variables DXVK_CONFIG_FILE "path/to/dxvk.conf" # check_variables DXVK_CONFIG_FILE "path/to/dxvk.conf"
check_variables VKD3D_VER "1.1-2602" check_variables VKD3D_VER "none"
# check_variables VKD3D_LIMIT_TESS_FACTORS 64 # check_variables VKD3D_LIMIT_TESS_FACTORS 64
# check_variables VKD3D_FEATURE_LEVEL "12_0" # check_variables VKD3D_FEATURE_LEVEL "12_0"
@@ -395,10 +399,14 @@ print_license_agreement () {
} }
try_download () { try_download () {
if [[ $WH_USE_GUI == "1" ]] \ if [[ $1 != "cloud" ]] ; then
&& [[ $(ps -o command= -p "$PPID" | awk '{print $2}') =~ "$DATA_PATH/winehelper_gui.py" ]] if [[ $WH_USE_GUI == "1" ]] \
then print_ok "Соглашения приняты из графического интерфейса." && [[ $(ps -o command= -p "$PPID" | awk '{print $2}') =~ "$DATA_PATH/winehelper_gui.py" ]]
else print_license_agreement then print_ok "Соглашения приняты из графического интерфейса."
else print_license_agreement
fi
else
shift
fi fi
local download_file_url output_file output_file_name local download_file_url output_file output_file_name
download_file_url="${1// /%20}" download_file_url="${1// /%20}"
@@ -694,9 +702,11 @@ EOF
echo '#!/usr/bin/env bash' echo '#!/usr/bin/env bash'
echo "# cmd_name: $INSTALL_SCRIPT_NAME" echo "# cmd_name: $INSTALL_SCRIPT_NAME"
} > "$exe_file".whdb } > "$exe_file".whdb
grep -e "info_" -e "#####" -e "export" -e "var_" "$INSTALL_SCRIPT" \
| grep -vE "LAUNCH_PARAMETERS|AUTOINSTALL|WIN_FILE_EXEC|echo" \ grep -e "info_" -e "#####" -e "PROG_URL=" -e "WINEPREFIX=" -e "INSTALL_DLL=" \
-e "PROG_NAME=" -e "PROG_ICON=" -e "var_" "$INSTALL_SCRIPT" \
| awk '{$1=$1;print}' >> "$exe_file".whdb | awk '{$1=$1;print}' >> "$exe_file".whdb
print_info "Создан файл настроек для $exe_file" print_info "Создан файл настроек для $exe_file"
fi fi
} }
@@ -760,31 +770,25 @@ run_installed_programs () {
fi fi
} }
init_wined3d () { copy_wined3d () {
if [[ "$USE_RENDERER" != "proton" ]] ; then for wined3dfiles in $1 ; do
WINED3D_FILES="d3d8 d3d9 d3d10_1 d3d10 d3d10core d3d11 dxgi d3d12 d3d12core" try_copy_wine_dll_to_pfx_64 "$wined3dfiles.dll"
for wined3dfiles in $WINED3D_FILES ; do try_copy_wine_dll_to_pfx_32 "$wined3dfiles.dll"
try_copy_wine_dll_to_pfx_64 "$wined3dfiles.dll" done
try_copy_wine_dll_to_pfx_32 "$wined3dfiles.dll"
done
# if [[ "$USE_RENDERER" == "damavand" ]]
# then export WINE_D3D_CONFIG="renderer=vulkan"
# else export WINE_D3D_CONFIG="renderer=gl"
# fi
return 0
else
return 1
fi
} }
init_dxvk () { init_dxvk () {
check_variables USE_DXVK_VER "$1" DXVK_VER="$1"
if [[ $DXVK_VER == "none" ]] ; then
copy_wined3d "d3d8 d3d9 d3d10_1 d3d10 d3d10core d3d11 dxgi"
return 0
fi
get_dxvk() { get_dxvk() {
local DXVK_URL="$1" local DXVK_URL="$1"
local DXVK_VAR_VER="$2" local DXVK_VAR_VER="$2"
local DXVK_PACKAGE="${WH_VULKAN_LIBDIR}/${DXVK_VAR_VER}.tar.$(echo "${DXVK_URL#*.tar.}")" local DXVK_PACKAGE="${WH_VULKAN_LIBDIR}/${DXVK_VAR_VER}.tar.$(echo "${DXVK_URL#*.tar.}")"
if try_download "$DXVK_URL" "$DXVK_PACKAGE" check256sum \ if try_download cloud "$DXVK_URL" "$DXVK_PACKAGE" check256sum \
&& unpack "$DXVK_PACKAGE" "$WH_VULKAN_LIBDIR" && unpack "$DXVK_PACKAGE" "$WH_VULKAN_LIBDIR"
then then
try_remove_file "$DXVK_PACKAGE" try_remove_file "$DXVK_PACKAGE"
@@ -793,36 +797,37 @@ init_dxvk () {
return 1 return 1
} }
for DXVK_VAR_VER in "$USE_DXVK_VER" $@ ; do if [[ ! -d "${WH_VULKAN_LIBDIR}/${DXVK_VER}" ]] ; then
if [[ ! -d "${WH_VULKAN_LIBDIR}/${DXVK_VAR_VER}" ]] ; then get_dxvk "$CLOUD_URL/${DXVK_VER}.tar.xz" "$DXVK_VER"
get_dxvk "$CLOUD_URL/${DXVK_VAR_VER}.tar.xz" "$DXVK_VAR_VER" fi
fi
done
if [[ "${WH_USE_WINE_DXGI}" == 1 ]] ; then if [[ $WH_USE_WINE_DXGI == "1" ]] ; then
DXVK_FILES="d3d9 d3d10_1 d3d10 d3d11" # dxvk_config openvr_api_dxvk" DXVK_FILES="d3d9 d3d10_1 d3d10 d3d11" # dxvk_config openvr_api_dxvk"
try_copy_wine_dll_to_pfx_64 "dxgi.dll" copy_wined3d "dxgi"
try_copy_wine_dll_to_pfx_32 "dxgi.dll"
else else
DXVK_FILES="d3d9 d3d10_1 d3d10 d3d11 dxgi" # dxvk_config openvr_api_dxvk" DXVK_FILES="d3d9 d3d10_1 d3d10 d3d11 dxgi" # dxvk_config openvr_api_dxvk"
fi fi
for dxvkfiles in $DXVK_FILES ; do for dxvkfiles in $DXVK_FILES ; do
try_copy_other_dll_to_pfx_64 "${WH_VULKAN_LIBDIR}/${USE_DXVK_VER}/x64/$dxvkfiles.dll" try_copy_other_dll_to_pfx_64 "${WH_VULKAN_LIBDIR}/${DXVK_VER}/x64/$dxvkfiles.dll"
if try_copy_other_dll_to_pfx_32 "${WH_VULKAN_LIBDIR}/${USE_DXVK_VER}/x32/$dxvkfiles.dll" if try_copy_other_dll_to_pfx_32 "${WH_VULKAN_LIBDIR}/${DXVK_VER}/x32/$dxvkfiles.dll"
then var_winedlloverride_update "$dxvkfiles=n" then var_winedlloverride_update "$dxvkfiles=n"
fi fi
done done
} }
init_vkd3d () { init_vkd3d () {
check_variables USE_VKD3D_VER "$1" VKD3D_VER="$1"
if [[ $VKD3D_VER == "none" ]] ; then
copy_wined3d "d3d12 d3d12core"
return 0
fi
get_vkd3d() { get_vkd3d() {
local VKD3D_URL="$1" local VKD3D_URL="$1"
local VKD3D_VAR_VER="$2" local VKD3D_VAR_VER="$2"
local VKD3D_PACKAGE="${WH_VULKAN_LIBDIR}/${VKD3D_VAR_VER}.tar.$(echo "${VKD3D_URL#*.tar.}")" local VKD3D_PACKAGE="${WH_VULKAN_LIBDIR}/${VKD3D_VAR_VER}.tar.$(echo "${VKD3D_URL#*.tar.}")"
if try_download "$VKD3D_URL" "$VKD3D_PACKAGE" check256sum \ if try_download cloud "$VKD3D_URL" "$VKD3D_PACKAGE" check256sum \
&& unpack "$VKD3D_PACKAGE" "$WH_VULKAN_LIBDIR" && unpack "$VKD3D_PACKAGE" "$WH_VULKAN_LIBDIR"
then then
try_remove_file "$VKD3D_PACKAGE" try_remove_file "$VKD3D_PACKAGE"
@@ -831,16 +836,14 @@ init_vkd3d () {
return 1 return 1
} }
for VKD3D_VAR_VER in "$USE_VKD3D_VER" $@ ; do if [[ ! -d "${WH_VULKAN_LIBDIR}/${VKD3D_VER}" ]] ; then
if [[ ! -d "${WH_VULKAN_LIBDIR}/${VKD3D_VAR_VER}" ]] ; then get_vkd3d "$CLOUD_URL/${VKD3D_VER}.tar.xz" "$VKD3D_VER"
get_vkd3d "$CLOUD_URL/${VKD3D_VAR_VER}.tar.xz" "$VKD3D_VAR_VER" fi
fi
done
VKD3D_FILES="d3d12 d3d12core libvkd3d-shader-1 libvkd3d-1" # libvkd3d-proton-utils-3 VKD3D_FILES="d3d12 d3d12core libvkd3d-shader-1 libvkd3d-1" # libvkd3d-proton-utils-3
for vkd3dfiles in $VKD3D_FILES ; do for vkd3dfiles in $VKD3D_FILES ; do
try_copy_other_dll_to_pfx_64 "${WH_VULKAN_LIBDIR}/${USE_VKD3D_VER}/x64/$vkd3dfiles.dll" try_copy_other_dll_to_pfx_64 "${WH_VULKAN_LIBDIR}/${VKD3D_VER}/x64/$vkd3dfiles.dll"
if try_copy_other_dll_to_pfx_32 "${WH_VULKAN_LIBDIR}/${USE_VKD3D_VER}/x86/$vkd3dfiles.dll" if try_copy_other_dll_to_pfx_32 "${WH_VULKAN_LIBDIR}/${VKD3D_VER}/x86/$vkd3dfiles.dll"
then var_winedlloverride_update "$vkd3dfiles=n" then var_winedlloverride_update "$vkd3dfiles=n"
fi fi
done done
@@ -855,7 +858,7 @@ init_wine_ver () {
download_url="$CLOUD_URL/$WH_WINE_USE.tar.xz" download_url="$CLOUD_URL/$WH_WINE_USE.tar.xz"
wine_package="$WH_TMP_DIR/$WH_WINE_USE.tar.xz" wine_package="$WH_TMP_DIR/$WH_WINE_USE.tar.xz"
try_download "$download_url" "$wine_package" "check256sum" try_download cloud "$download_url" "$wine_package" "check256sum"
unpack "$wine_package" "$WH_DIST_DIR/" unpack "$wine_package" "$WH_DIST_DIR/"
try_remove_file "$wine_package" try_remove_file "$wine_package"
@@ -908,7 +911,7 @@ init_wine_ver () {
CPCSP_PROXY_NAME="wine-cpcsp_proxy-$CPCSP_PROXY_VER" CPCSP_PROXY_NAME="wine-cpcsp_proxy-$CPCSP_PROXY_VER"
CPCSP_PROXY_URL="$CLOUD_URL/$CPCSP_PROXY_NAME.tar.xz" CPCSP_PROXY_URL="$CLOUD_URL/$CPCSP_PROXY_NAME.tar.xz"
try_download "$CPCSP_PROXY_URL" "$WH_TMP_DIR/$CPCSP_PROXY_NAME.tar.xz" check256sum try_download cloud "$CPCSP_PROXY_URL" "$WH_TMP_DIR/$CPCSP_PROXY_NAME.tar.xz" check256sum
unpack "$WH_TMP_DIR/$CPCSP_PROXY_NAME.tar.xz" "$WH_TMP_DIR" unpack "$WH_TMP_DIR/$CPCSP_PROXY_NAME.tar.xz" "$WH_TMP_DIR"
cp -fr "$WH_TMP_DIR/$CPCSP_PROXY_NAME/"i386-* "$WINEDIR/lib/wine/" cp -fr "$WH_TMP_DIR/$CPCSP_PROXY_NAME/"i386-* "$WINEDIR/lib/wine/"
@@ -1111,6 +1114,11 @@ init_wineprefix () {
export DRIVE_C="$WINEPREFIX/drive_c" export DRIVE_C="$WINEPREFIX/drive_c"
export XUSER_PATH="$DRIVE_C/users/xuser" export XUSER_PATH="$DRIVE_C/users/xuser"
if [[ -d "$XUSER_PATH" ]] \
&& [[ ! -d "$DRIVE_C/users/$USER" ]]
then try_force_link_dir "$XUSER_PATH" "$DRIVE_C/users/$USER"
fi
if [[ ! -f "$WINEPREFIX/.firstboot" ]] ; then if [[ ! -f "$WINEPREFIX/.firstboot" ]] ; then
create_new_dir "$WINEPREFIX" create_new_dir "$WINEPREFIX"
if [[ "$CLEAR_PREFIX" == "1" ]] if [[ "$CLEAR_PREFIX" == "1" ]]
@@ -1179,16 +1187,7 @@ init_wineprefix () {
# добавление ассоциаций файлов для запуска нативного приложения из wine # добавление ассоциаций файлов для запуска нативного приложения из wine
# пример переменной: WH_XDG_OPEN="txt doc pdf" # пример переменной: WH_XDG_OPEN="txt doc pdf"
check_variables WH_XDG_OPEN "0" check_variables WH_XDG_OPEN "0"
# Сохраняем старые ассоциации, чтобы потом удалить те, что больше не нужны
local old_xdg_open
if [[ -f "$WINEPREFIX/last.conf" ]]; then
old_xdg_open=$(grep "WH_XDG_OPEN=" "$WINEPREFIX/last.conf" | sed -e 's/.*WH_XDG_OPEN="//' -e 's/"$//')
fi
# Если переменная WH_XDG_OPEN была установлена извне (например, из GUI),
# то мы должны принудительно установить ее значение в "0", если она пуста,
# чтобы корректно удалить старые ассоциации.
[[ -z "$WH_XDG_OPEN" ]] && WH_XDG_OPEN="0"
local WRAPPER="${WH_TMP_DIR}/wh-xdg-open.sh" local WRAPPER="${WH_TMP_DIR}/wh-xdg-open.sh"
local XDG_OPEN_REG="Software\Classes\xdg-open\shell\open\command" local XDG_OPEN_REG="Software\Classes\xdg-open\shell\open\command"
if [[ $WH_XDG_OPEN != "0" ]] ; then if [[ $WH_XDG_OPEN != "0" ]] ; then
@@ -1211,14 +1210,8 @@ init_wineprefix () {
# добавляем новую команду xdg-open в реестр # добавляем новую команду xdg-open в реестр
get_and_set_reg_file --add "$XDG_OPEN_REG" '@=' 'REG_SZ' "$WRAPPER %1" "system" get_and_set_reg_file --add "$XDG_OPEN_REG" '@=' 'REG_SZ' "$WRAPPER %1" "system"
# Удаляем старые ассоциации, которых нет в новом списке # удаляем старые ассоциации, которых нет в новом списке
if [[ -n "$old_xdg_open" ]]; then sed -i '/@="xdg-open"/d' "$WINEPREFIX/system.reg"
for old_ext in $old_xdg_open; do
if ! echo " $WH_XDG_OPEN " | grep -q " $old_ext "; then
get_and_set_reg_file --delete "Software\Classes\.$old_ext" '@='
fi
done
fi
# добавляем ассоциации файлов для запуска с помощью xdg-open # добавляем ассоциации файлов для запуска с помощью xdg-open
for ext in $WH_XDG_OPEN ; do for ext in $WH_XDG_OPEN ; do
@@ -1291,7 +1284,7 @@ init_wineprefix () {
echo "# переменные последнего использования префикса:" > "$WINEPREFIX/last.conf" echo "# переменные последнего использования префикса:" > "$WINEPREFIX/last.conf"
for var in WH_WINE_USE BASE_PFX WINEARCH WH_WINDOWS_VER WINEESYNC WINEFSYNC \ for var in WH_WINE_USE BASE_PFX WINEARCH WH_WINDOWS_VER WINEESYNC WINEFSYNC \
STAGING_SHARED_MEMORY WINE_LARGE_ADDRESS_AWARE WH_USE_SHADER_CACHE WH_USE_WINE_DXGI \ STAGING_SHARED_MEMORY WINE_LARGE_ADDRESS_AWARE WH_USE_SHADER_CACHE WH_USE_WINE_DXGI \
WINE_CPU_TOPOLOGY USE_RENDERER DXVK_VER VKD3D_VER WH_XDG_OPEN WH_USE_MESA_GL_OVERRIDE WINE_CPU_TOPOLOGY DXVK_VER VKD3D_VER WH_XDG_OPEN WH_USE_MESA_GL_OVERRIDE
do do
echo "export $var=\"${!var}\"" >> "$WINEPREFIX/last.conf" echo "export $var=\"${!var}\"" >> "$WINEPREFIX/last.conf"
done done
@@ -1341,7 +1334,8 @@ use_winetricks () {
} }
kill_wine () { kill_wine () {
wine_pids=$(ls -l /proc/*/exe 2>/dev/null | grep -E 'wine(64)?-preloader|wineserver' | awk -F/ '{print $3}') wine_pids=$(ls -l /proc/*/exe 2>/dev/null | grep -E 'wine(64)?-preloader|wineserver' \
| grep "$USER_WORK_PATH" | awk -F/ '{print $3}')
for pw_kill_pids in ${wine_pids}; do for pw_kill_pids in ${wine_pids}; do
if ps cax | grep "${pw_kill_pids}" ; then if ps cax | grep "${pw_kill_pids}" ; then
@@ -1368,46 +1362,27 @@ init_database () {
if [[ "$WHDB_FILE" != "0" ]] ; then if [[ "$WHDB_FILE" != "0" ]] ; then
print_info "Используется файл настроек: $WHDB_FILE" print_info "Используется файл настроек: $WHDB_FILE"
. "$WHDB_FILE" . "$WHDB_FILE"
elif check_prefix_var && [[ -f "$WINEPREFIX/last.conf" ]] ; then fi
if check_prefix_var && [[ -f "$WINEPREFIX/last.conf" ]] ; then
print_info "Найдены настройки из предыдущего использования префикса: $WINEPREFIX" print_info "Найдены настройки из предыдущего использования префикса: $WINEPREFIX"
# Сохраняем значение WH_XDG_OPEN, если оно было установлено извне (например, из GUI).
# Это предотвращает его перезапись старым значением из last.conf.
# Используем `declare -p` для надежного определения, была ли переменная установлена,
# даже если она пустая (что означает "удалить все ассоциации").
if declare -p WH_XDG_OPEN &>/dev/null; then
wh_xdg_open_from_env="$WH_XDG_OPEN"
fi
cat "$WINEPREFIX/last.conf" cat "$WINEPREFIX/last.conf"
. "$WINEPREFIX/last.conf" . "$WINEPREFIX/last.conf"
else
print_warning "Файл настроек не найден. Пропускаем."
fi fi
} }
prepair_wine () { prepair_wine () {
# Объявляем переменную здесь, чтобы она была доступна после вызова init_database
local wh_xdg_open_from_env
if [[ -n "$INSTALL_SCRIPT_NAME" ]] if [[ -n "$INSTALL_SCRIPT_NAME" ]]
then print_info "Используются настройки из скрипта установки: $INSTALL_SCRIPT_NAME" then print_info "Используются настройки из скрипта установки: $INSTALL_SCRIPT_NAME"
else init_database else init_database
fi fi
# Восстанавливаем значение WH_XDG_OPEN, если оно было установлено извне.
# Проверяем, была ли переменная сохранена, а не ее значение.
# Это позволяет корректно передать пустую строку.
if declare -p wh_xdg_open_from_env &>/dev/null; then
export WH_XDG_OPEN="$wh_xdg_open_from_env"
fi
init_wine_ver init_wine_ver
init_wineprefix init_wineprefix
use_winetricks use_winetricks
init_dxvk "$DXVK_VER"
init_vkd3d "$VKD3D_VER"
if init_wined3d ; then
:
else
init_dxvk "$DXVK_VER"
init_vkd3d "$VKD3D_VER"
fi
[[ "$MANGOHUD" == 1 ]] && MANGOHUD_RUN="mangohud" [[ "$MANGOHUD" == 1 ]] && MANGOHUD_RUN="mangohud"
} }
@@ -1427,9 +1402,9 @@ wine_run () {
echo "##### Лог WINE #####" | tee -a "$LOG_FILE" echo "##### Лог WINE #####" | tee -a "$LOG_FILE"
$MANGOHUD_RUN "$WINELOADER" "$@" $LAUNCH_PARAMETERS 2>&1 | tee -a "$LOG_FILE" $MANGOHUD_RUN "$WINELOADER" "$@" $LAUNCH_PARAMETERS 2>&1 | tee -a "$LOG_FILE"
else else
exec $MANGOHUD_RUN "$WINELOADER" "$@" $LAUNCH_PARAMETERS $MANGOHUD_RUN "$WINELOADER" "$@" $LAUNCH_PARAMETERS
fi fi
# wait_wineserver wait_wineserver
} }
wine_run_install () { wine_run_install () {
@@ -1448,6 +1423,12 @@ wine_run_install () {
} }
run_autoinstall () { run_autoinstall () {
if [[ $WH_USE_GUI == "1" ]] \
&& [[ $(ps -o command= -p "$PPID" | awk '{print $2}') =~ "$DATA_PATH/winehelper_gui.py" ]]
then print_ok "Соглашения приняты из графического интерфейса."
else print_license_agreement
fi
if [[ $1 == "--clear-pfx" ]] ; then if [[ $1 == "--clear-pfx" ]] ; then
export CLEAR_PREFIX="1" export CLEAR_PREFIX="1"
shift shift
@@ -1851,9 +1832,9 @@ create_base_pfx () {
&& [[ ! -L "$users_dir/$USER" ]] && [[ ! -L "$users_dir/$USER" ]]
then then
if [[ -L "$users_dir/xuser" ]] if [[ -L "$users_dir/xuser" ]]
then try_remove_dir "$users_dir/xuser/" then try_remove_dir "$users_dir/xuser"
fi fi
create_new_dir "$users_dir/xuser/" create_new_dir "$users_dir/xuser"
cp -fr "$users_dir/$USER"/* "$users_dir/xuser/" cp -fr "$users_dir/$USER"/* "$users_dir/xuser/"
fi fi
@@ -2188,16 +2169,14 @@ run_install_dxvk() {
fi fi
check_prefix_var check_prefix_var
init_database init_database
export DXVK_VER="$version"
init_wine_ver init_wine_ver
init_wineprefix init_wineprefix
if [[ "$version" == "none" ]] ; then if [[ "$DXVK_VER" == "none" ]]
print_info "Удаление DXVK..." then print_info "Удаление DXVK..."
init_wined3d else print_info "Установка DXVK: $DXVK_VER"
update_last_conf_var "DXVK_VER" ""
else
init_dxvk "$version"
update_last_conf_var "DXVK_VER" "$USE_DXVK_VER"
fi fi
init_dxvk "$DXVK_VER"
wait_wineserver wait_wineserver
} }
@@ -2212,16 +2191,14 @@ run_install_vkd3d() {
fi fi
check_prefix_var check_prefix_var
init_database init_database
export VKD3D_VER="$version"
init_wine_ver init_wine_ver
init_wineprefix init_wineprefix
if [[ "$version" == "none" ]] ; then if [[ "$VKD3D_VER" == "none" ]]
print_info "Удаление VKD3D..." then print_info "Удаление VKD3D..."
init_wined3d else print_info "Установка VKD3D: $VKD3D_VER"
update_last_conf_var "VKD3D_VER" ""
else
init_vkd3d "$version"
update_last_conf_var "VKD3D_VER" "$USE_VKD3D_VER"
fi fi
init_vkd3d "$VKD3D_VER"
wait_wineserver wait_wineserver
} }

View File

@@ -10,11 +10,11 @@ import time
import json import json
import hashlib import hashlib
from functools import partial from functools import partial
from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,QPushButton, QLabel, QTabWidget, QTabBar, from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QLabel, QTabWidget, QTabBar,
QTextEdit, QFileDialog, QMessageBox, QLineEdit, QCheckBox, QStackedWidget, QScrollArea, QFormLayout, QGroupBox, QRadioButton, QComboBox, QTextEdit, QFileDialog, QMessageBox, QLineEdit, QCheckBox, QStackedWidget, QScrollArea, QFormLayout, QGroupBox, QRadioButton, QComboBox,
QListWidget, QListWidgetItem, QGridLayout, QFrame, QDialog, QTextBrowser, QInputDialog, QDialogButtonBox) QListWidget, QListWidgetItem, QGridLayout, QFrame, QDialog, QTextBrowser, QInputDialog, QDialogButtonBox, QSystemTrayIcon, QMenu)
from PyQt5.QtCore import Qt, QProcess, QSize, QTimer, QProcessEnvironment, QPropertyAnimation, QEasingCurve from PyQt5.QtCore import Qt, QProcess, QSize, QTimer, QProcessEnvironment, QPropertyAnimation, QEasingCurve
from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPixmap, QPainter, QDesktopServices from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPixmap, QPainter, QCursor
from PyQt5.QtNetwork import QLocalServer, QLocalSocket from PyQt5.QtNetwork import QLocalServer, QLocalSocket
@@ -1210,9 +1210,9 @@ class CreatePrefixDialog(QDialog):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.parent_gui = parent # Store reference to main window self.parent_gui = parent # Сохранить ссылку на главное окно
self.setWindowTitle("Создание нового префикса") self.setWindowTitle("Создание нового префикса")
self.setMinimumSize(500, 250) self.setMinimumSize(680, 250)
self.setModal(True) self.setModal(True)
# Attributes to store results # Attributes to store results
@@ -1226,9 +1226,22 @@ class CreatePrefixDialog(QDialog):
form_layout = QFormLayout() form_layout = QFormLayout()
form_layout.setSpacing(10) form_layout.setSpacing(10)
# Создаем виджет для поля ввода и предупреждения
name_input_widget = QWidget()
name_input_layout = QVBoxLayout(name_input_widget)
name_input_layout.setContentsMargins(0, 0, 0, 0)
name_input_layout.setSpacing(2)
self.prefix_name_edit = QLineEdit() self.prefix_name_edit = QLineEdit()
self.prefix_name_edit.setPlaceholderText("Например: my_prefix") self.prefix_name_edit.setPlaceholderText("Например: my_prefix")
form_layout.addRow("<b>Имя нового префикса:</b>", self.prefix_name_edit) name_input_layout.addWidget(self.prefix_name_edit)
self.name_warning_label = QLabel("Имя может содержать только латинские буквы, цифры, тире и знаки подчеркивания.")
self.name_warning_label.setStyleSheet("color: red;")
self.name_warning_label.setVisible(False)
name_input_layout.addWidget(self.name_warning_label)
form_layout.addRow("<b>Имя нового префикса:</b>", name_input_widget)
arch_widget = QWidget() arch_widget = QWidget()
arch_layout = QHBoxLayout(arch_widget) arch_layout = QHBoxLayout(arch_widget)
@@ -1285,7 +1298,7 @@ class CreatePrefixDialog(QDialog):
# Connect signals # Connect signals
self.arch_win32_radio.toggled.connect(self.clear_wine_version_selection) self.arch_win32_radio.toggled.connect(self.clear_wine_version_selection)
self.prefix_name_edit.textChanged.connect(self.update_create_button_state) self.prefix_name_edit.textChanged.connect(self.validate_prefix_name)
self.wine_version_edit.textChanged.connect(self.update_create_button_state) self.wine_version_edit.textChanged.connect(self.update_create_button_state)
def open_wine_version_dialog(self): def open_wine_version_dialog(self):
@@ -1301,11 +1314,28 @@ class CreatePrefixDialog(QDialog):
self.wine_version_edit.clear() self.wine_version_edit.clear()
self.selected_wine_version_value = None self.selected_wine_version_value = None
def validate_prefix_name(self, text):
"""Проверяет имя префикса в реальном времени и показывает/скрывает предупреждение."""
valid_pattern = r'^[a-zA-Z0-9_-]*$'
if re.match(valid_pattern, text):
self.name_warning_label.setVisible(False)
else:
# Удаляем недопустимые символы
cleaned_text = re.sub(r'[^a-zA-Z0-9_-]', '', text)
# Блокируем сигналы, чтобы избежать рекурсии при изменении текста
self.prefix_name_edit.blockSignals(True)
self.prefix_name_edit.setText(cleaned_text)
self.prefix_name_edit.blockSignals(False)
self.name_warning_label.setVisible(True)
self.update_create_button_state()
def update_create_button_state(self): def update_create_button_state(self):
"""Включает или выключает кнопку 'Создать'.""" """Включает или выключает кнопку 'Создать'."""
name_ok = bool(self.prefix_name_edit.text().strip()) name_ok = bool(self.prefix_name_edit.text().strip())
version_ok = bool(self.wine_version_edit.text().strip()) version_ok = bool(self.wine_version_edit.text().strip())
self.create_button.setEnabled(name_ok and version_ok) # Кнопка активна, только если имя валидно и версия выбрана
self.create_button.setEnabled(name_ok and version_ok and not self.name_warning_label.isVisible())
def accept_creation(self): def accept_creation(self):
"""Валидирует данные, сохраняет их и закрывает диалог с успехом.""" """Валидирует данные, сохраняет их и закрывает диалог с успехом."""
@@ -1315,8 +1345,8 @@ class CreatePrefixDialog(QDialog):
QMessageBox.warning(self, "Ошибка", "Имя префикса не может быть пустым.") QMessageBox.warning(self, "Ошибка", "Имя префикса не может быть пустым.")
return return
if not re.match(r'^[a-zA-Z0-9_.-]+$', prefix_name): if not re.match(r'^[a-zA-Z0-9_-]+$', prefix_name):
QMessageBox.warning(self, "Ошибка", "Имя префикса может содержать только латинские буквы, цифры, точки, дефисы и подчеркивания.") QMessageBox.warning(self, "Ошибка", "Имя префикса может содержать только латинские буквы, цифры, дефисы и знаки подчеркивания.")
return return
prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name)
@@ -1581,6 +1611,7 @@ class WineHelperGUI(QMainWindow):
self.current_managed_prefix_name = None # Имя префикса, выбранного в выпадающем списке self.current_managed_prefix_name = None # Имя префикса, выбранного в выпадающем списке
self.prefixes_before_install = set() self.prefixes_before_install = set()
self.is_quitting = False # Флаг для корректного выхода из приложения
self.command_output_buffer = "" self.command_output_buffer = ""
self.command_last_line_was_progress = False self.command_last_line_was_progress = False
# Создаем главный виджет и layout # Создаем главный виджет и layout
@@ -1647,6 +1678,50 @@ class WineHelperGUI(QMainWindow):
self.raise_() self.raise_()
self.activateWindow() self.activateWindow()
def create_tray_icon(self):
"""Создает и настраивает иконку в системном трее."""
if not QSystemTrayIcon.isSystemTrayAvailable():
print("Системный трей не доступен.")
return
self.tray_icon = QSystemTrayIcon(self)
icon_path = Var.WH_ICON_PATH
if icon_path and os.path.exists(icon_path):
pixmap = QPixmap(icon_path)
if not pixmap.isNull():
self.tray_icon.setIcon(QIcon(pixmap))
# Создаем и сохраняем меню как атрибут класса, чтобы оно не удалялось
self.tray_menu = QMenu(self)
toggle_visibility_action = self.tray_menu.addAction("Показать/Скрыть")
toggle_visibility_action.triggered.connect(self.toggle_visibility)
self.tray_menu.addSeparator()
quit_action = self.tray_menu.addAction("Выход")
quit_action.triggered.connect(self.quit_application)
self.tray_icon.activated.connect(self.on_tray_icon_activated)
self.tray_icon.show()
def on_tray_icon_activated(self, reason):
"""Обрабатывает клики по иконке в трее."""
# Показываем меню при левом клике
if reason == QSystemTrayIcon.Trigger:
# Получаем позицию курсора и показываем меню
self.tray_menu.popup(QCursor.pos())
def toggle_visibility(self):
"""Переключает видимость главного окна."""
if self.isVisible() and self.isActiveWindow():
self.hide()
else:
# Сначала скрываем, чтобы "сбросить" состояние, затем активируем.
# Это помогает обойти проблемы с фокусом и переключением рабочих столов.
self.hide()
self.activate()
def add_tab(self, widget, title): def add_tab(self, widget, title):
"""Добавляет вкладку в кастомный TabBar и страницу в StackedWidget.""" """Добавляет вкладку в кастомный TabBar и страницу в StackedWidget."""
self.tab_bar.addTab(title) self.tab_bar.addTab(title)
@@ -2088,7 +2163,7 @@ class WineHelperGUI(QMainWindow):
# --- Контейнер для выбора и управления созданными префиксами --- # --- Контейнер для выбора и управления созданными префиксами ---
self.management_container_groupbox = QGroupBox() self.management_container_groupbox = QGroupBox()
self.management_container_groupbox.setVisible(False) # Скрыт, пока нет префиксов self.management_container_groupbox.setVisible(True) # Всегда виден
container_layout = QVBoxLayout(self.management_container_groupbox) container_layout = QVBoxLayout(self.management_container_groupbox)
selector_layout = QHBoxLayout() selector_layout = QHBoxLayout()
@@ -2097,6 +2172,13 @@ class WineHelperGUI(QMainWindow):
self.created_prefix_selector.currentIndexChanged.connect(self.on_created_prefix_selected) self.created_prefix_selector.currentIndexChanged.connect(self.on_created_prefix_selected)
selector_layout.addWidget(self.created_prefix_selector, 1) selector_layout.addWidget(self.created_prefix_selector, 1)
self.create_base_pfx_button = QPushButton()
self.create_base_pfx_button.setIcon(QIcon.fromTheme("document-export"))
self.create_base_pfx_button.setToolTip("Создать шаблон из выбранного префикса (для опытных пользователей)")
self.create_base_pfx_button.setEnabled(False)
self.create_base_pfx_button.clicked.connect(self.create_base_prefix_from_selected)
selector_layout.addWidget(self.create_base_pfx_button)
self.delete_prefix_button = QPushButton() self.delete_prefix_button = QPushButton()
self.delete_prefix_button.setIcon(QIcon.fromTheme("user-trash")) self.delete_prefix_button.setIcon(QIcon.fromTheme("user-trash"))
self.delete_prefix_button.setToolTip("Удалить выбранный префикс") self.delete_prefix_button.setToolTip("Удалить выбранный префикс")
@@ -2261,8 +2343,8 @@ class WineHelperGUI(QMainWindow):
def _load_created_prefixes(self): def _load_created_prefixes(self):
"""Загружает и обновляет список созданных префиксов в выпадающем списке.""" """Загружает и обновляет список созданных префиксов в выпадающем списке."""
prefixes_root_path = os.path.join(Var.USER_WORK_PATH, "prefixes") prefixes_root_path = os.path.join(Var.USER_WORK_PATH, "prefixes")
if not os.path.isdir(prefixes_root_path): has_prefixes_dir = os.path.isdir(prefixes_root_path)
self.management_container_groupbox.setVisible(False) if not has_prefixes_dir:
return return
try: try:
@@ -2281,18 +2363,16 @@ class WineHelperGUI(QMainWindow):
self.created_prefix_selector.blockSignals(False) self.created_prefix_selector.blockSignals(False)
if not prefix_names: if not prefix_names:
self.management_container_groupbox.setVisible(False)
self.on_created_prefix_selected(-1) # Убедимся, что панель управления сброшена self.on_created_prefix_selected(-1) # Убедимся, что панель управления сброшена
return return
self.management_container_groupbox.setVisible(True)
def on_created_prefix_selected(self, index): def on_created_prefix_selected(self, index):
"""Обрабатывает выбор префикса из выпадающего списка.""" """Обрабатывает выбор префикса из выпадающего списка."""
if index == -1: if index == -1:
self.current_managed_prefix_name = None self.current_managed_prefix_name = None
self._setup_prefix_management_panel(None) self._setup_prefix_management_panel(None)
self.delete_prefix_button.setEnabled(False) self.delete_prefix_button.setEnabled(False)
self.create_base_pfx_button.setEnabled(False)
else: else:
# Прокручиваем к выбранному элементу, чтобы он был виден в списке # Прокручиваем к выбранному элементу, чтобы он был виден в списке
self.created_prefix_selector.view().scrollTo( self.created_prefix_selector.view().scrollTo(
@@ -2302,6 +2382,7 @@ class WineHelperGUI(QMainWindow):
self.current_managed_prefix_name = prefix_name self.current_managed_prefix_name = prefix_name
self._setup_prefix_management_panel(prefix_name) self._setup_prefix_management_panel(prefix_name)
self.delete_prefix_button.setEnabled(True) self.delete_prefix_button.setEnabled(True)
self.create_base_pfx_button.setEnabled(True)
def delete_selected_prefix(self): def delete_selected_prefix(self):
"""Удаляет префикс, выбранный в выпадающем списке на вкладке 'Менеджер префиксов'.""" """Удаляет префикс, выбранный в выпадающем списке на вкладке 'Менеджер префиксов'."""
@@ -2366,6 +2447,50 @@ class WineHelperGUI(QMainWindow):
else: else:
QMessageBox.critical(self, "Ошибка удаления", f"Не удалось удалить префикс '{prefix_name}'.\nПодробности смотрите в логе.") QMessageBox.critical(self, "Ошибка удаления", f"Не удалось удалить префикс '{prefix_name}'.\nПодробности смотрите в логе.")
def create_base_prefix_from_selected(self):
"""Создает шаблон префикса из выбранного в выпадающем списке."""
prefix_name = self.current_managed_prefix_name
if not prefix_name:
return
msg_box = QMessageBox(self)
msg_box.setIcon(QMessageBox.Question)
msg_box.setWindowTitle("Создание шаблона префикса")
msg_box.setText(
f"Будет создан 'шаблон' из префикса '{prefix_name}'.\n"
"Это продвинутая функция для создания базовых архивов префиксов.\n\n"
"Продолжить?"
)
yes_button = msg_box.addButton("Да, создать", QMessageBox.YesRole)
no_button = msg_box.addButton("Нет", QMessageBox.NoRole)
msg_box.setDefaultButton(no_button)
msg_box.exec_()
if msg_box.clickedButton() != yes_button:
return
self.command_dialog = QDialog(self)
self.command_dialog.setWindowTitle(f"Создание шаблона: {prefix_name}")
self.command_dialog.setMinimumSize(750, 400)
self.command_dialog.setModal(True)
self.command_dialog.setWindowFlags(self.command_dialog.windowFlags() & ~Qt.WindowCloseButtonHint)
layout = QVBoxLayout()
self.command_log_output = QTextEdit()
self.command_log_output.setReadOnly(True)
layout.addWidget(self.command_log_output)
self.command_close_button = QPushButton("Закрыть")
self.command_close_button.setEnabled(False)
self.command_close_button.clicked.connect(self.command_dialog.close)
layout.addWidget(self.command_close_button)
self.command_dialog.setLayout(layout)
self._run_simple_command("create-base-pfx", [prefix_name])
self.command_dialog.exec_()
def _setup_prefix_management_panel(self, prefix_name): def _setup_prefix_management_panel(self, prefix_name):
"""Настраивает панель управления префиксом на основе текущего состояния.""" """Настраивает панель управления префиксом на основе текущего состояния."""
is_prefix_selected = bool(prefix_name) is_prefix_selected = bool(prefix_name)
@@ -2585,11 +2710,12 @@ class WineHelperGUI(QMainWindow):
# Для удаления лицензия не нужна, запускаем сразу. # Для удаления лицензия не нужна, запускаем сразу.
self.run_component_install_command(prefix_name, command, version) self.run_component_install_command(prefix_name, command, version)
else: else:
# Установка: сначала показываем лицензионное соглашение. # Установка: для DXVK и VKD3D лицензию не показываем.
if not self._show_license_agreement_dialog(): if component not in ['dxvk', 'vkd3d-proton']:
return # Пользователь отклонил лицензию if not self._show_license_agreement_dialog():
return # Пользователь отклонил лицензию
# Если лицензия принята, запускаем установку. # Запускаем установку.
self.run_component_install_command(prefix_name, command, version) self.run_component_install_command(prefix_name, command, version)
def open_wine_version_manager(self): def open_wine_version_manager(self):
@@ -2620,9 +2746,6 @@ class WineHelperGUI(QMainWindow):
new_version = dialog.selected_version new_version = dialog.selected_version
new_version_display = dialog.selected_display_text new_version_display = dialog.selected_display_text
if not self._show_license_agreement_dialog():
return # Пользователь отклонил лицензию
self.run_change_wine_version_command(prefix_name, new_version, new_version_display) self.run_change_wine_version_command(prefix_name, new_version, new_version_display)
def run_change_wine_version_command(self, prefix_name, new_version, new_version_display): def run_change_wine_version_command(self, prefix_name, new_version, new_version_display):
@@ -2743,17 +2866,43 @@ class WineHelperGUI(QMainWindow):
QMessageBox.warning(self, "Ошибка", "Сначала выберите префикс.") QMessageBox.warning(self, "Ошибка", "Сначала выберите префикс.")
return return
current_associations = self._get_prefix_component_version(prefix_name, "WH_XDG_OPEN") or "" current_associations = self._get_prefix_component_version(prefix_name, "WH_XDG_OPEN") or "0"
dialog = FileAssociationsDialog(current_associations, self) dialog = FileAssociationsDialog(current_associations if current_associations != "0" else "", self)
if dialog.exec_() == QDialog.Accepted: if dialog.exec_() == QDialog.Accepted:
new_associations = dialog.new_associations new_associations = dialog.new_associations
# Запускаем обновление, только если значение изменилось # Запускаем обновление, только если значение изменилось
if new_associations != current_associations: if new_associations != (current_associations if current_associations != "0" else "0"):
self.run_update_associations_command(prefix_name, new_associations) self.run_update_associations_command(prefix_name, new_associations)
def run_update_associations_command(self, prefix_name, new_associations): def run_update_associations_command(self, prefix_name, new_associations):
"""Выполняет команду обновления ассоциаций файлов.""" """Выполняет команду обновления ассоциаций файлов."""
# --- Прямое редактирование last.conf, чтобы обойти перезапись переменных в winehelper ---
last_conf_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name, "last.conf")
if not os.path.exists(last_conf_path):
QMessageBox.critical(self, "Ошибка", f"Файл конфигурации last.conf не найден для префикса '{prefix_name}'.")
return
try:
with open(last_conf_path, 'r', encoding='utf-8') as f:
lines = f.readlines()
updated = False
for i, line in enumerate(lines):
if line.strip().startswith("export WH_XDG_OPEN="):
lines[i] = f'export WH_XDG_OPEN="{new_associations}"\n'
updated = True
break
if not updated:
lines.append(f'export WH_XDG_OPEN="{new_associations}"\n')
with open(last_conf_path, 'w', encoding='utf-8') as f:
f.writelines(lines)
except IOError as e:
QMessageBox.critical(self, "Ошибка записи", f"Не удалось обновить файл last.conf: {e}")
return
prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name) prefix_path = os.path.join(Var.USER_WORK_PATH, "prefixes", prefix_name)
self.command_dialog = QDialog(self) self.command_dialog = QDialog(self)
@@ -2779,16 +2928,14 @@ class WineHelperGUI(QMainWindow):
self.command_process.readyReadStandardOutput.connect(self._handle_command_output) self.command_process.readyReadStandardOutput.connect(self._handle_command_output)
self.command_process.finished.connect( self.command_process.finished.connect(
lambda exit_code, exit_status: self._handle_component_install_finished( lambda exit_code, exit_status: self._handle_component_install_finished(
prefix_name, exit_code, exit_status prefix_name, exit_code, exit_status))
)
)
env = QProcessEnvironment.systemEnvironment() env = QProcessEnvironment.systemEnvironment()
env.insert("WINEPREFIX", prefix_path) env.insert("WINEPREFIX", prefix_path)
# Устанавливаем новую переменную окружения для скрипта # Переменная WH_XDG_OPEN теперь читается из измененного last.conf
env.insert("WH_XDG_OPEN", new_associations)
self.command_process.setProcessEnvironment(env) self.command_process.setProcessEnvironment(env)
# Вызываем init-prefix, который теперь прочитает правильное значение из last.conf
args = ["init-prefix"] args = ["init-prefix"]
self.command_log_output.append(f"Выполнение: {shlex.quote(self.winehelper_path)} {' '.join(shlex.quote(a) for a in args)}") self.command_log_output.append(f"Выполнение: {shlex.quote(self.winehelper_path)} {' '.join(shlex.quote(a) for a in args)}")
self.command_process.start(self.winehelper_path, args) self.command_process.start(self.winehelper_path, args)
@@ -2894,14 +3041,15 @@ class WineHelperGUI(QMainWindow):
authors_text = QTextEdit() authors_text = QTextEdit()
authors_text.setReadOnly(True) authors_text.setReadOnly(True)
authors_text.setHtml(""" authors_text.setHtml("""
<div style="text-align: center;"> <div style="text-align: center; font-size: 10pt;">
<h2>Разработчики</h2> <p><span style="font-size: 11pt;"><b>Разработчики</b></span><br>
Михаил Тергоев (fidel)<br> Михаил Тергоев (fidel)<br>
Сергей Пальчех (minergenon)</p> Сергей Пальчех (minergenon)</p>
<p><b>Помощники</b><br> <p><span style="font-size: 11pt;"><b>Помощники</b></span><br>
Иван Мажукин (vanomj)</p> Иван Мажукин (vanomj)</p>
<p><b>Идея и поддержка:</b><br> <p><span style="font-size: 11pt;"><b>Идея и поддержка</b></span><br>
сообщество ALT Linux</p> ООО "Базальт СПО"<br>
ALT Linux Team</p>
<br> <br>
<p>Отдельная благодарность всем, кто вносит свой вклад в развитие проекта,<br> <p>Отдельная благодарность всем, кто вносит свой вклад в развитие проекта,<br>
тестирует и сообщает об ошибках!</p> тестирует и сообщает об ошибках!</p>
@@ -3079,9 +3227,6 @@ class WineHelperGUI(QMainWindow):
self.created_prefix_selector.setCurrentText(prefix_name) self.created_prefix_selector.setCurrentText(prefix_name)
if not self.management_container_groupbox.isVisible():
self.management_container_groupbox.setVisible(True)
def update_installed_apps(self): def update_installed_apps(self):
"""Обновляет список установленных приложений в виде кнопок""" """Обновляет список установленных приложений в виде кнопок"""
# Если активная кнопка находится в списке удаляемых, сбрасываем ее # Если активная кнопка находится в списке удаляемых, сбрасываем ее
@@ -3666,8 +3811,14 @@ class WineHelperGUI(QMainWindow):
QMessageBox.critical(self, "Ошибка", QMessageBox.critical(self, "Ошибка",
f"Не удалось обработать команду запуска:\n{command_str}\n\nОшибка: {str(e)}") f"Не удалось обработать команду запуска:\n{command_str}\n\nОшибка: {str(e)}")
def quit_application(self):
"""Инициирует процесс выхода из приложения."""
self.is_quitting = True
self.close() # Инициируем событие закрытия, которое будет обработано в closeEvent
def closeEvent(self, event): def closeEvent(self, event):
"""Обрабатывает событие закрытия главного окна.""" """Обрабатывает событие закрытия главного окна."""
# Теперь любое закрытие окна (крестик или выход из меню) инициирует выход
if self.running_apps: if self.running_apps:
msg_box = QMessageBox(self) msg_box = QMessageBox(self)
msg_box.setWindowTitle('Подтверждение выхода') msg_box.setWindowTitle('Подтверждение выхода')
@@ -3683,18 +3834,27 @@ class WineHelperGUI(QMainWindow):
msg_box.exec_() msg_box.exec_()
if msg_box.clickedButton() == yes_button: if msg_box.clickedButton() == yes_button:
# Корректно завершаем все дочерние процессы # Отключаем обработчики сигналов от всех запущенных процессов,
for desktop_path, process in list(self.running_apps.items()): # так как мы собираемся их принудительно завершить и выйти.
if process.state() == QProcess.Running: # Это предотвращает ошибку RuntimeError при закрытии.
print(f"Завершение процесса для {desktop_path}...") for process in self.running_apps.values():
process.terminate() process.finished.disconnect()
if not process.waitForFinished(2000): # Ждем 2 сек
process.kill() # Если не закрылся, убиваем # Используем встроенную команду killall для надежного завершения всех процессов wine
print("Завершение всех запущенных приложений через 'winehelper killall'...")
# Используем subprocess.run, который дождется завершения команды
subprocess.run([self.winehelper_path, "killall"], check=False, capture_output=True)
# Принудительно дожидаемся завершения всех дочерних процессов
for process in self.running_apps.values():
process.waitForFinished(5000) # Ждем до 5 секунд
QApplication.instance().quit()
event.accept() event.accept()
else: else:
event.ignore() event.ignore()
else: else:
super().closeEvent(event) QApplication.instance().quit() # Если нет запущенных приложений, просто выходим
def uninstall_app(self): def uninstall_app(self):
"""Удаляет выбранное установленное приложение и его префикс""" """Удаляет выбранное установленное приложение и его префикс"""
@@ -4325,13 +4485,22 @@ class WineHelperGUI(QMainWindow):
self.install_process.terminate() self.install_process.terminate()
def _handle_command_output(self): def _handle_command_output(self):
"""Обрабатывает вывод для диалога команды""" """Обрабатывает вывод для общих команд в модальном диалоге."""
if hasattr(self, 'command_process') and self.command_process: if hasattr(self, 'command_process') and self.command_process:
output = self.command_process.readAllStandardOutput().data().decode('utf-8', errors='ignore').strip() # Используем readAll, чтобы получить и stdout, и stderr
output_bytes = self.command_process.readAll()
output = output_bytes.data().decode('utf-8', errors='ignore').strip()
if output and hasattr(self, 'command_log_output'): if output and hasattr(self, 'command_log_output'):
self.command_log_output.append(output) self.command_log_output.append(output)
QApplication.processEvents() QApplication.processEvents()
def _run_simple_command(self, command, args=None):
"""Запускает простую команду winehelper и выводит лог."""
self.command_process = QProcess(self.command_dialog)
self.command_process.setProcessChannelMode(QProcess.MergedChannels)
self.command_process.readyReadStandardOutput.connect(self._handle_command_output)
self.command_process.finished.connect(self._handle_command_finished)
self.command_process.start(self.winehelper_path, [command] + (args or []))
def _handle_command_finished(self, exit_code, exit_status): def _handle_command_finished(self, exit_code, exit_status):
"""Обрабатывает завершение для диалога команды""" """Обрабатывает завершение для диалога команды"""
if exit_code == 0: if exit_code == 0:
@@ -4432,6 +4601,8 @@ def main():
# Сохраняем ссылку на сервер, чтобы он не был удален сборщиком мусора # Сохраняем ссылку на сервер, чтобы он не был удален сборщиком мусора
window.server = server window.server = server
window.show() window.show()
# Создаем иконку в системном трее после создания окна
window.create_tray_icon()
return app.exec_() return app.exec_()
return 1 return 1