mirror of
https://github.com/relativemodder/aegnux.git
synced 2025-12-10 05:29:38 +05:00
first version
This commit is contained in:
BIN
assets/msxml3.zip
Normal file
BIN
assets/msxml3.zip
Normal file
Binary file not shown.
BIN
bin/cabextract
Executable file
BIN
bin/cabextract
Executable file
Binary file not shown.
19627
bin/winetricks
Executable file
19627
bin/winetricks
Executable file
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,25 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
|
LOG_THROTTLE_SECONDS=0.1
|
||||||
DESKTOP_FILE_NAME='com.relative.Aegnux'
|
DESKTOP_FILE_NAME='com.relative.Aegnux'
|
||||||
|
|
||||||
BASE_DIR = os.getcwd()
|
BASE_DIR = os.getcwd()
|
||||||
|
|
||||||
AE_ICON_PATH = BASE_DIR + '/icons/afterfx.png'
|
AE_ICON_PATH = BASE_DIR + '/icons/afterfx.png'
|
||||||
STYLES_PATH = BASE_DIR + '/styles'
|
STYLES_PATH = BASE_DIR + '/styles'
|
||||||
|
|
||||||
|
WINE_RUNNER_TAR = BASE_DIR + '/assets/wine-10.17-amd64-wow64.tar.gz'
|
||||||
|
WINETRICKS_BIN = BASE_DIR + '/bin/winetricks'
|
||||||
|
CABEXTRACT_BIN = BASE_DIR + '/bin/cabextract'
|
||||||
|
|
||||||
|
VCR_ZIP = BASE_DIR + '/assets/vcr.zip'
|
||||||
|
MSXML_ZIP = BASE_DIR + '/assets/msxml3.zip'
|
||||||
|
|
||||||
|
WINE_STYLE_REG = STYLES_PATH + '/wine_dark_theme.reg'
|
||||||
|
|
||||||
|
AE_DOWNLOAD_URL = 'https://huggingface.co/cutefishae/AeNux-model/resolve/main/2024.zip'
|
||||||
|
AE_PLUGINS_URL = 'https://huggingface.co/cutefishae/AeNux-model/resolve/main/aenux-require-plugin.zip'
|
||||||
|
|
||||||
|
AE_FILENAME = '/tmp/ae2024.zip'
|
||||||
|
|
||||||
|
DOWNLOAD_CHUNK_SIZE=1024
|
||||||
162
src/installationthread.py
Normal file
162
src/installationthread.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
from src.config import (
|
||||||
|
AE_DOWNLOAD_URL, AE_FILENAME,
|
||||||
|
WINE_RUNNER_TAR, WINETRICKS_BIN,
|
||||||
|
CABEXTRACT_BIN, WINE_STYLE_REG,
|
||||||
|
VCR_ZIP, MSXML_ZIP
|
||||||
|
)
|
||||||
|
from src.processthread import ProcessThread
|
||||||
|
from src.utils import (
|
||||||
|
DownloadMethod, get_aegnux_installation_dir,
|
||||||
|
get_ae_install_dir, get_wine_runner_dir, is_nvidia_present,
|
||||||
|
get_winetricks_bin, get_wineprefix_dir, get_cabextract_bin,
|
||||||
|
get_vcr_dir_path, get_msxml_dir_path, mark_aegnux_as_installed
|
||||||
|
)
|
||||||
|
|
||||||
|
class InstallationThread(ProcessThread):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def set_download_method(self, method: DownloadMethod):
|
||||||
|
self.download_method = method
|
||||||
|
|
||||||
|
def set_offline_filename(self, filename: str):
|
||||||
|
self.ae_filename = filename
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
self.log_signal.emit(f'[CLEANUP] Removing temporary AE .zip file')
|
||||||
|
if self.download_method == DownloadMethod.ONLINE:
|
||||||
|
os.remove(AE_FILENAME)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
self.progress_signal.emit(10)
|
||||||
|
|
||||||
|
if self.download_method == DownloadMethod.ONLINE:
|
||||||
|
self.download_file_to(AE_DOWNLOAD_URL, AE_FILENAME)
|
||||||
|
self.ae_filename = AE_FILENAME
|
||||||
|
|
||||||
|
self.progress_signal.emit(15)
|
||||||
|
|
||||||
|
self.log_signal.emit(f'[DEBUG] Unpacking AE from {self.ae_filename}...')
|
||||||
|
self.unpack_zip(self.ae_filename, get_aegnux_installation_dir().as_posix())
|
||||||
|
os.rename(get_aegnux_installation_dir().joinpath('Support Files'), get_ae_install_dir())
|
||||||
|
|
||||||
|
self.progress_signal.emit(20)
|
||||||
|
|
||||||
|
self.log_signal.emit(f'[DEBUG] Unpacking Wine Runner from {WINE_RUNNER_TAR}...')
|
||||||
|
self.unpack_tar(WINE_RUNNER_TAR, get_wine_runner_dir().as_posix())
|
||||||
|
|
||||||
|
self.progress_signal.emit(30)
|
||||||
|
|
||||||
|
self.log_signal.emit(f'[DEBUG] Copying winetricks to {get_winetricks_bin()}...')
|
||||||
|
shutil.copy(WINETRICKS_BIN, get_winetricks_bin())
|
||||||
|
|
||||||
|
self.progress_signal.emit(35)
|
||||||
|
|
||||||
|
self.log_signal.emit(f'[DEBUG] Copying cabextract to {get_cabextract_bin()}...')
|
||||||
|
shutil.copy(CABEXTRACT_BIN, get_cabextract_bin())
|
||||||
|
|
||||||
|
self.progress_signal.emit(40)
|
||||||
|
|
||||||
|
self.log_signal.emit(f'[DEBUG] Initializing wineprefix in {get_wineprefix_dir()}...')
|
||||||
|
self.run_command(['wineboot'], in_prefix=True)
|
||||||
|
|
||||||
|
self.progress_signal.emit(50)
|
||||||
|
|
||||||
|
self.log_signal.emit(f'[DEBUG] Tweaking visual settings in prefix')
|
||||||
|
self.run_command(['wine', 'regedit', WINE_STYLE_REG], in_prefix=True)
|
||||||
|
|
||||||
|
self.progress_signal.emit(55)
|
||||||
|
|
||||||
|
self.log_signal.emit(f'[DEBUG] [WORKAROUND] Killing wineserver')
|
||||||
|
self.run_command(['wineserver', '-k'], in_prefix=True)
|
||||||
|
|
||||||
|
self.progress_signal.emit(60)
|
||||||
|
|
||||||
|
tweaks = ['dxvk', 'corefonts', 'gdiplus', 'fontsmooth=rgb']
|
||||||
|
for tweak in tweaks:
|
||||||
|
self.log_signal.emit(f'[DEBUG] Installing {tweak} with winetricks')
|
||||||
|
self.run_command(['winetricks', '-q', tweak], in_prefix=True)
|
||||||
|
self.progress_signal.emit(60 + tweaks.index(tweak) * 2)
|
||||||
|
|
||||||
|
self.progress_signal.emit(70)
|
||||||
|
|
||||||
|
self.log_signal.emit(f'[DEBUG] Unpacking VCR to {get_vcr_dir_path()}...')
|
||||||
|
self.unpack_zip(VCR_ZIP, get_vcr_dir_path().as_posix())
|
||||||
|
|
||||||
|
self.progress_signal.emit(80)
|
||||||
|
|
||||||
|
self.log_signal.emit(f'[DEBUG] Installing VCR')
|
||||||
|
self.run_command(['wine', get_vcr_dir_path().joinpath('install_all.bat').as_posix()], in_prefix=True)
|
||||||
|
|
||||||
|
self.progress_signal.emit(85)
|
||||||
|
|
||||||
|
self.log_signal.emit(f'[DEBUG] Unpacking MSXML3 to {get_msxml_dir_path()}...')
|
||||||
|
self.unpack_zip(MSXML_ZIP, get_msxml_dir_path().as_posix())
|
||||||
|
|
||||||
|
self.progress_signal.emit(90)
|
||||||
|
|
||||||
|
self.log_signal.emit(f'[DEBUG] Overriding MSXML3 DLL...')
|
||||||
|
system32_dir = get_wineprefix_dir().joinpath('drive_c/windows/system32')
|
||||||
|
shutil.copy(get_msxml_dir_path().joinpath('msxml3.dll'), system32_dir.joinpath('msxml3.dll'))
|
||||||
|
shutil.copy(get_msxml_dir_path().joinpath('msxml3r.dll'), system32_dir.joinpath('msxml3r.dll'))
|
||||||
|
|
||||||
|
self.run_command(
|
||||||
|
['wine', 'reg', 'add',
|
||||||
|
'HKCU\\Software\\Wine\\DllOverrides', '/v',
|
||||||
|
'msxml3', '/d', 'native,builtin', '/f'],
|
||||||
|
in_prefix=True
|
||||||
|
)
|
||||||
|
|
||||||
|
if is_nvidia_present():
|
||||||
|
self.log_signal.emit("[INFO] Starting NVIDIA libs installation...")
|
||||||
|
self.install_nvidia_libs()
|
||||||
|
|
||||||
|
self.progress_signal.emit(99)
|
||||||
|
|
||||||
|
self.cleanup()
|
||||||
|
|
||||||
|
mark_aegnux_as_installed()
|
||||||
|
|
||||||
|
self.progress_signal.emit(100)
|
||||||
|
|
||||||
|
self.finished_signal.emit(True)
|
||||||
|
except Exception as e:
|
||||||
|
self.log_signal.emit(f'[ERROR] {e}')
|
||||||
|
self.finished_signal.emit(False)
|
||||||
|
|
||||||
|
def install_nvidia_libs(self):
|
||||||
|
download_url = "https://github.com/SveSop/nvidia-libs/releases/download/v0.8.5/nvidia-libs-v0.8.5.tar.xz"
|
||||||
|
nvidia_libs_dir = get_wineprefix_dir().joinpath("nvidia-libs")
|
||||||
|
os.makedirs(nvidia_libs_dir, exist_ok=True)
|
||||||
|
tar_file = nvidia_libs_dir.joinpath("nvidia-libs-v0.8.5.tar.xz")
|
||||||
|
|
||||||
|
self.download_file_to(download_url, tar_file)
|
||||||
|
self.log_signal.emit("[DEBUG] Download completed.")
|
||||||
|
self.log_signal.emit("[DEBUG] Extracting NVIDIA libs...")
|
||||||
|
|
||||||
|
extract_dir = nvidia_libs_dir.joinpath("nvidia-libs-v0.8.5")
|
||||||
|
|
||||||
|
self.unpack_tar(tar_file, nvidia_libs_dir)
|
||||||
|
|
||||||
|
self.log_signal.emit("[DEBUG] Extraction completed.")
|
||||||
|
|
||||||
|
os.remove(tar_file)
|
||||||
|
|
||||||
|
self.log_signal.emit("[DEBUG] Running setup script...")
|
||||||
|
|
||||||
|
setup_script = extract_dir.joinpath("setup_nvlibs.sh")
|
||||||
|
|
||||||
|
self.log_signal.emit(f"[DEBUG] Running: {setup_script} install")
|
||||||
|
|
||||||
|
returncode = self.run_command([setup_script.as_posix(), 'install'], in_prefix=True)
|
||||||
|
|
||||||
|
if returncode != 0:
|
||||||
|
self.log_signal.emit(f"[ERROR] Setup script failed with return code {returncode}.")
|
||||||
|
self.finished_signal.emit(False)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.log_signal.emit("[INFO] NVIDIA libs installation completed!")
|
||||||
|
|
||||||
13
src/killaethread.py
Normal file
13
src/killaethread.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from src.processthread import ProcessThread
|
||||||
|
|
||||||
|
class KillAEThread(ProcessThread):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.run_command(
|
||||||
|
['wineserver', '-k'],
|
||||||
|
in_prefix=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.finished_signal.emit(True)
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
|
import os
|
||||||
from ui.mainwindow import MainWindowUI
|
from ui.mainwindow import MainWindowUI
|
||||||
from translations import gls
|
from translations import gls
|
||||||
from PySide6.QtCore import Slot
|
from PySide6.QtCore import Slot
|
||||||
|
from PySide6.QtWidgets import QFileDialog
|
||||||
|
from src.installationthread import InstallationThread
|
||||||
|
from src.runaethread import RunAEThread
|
||||||
|
from src.killaethread import KillAEThread
|
||||||
|
from src.removeaethread import RemoveAEThread
|
||||||
|
from src.utils import show_download_method_dialog, get_ae_plugins_dir, get_wineprefix_dir
|
||||||
|
from src.types import DownloadMethod
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(MainWindowUI):
|
class MainWindow(MainWindowUI):
|
||||||
@@ -9,7 +17,93 @@ class MainWindow(MainWindowUI):
|
|||||||
|
|
||||||
self.setWindowTitle(gls('welcome_win_title'))
|
self.setWindowTitle(gls('welcome_win_title'))
|
||||||
self.install_button.clicked.connect(self.install_button_clicked)
|
self.install_button.clicked.connect(self.install_button_clicked)
|
||||||
|
self.run_button.clicked.connect(self.run_ae_button_clicked)
|
||||||
|
self.kill_button.clicked.connect(self.kill_ae_button_clicked)
|
||||||
|
self.remove_aegnux_button.clicked.connect(self.remove_aegnux_button_clicked)
|
||||||
|
self.toggle_logs_button.clicked.connect(self.toggle_logs)
|
||||||
|
self.plugins_button.clicked.connect(self.plugins_folder_clicked)
|
||||||
|
self.wineprefix_button.clicked.connect(self.wineprefix_folder_clicked)
|
||||||
|
|
||||||
|
self.install_thread = InstallationThread()
|
||||||
|
self.install_thread.log_signal.connect(self._log)
|
||||||
|
self.install_thread.progress_signal.connect(self.progress_bar.setValue)
|
||||||
|
self.install_thread.finished_signal.connect(self._finished)
|
||||||
|
|
||||||
|
self.run_ae_thread = RunAEThread()
|
||||||
|
self.run_ae_thread.log_signal.connect(self._log)
|
||||||
|
self.run_ae_thread.finished_signal.connect(self._finished)
|
||||||
|
|
||||||
|
self.kill_ae_thread = KillAEThread()
|
||||||
|
self.remove_ae_thread = RemoveAEThread()
|
||||||
|
self.remove_ae_thread.finished_signal.connect(self._finished)
|
||||||
|
|
||||||
|
self.init_installation()
|
||||||
|
|
||||||
|
def lock_ui(self, lock: bool = True):
|
||||||
|
self.install_button.setEnabled(not lock)
|
||||||
|
self.run_button.setEnabled(not lock)
|
||||||
|
self.remove_aegnux_button.setEnabled(not lock)
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def toggle_logs(self):
|
||||||
|
if self.logs_edit.isHidden():
|
||||||
|
self.logs_edit.show()
|
||||||
|
return
|
||||||
|
self.logs_edit.hide()
|
||||||
|
|
||||||
|
@Slot(bool)
|
||||||
|
def _finished(self, success: bool):
|
||||||
|
self.lock_ui(False)
|
||||||
|
self.progress_bar.hide()
|
||||||
|
self.init_installation()
|
||||||
|
|
||||||
|
@Slot(str)
|
||||||
|
def _log(self, message: str):
|
||||||
|
self.logs_edit.append(message + '\n')
|
||||||
|
|
||||||
@Slot()
|
@Slot()
|
||||||
def install_button_clicked(self):
|
def install_button_clicked(self):
|
||||||
pass
|
method = show_download_method_dialog(gls('installation_method_title'), gls('installation_method_text'))
|
||||||
|
|
||||||
|
if method == DownloadMethod.CANCEL:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.install_thread.set_download_method(method)
|
||||||
|
|
||||||
|
if method == DownloadMethod.OFFLINE:
|
||||||
|
filename, _ = QFileDialog.getOpenFileName(
|
||||||
|
self,
|
||||||
|
gls('offline_ae_zip_title'),
|
||||||
|
"",
|
||||||
|
"Zip Files (*.zip);;All Files (*)"
|
||||||
|
)
|
||||||
|
if filename == '':
|
||||||
|
return
|
||||||
|
|
||||||
|
self.install_thread.set_offline_filename(filename)
|
||||||
|
|
||||||
|
self.lock_ui()
|
||||||
|
self.progress_bar.show()
|
||||||
|
self.install_thread.start()
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def run_ae_button_clicked(self):
|
||||||
|
self.lock_ui()
|
||||||
|
self.run_ae_thread.start()
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def kill_ae_button_clicked(self):
|
||||||
|
self.kill_ae_thread.start()
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def remove_aegnux_button_clicked(self):
|
||||||
|
self.lock_ui()
|
||||||
|
self.remove_ae_thread.start()
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def plugins_folder_clicked(self):
|
||||||
|
os.system(f'xdg-open "{get_ae_plugins_dir()}"')
|
||||||
|
|
||||||
|
@Slot()
|
||||||
|
def wineprefix_folder_clicked(self):
|
||||||
|
os.system(f'xdg-open "{get_wineprefix_dir()}"')
|
||||||
263
src/processthread.py
Normal file
263
src/processthread.py
Normal file
@@ -0,0 +1,263 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
import requests
|
||||||
|
import zipfile
|
||||||
|
import tarfile
|
||||||
|
import subprocess
|
||||||
|
import select
|
||||||
|
import fcntl
|
||||||
|
from src.utils import format_size, get_wineprefix_dir, get_wine_bin_path_env
|
||||||
|
from src.config import DOWNLOAD_CHUNK_SIZE, LOG_THROTTLE_SECONDS
|
||||||
|
from PySide6.QtCore import QThread, Signal
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessThread(QThread):
|
||||||
|
log_signal = Signal(str)
|
||||||
|
progress_signal = Signal(int)
|
||||||
|
finished_signal = Signal(bool)
|
||||||
|
cancelled = Signal()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._is_cancelled = False
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self._is_cancelled = True
|
||||||
|
|
||||||
|
def download_file_to(self, url: str, filename: str):
|
||||||
|
r = requests.get(url, stream=True)
|
||||||
|
total = int(r.headers.get('content-length', 0))
|
||||||
|
|
||||||
|
downloaded = 0
|
||||||
|
start_time = time.time()
|
||||||
|
last_update_time = time.time() # throttling timer
|
||||||
|
|
||||||
|
with open(filename, 'wb') as f:
|
||||||
|
for data in r.iter_content(chunk_size=DOWNLOAD_CHUNK_SIZE):
|
||||||
|
if self._is_cancelled:
|
||||||
|
self.log_signal.emit(f'[DOWNLOAD] Cancelled by user. Deleting partial file: {filename}')
|
||||||
|
r.close()
|
||||||
|
os.remove(filename)
|
||||||
|
self.cancelled.emit()
|
||||||
|
self.finished_signal.emit(False)
|
||||||
|
return
|
||||||
|
|
||||||
|
f.write(data)
|
||||||
|
downloaded += len(data)
|
||||||
|
|
||||||
|
current_time = time.time()
|
||||||
|
if current_time - last_update_time >= LOG_THROTTLE_SECONDS:
|
||||||
|
if total > 0:
|
||||||
|
percent = int((downloaded / total) * 100)
|
||||||
|
else:
|
||||||
|
percent = 0
|
||||||
|
|
||||||
|
elapsed_time = current_time - start_time
|
||||||
|
speed = (downloaded / elapsed_time) if elapsed_time > 0 else 0
|
||||||
|
|
||||||
|
self.log_signal.emit(f'[DOWNLOADING] {filename} ({percent}%/{format_size(total)}), {format_size(speed)}/s')
|
||||||
|
last_update_time = current_time
|
||||||
|
|
||||||
|
if total > 0:
|
||||||
|
final_percent = 100
|
||||||
|
else:
|
||||||
|
final_percent = 0
|
||||||
|
|
||||||
|
self.log_signal.emit(f'[DOWNLOADED] {filename} (100%/{format_size(total)})')
|
||||||
|
|
||||||
|
|
||||||
|
def unpack_zip(self, zip_file_path: str, extract_to_path: str):
|
||||||
|
self.log_signal.emit(f'[EXTRACTING] Starting ZIP extraction: {zip_file_path}')
|
||||||
|
with zipfile.ZipFile(zip_file_path, 'r') as zip_ref:
|
||||||
|
members = [m for m in zip_ref.infolist() if not m.is_dir()]
|
||||||
|
total_files = len(members)
|
||||||
|
extracted_files = 0
|
||||||
|
last_update_time = time.time()
|
||||||
|
|
||||||
|
os.makedirs(extract_to_path, exist_ok=True)
|
||||||
|
|
||||||
|
for file_info in members:
|
||||||
|
if self._is_cancelled:
|
||||||
|
self.log_signal.emit('[EXTRACTING] ZIP extraction cancelled by user.')
|
||||||
|
self.cancelled.emit()
|
||||||
|
self.finished_signal.emit(False)
|
||||||
|
return
|
||||||
|
|
||||||
|
zip_ref.extract(file_info, extract_to_path)
|
||||||
|
extracted_files += 1
|
||||||
|
|
||||||
|
current_time = time.time()
|
||||||
|
# Throttling logic
|
||||||
|
if current_time - last_update_time >= LOG_THROTTLE_SECONDS or extracted_files == total_files:
|
||||||
|
if total_files > 0:
|
||||||
|
percent = int((extracted_files / total_files) * 100)
|
||||||
|
else:
|
||||||
|
percent = 0
|
||||||
|
|
||||||
|
self.log_signal.emit(f'[EXTRACTING] {zip_file_path}: {extracted_files}/{total_files} files ({percent}%)')
|
||||||
|
last_update_time = current_time
|
||||||
|
|
||||||
|
self.log_signal.emit(f'[EXTRACTED] ZIP finished extracting to {extract_to_path}')
|
||||||
|
|
||||||
|
|
||||||
|
def unpack_tar(self, tar_file_path: str, extract_to_path: str):
|
||||||
|
self.log_signal.emit(f'[EXTRACTING] Starting TAR extraction: {tar_file_path}')
|
||||||
|
with tarfile.open(tar_file_path, 'r') as tar_ref:
|
||||||
|
members = [m for m in tar_ref.getmembers() if m.isfile()]
|
||||||
|
total_files = len(members)
|
||||||
|
extracted_files = 0
|
||||||
|
last_update_time = time.time()
|
||||||
|
|
||||||
|
os.makedirs(extract_to_path, exist_ok=True)
|
||||||
|
|
||||||
|
for member in members:
|
||||||
|
if self._is_cancelled:
|
||||||
|
self.log_signal.emit('[EXTRACTING] TAR extraction cancelled by user.')
|
||||||
|
self.cancelled.emit()
|
||||||
|
self.finished_signal.emit(False)
|
||||||
|
return
|
||||||
|
|
||||||
|
tar_ref.extract(member, extract_to_path)
|
||||||
|
extracted_files += 1
|
||||||
|
|
||||||
|
current_time = time.time()
|
||||||
|
if total_files > 0 and (current_time - last_update_time >= LOG_THROTTLE_SECONDS or extracted_files == total_files):
|
||||||
|
percent = int((extracted_files / total_files) * 100)
|
||||||
|
self.log_signal.emit(f'[EXTRACTING] {tar_file_path}: {extracted_files}/{total_files} files ({percent}%)')
|
||||||
|
last_update_time = current_time
|
||||||
|
|
||||||
|
self.log_signal.emit(f'[EXTRACTED] TAR finished extracting to {extract_to_path}')
|
||||||
|
|
||||||
|
|
||||||
|
def _set_non_blocking(self, file):
|
||||||
|
if os.name == 'posix':
|
||||||
|
fd = file.fileno()
|
||||||
|
fl = fcntl.fcntl(fd, fcntl.F_GETFL)
|
||||||
|
fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
||||||
|
|
||||||
|
def run_command(self, command: list, cwd: str = None, in_prefix: bool = False):
|
||||||
|
self.log_signal.emit(f'[COMMAND] Running command: {" ".join(command)}')
|
||||||
|
self._is_cancelled = False
|
||||||
|
|
||||||
|
env = os.environ.copy()
|
||||||
|
if in_prefix:
|
||||||
|
env['WINEPREFIX'] = get_wineprefix_dir()
|
||||||
|
env['PATH'] = get_wine_bin_path_env(env.get('PATH', os.defpath))
|
||||||
|
|
||||||
|
try:
|
||||||
|
process = subprocess.Popen(
|
||||||
|
command,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
cwd=cwd,
|
||||||
|
env=env
|
||||||
|
)
|
||||||
|
except FileNotFoundError:
|
||||||
|
self.log_signal.emit(f'[ERROR] Command not found: {command[0]}')
|
||||||
|
self.finished_signal.emit(False)
|
||||||
|
return
|
||||||
|
|
||||||
|
self._set_non_blocking(process.stdout)
|
||||||
|
self._set_non_blocking(process.stderr)
|
||||||
|
|
||||||
|
stdout_buffer = b''
|
||||||
|
stderr_buffer = b''
|
||||||
|
|
||||||
|
pipes = {
|
||||||
|
process.stdout.fileno(): ('STDOUT', stdout_buffer),
|
||||||
|
process.stderr.fileno(): ('STDERR', stderr_buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
last_log_time = time.time()
|
||||||
|
|
||||||
|
while process.poll() is None or pipes:
|
||||||
|
if self._is_cancelled:
|
||||||
|
self.log_signal.emit('[COMMAND] Process cancelled by user. Terminating...')
|
||||||
|
process.terminate()
|
||||||
|
try:
|
||||||
|
process.wait(timeout=5)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
self.log_signal.emit('[COMMAND] Process did not terminate, killing...')
|
||||||
|
process.kill()
|
||||||
|
self.cancelled.emit()
|
||||||
|
self.finished_signal.emit(False)
|
||||||
|
return
|
||||||
|
|
||||||
|
if pipes:
|
||||||
|
rlist, _, _ = select.select(pipes.keys(), [], [], 0.1)
|
||||||
|
else:
|
||||||
|
rlist = []
|
||||||
|
|
||||||
|
current_time = time.time()
|
||||||
|
if rlist or current_time - last_log_time >= LOG_THROTTLE_SECONDS:
|
||||||
|
for fd in rlist:
|
||||||
|
stream_name, current_buffer_ref = pipes[fd]
|
||||||
|
pipe = process.stdout if stream_name == 'STDOUT' else process.stderr
|
||||||
|
|
||||||
|
try:
|
||||||
|
chunk = pipe.read(1024)
|
||||||
|
except BlockingIOError:
|
||||||
|
chunk = b''
|
||||||
|
|
||||||
|
if chunk:
|
||||||
|
current_buffer = current_buffer_ref + chunk
|
||||||
|
|
||||||
|
if stream_name == 'STDOUT':
|
||||||
|
stdout_buffer = current_buffer
|
||||||
|
pipes[fd] = (stream_name, stdout_buffer)
|
||||||
|
else:
|
||||||
|
stderr_buffer = current_buffer
|
||||||
|
pipes[fd] = (stream_name, stderr_buffer)
|
||||||
|
|
||||||
|
if not chunk and process.poll() is not None:
|
||||||
|
del pipes[fd]
|
||||||
|
break
|
||||||
|
|
||||||
|
if current_time - last_log_time >= LOG_THROTTLE_SECONDS:
|
||||||
|
if process.stdout.fileno() in pipes:
|
||||||
|
stream_name, current_buffer = pipes[process.stdout.fileno()]
|
||||||
|
lines = current_buffer.split(b'\n')
|
||||||
|
stdout_buffer = lines.pop()
|
||||||
|
pipes[process.stdout.fileno()] = (stream_name, stdout_buffer)
|
||||||
|
|
||||||
|
for line_bytes in lines:
|
||||||
|
line = line_bytes.decode('utf-8', errors='replace').strip()
|
||||||
|
if line:
|
||||||
|
self.log_signal.emit(f'[STDOUT] {line}')
|
||||||
|
|
||||||
|
if process.stderr.fileno() in pipes:
|
||||||
|
stream_name, current_buffer = pipes[process.stderr.fileno()]
|
||||||
|
lines = current_buffer.split(b'\n')
|
||||||
|
stderr_buffer = lines.pop()
|
||||||
|
pipes[process.stderr.fileno()] = (stream_name, stderr_buffer)
|
||||||
|
|
||||||
|
for line_bytes in lines:
|
||||||
|
line = line_bytes.decode('utf-8', errors='replace').strip()
|
||||||
|
if line:
|
||||||
|
self.log_signal.emit(f'[STDERR] {line}')
|
||||||
|
|
||||||
|
last_log_time = current_time
|
||||||
|
|
||||||
|
if process.poll() is not None and not pipes:
|
||||||
|
break
|
||||||
|
|
||||||
|
def flush_buffer(buffer, stream_name):
|
||||||
|
if buffer.strip():
|
||||||
|
try:
|
||||||
|
line = buffer.decode('utf-8', errors='replace').strip()
|
||||||
|
if line:
|
||||||
|
self.log_signal.emit(f'[{stream_name}] {line}')
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
flush_buffer(stdout_buffer, 'STDOUT')
|
||||||
|
flush_buffer(stderr_buffer, 'STDERR')
|
||||||
|
|
||||||
|
return_code = process.wait()
|
||||||
|
|
||||||
|
if return_code == 0:
|
||||||
|
self.log_signal.emit(f'[COMMAND] Command finished successfully. Return code: {return_code}')
|
||||||
|
else:
|
||||||
|
self.log_signal.emit(f'[COMMAND] Command failed. Return code: {return_code}')
|
||||||
|
|
||||||
|
return return_code
|
||||||
11
src/removeaethread.py
Normal file
11
src/removeaethread.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from src.processthread import ProcessThread
|
||||||
|
from src.utils import get_aegnux_installation_dir
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
class RemoveAEThread(ProcessThread):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
shutil.rmtree(get_aegnux_installation_dir(), True)
|
||||||
|
self.finished_signal.emit(True)
|
||||||
15
src/runaethread.py
Normal file
15
src/runaethread.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from src.processthread import ProcessThread
|
||||||
|
from src.utils import get_ae_install_dir
|
||||||
|
|
||||||
|
class RunAEThread(ProcessThread):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.run_command(
|
||||||
|
['wine', 'AfterFX.exe'],
|
||||||
|
cwd=get_ae_install_dir(),
|
||||||
|
in_prefix=True
|
||||||
|
)
|
||||||
|
|
||||||
|
self.finished_signal.emit(True)
|
||||||
6
src/types.py
Normal file
6
src/types.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class DownloadMethod(Enum):
|
||||||
|
ONLINE = 1
|
||||||
|
OFFLINE = 2
|
||||||
|
CANCEL = 3
|
||||||
140
src/utils.py
Normal file
140
src/utils.py
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import math
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
from src.types import DownloadMethod
|
||||||
|
from PySide6.QtWidgets import QMessageBox
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
def format_size(size_bytes):
|
||||||
|
if size_bytes == 0:
|
||||||
|
return "0 B"
|
||||||
|
|
||||||
|
size_name = ("B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
||||||
|
|
||||||
|
i = int(math.floor(math.log(size_bytes, 1024)))
|
||||||
|
p = math.pow(1024, i)
|
||||||
|
s = round(size_bytes / p, 2)
|
||||||
|
|
||||||
|
return f"{s} {size_name[i]}"
|
||||||
|
|
||||||
|
def is_nvidia_present():
|
||||||
|
if shutil.which('nvidia-smi'):
|
||||||
|
return True
|
||||||
|
|
||||||
|
if os.path.exists('/proc/driver/nvidia'):
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
process = subprocess.Popen(
|
||||||
|
['lspci'],
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
text=False
|
||||||
|
)
|
||||||
|
stdout, _ = process.communicate()
|
||||||
|
if 'NVIDIA' in stdout.decode('utf-8', errors='ignore').upper():
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
def show_download_method_dialog(title: str, message: str) -> DownloadMethod:
|
||||||
|
dialog = QMessageBox()
|
||||||
|
dialog.setWindowTitle(title)
|
||||||
|
dialog.setText(message)
|
||||||
|
|
||||||
|
download_btn = dialog.addButton("Download", QMessageBox.ButtonRole.AcceptRole)
|
||||||
|
choose_file_btn = dialog.addButton("Choose Local File", QMessageBox.ButtonRole.ActionRole)
|
||||||
|
cancel_btn = dialog.addButton("Cancel", QMessageBox.ButtonRole.RejectRole)
|
||||||
|
|
||||||
|
dialog.exec()
|
||||||
|
|
||||||
|
clicked_button = dialog.clickedButton()
|
||||||
|
|
||||||
|
if clicked_button == download_btn:
|
||||||
|
return DownloadMethod.ONLINE
|
||||||
|
|
||||||
|
if clicked_button == choose_file_btn:
|
||||||
|
return DownloadMethod.OFFLINE
|
||||||
|
|
||||||
|
return DownloadMethod.CANCEL
|
||||||
|
|
||||||
|
def get_aegnux_installation_dir():
|
||||||
|
data_home = os.getenv('XDG_DATA_HOME', os.path.expanduser('~/.local/share'))
|
||||||
|
aegnux_dir = Path(data_home).joinpath('aegnux')
|
||||||
|
|
||||||
|
if not os.path.exists(aegnux_dir):
|
||||||
|
os.makedirs(aegnux_dir)
|
||||||
|
|
||||||
|
return aegnux_dir
|
||||||
|
|
||||||
|
def get_ae_install_dir():
|
||||||
|
aegnux_dir = get_aegnux_installation_dir()
|
||||||
|
ae_dir = aegnux_dir.joinpath('AE')
|
||||||
|
|
||||||
|
if not os.path.exists(ae_dir):
|
||||||
|
os.makedirs(ae_dir)
|
||||||
|
|
||||||
|
return ae_dir
|
||||||
|
|
||||||
|
def get_ae_plugins_dir():
|
||||||
|
ae_dir = get_ae_install_dir()
|
||||||
|
return ae_dir.joinpath('Plug-ins')
|
||||||
|
|
||||||
|
def get_wineprefix_dir():
|
||||||
|
aegnux_dir = get_aegnux_installation_dir()
|
||||||
|
wineprefix_dir = aegnux_dir.joinpath('wineprefix')
|
||||||
|
|
||||||
|
if not os.path.exists(wineprefix_dir):
|
||||||
|
os.makedirs(wineprefix_dir)
|
||||||
|
|
||||||
|
return wineprefix_dir
|
||||||
|
|
||||||
|
def get_wine_runner_dir():
|
||||||
|
aegnux_dir = get_aegnux_installation_dir()
|
||||||
|
runner_dir = aegnux_dir.joinpath('runner')
|
||||||
|
|
||||||
|
if not os.path.exists(runner_dir):
|
||||||
|
os.makedirs(runner_dir)
|
||||||
|
|
||||||
|
return runner_dir
|
||||||
|
|
||||||
|
def get_wine_bin():
|
||||||
|
runner_dir = get_wine_runner_dir()
|
||||||
|
return runner_dir.joinpath('bin/wine')
|
||||||
|
|
||||||
|
def get_wineserver_bin():
|
||||||
|
runner_dir = get_wine_runner_dir()
|
||||||
|
return runner_dir.joinpath('bin/wineserver')
|
||||||
|
|
||||||
|
def get_winetricks_bin():
|
||||||
|
runner_dir = get_wine_runner_dir()
|
||||||
|
return runner_dir.joinpath('bin/winetricks')
|
||||||
|
|
||||||
|
def get_cabextract_bin():
|
||||||
|
runner_dir = get_wine_runner_dir()
|
||||||
|
return runner_dir.joinpath('bin/cabextract')
|
||||||
|
|
||||||
|
def get_vcr_dir_path():
|
||||||
|
runner_dir = get_wine_runner_dir()
|
||||||
|
return runner_dir.joinpath('vcr')
|
||||||
|
|
||||||
|
def get_msxml_dir_path():
|
||||||
|
runner_dir = get_wine_runner_dir()
|
||||||
|
return runner_dir.joinpath('msxml')
|
||||||
|
|
||||||
|
def get_aegnux_installed_flag_path():
|
||||||
|
hades = get_aegnux_installation_dir()
|
||||||
|
return hades.joinpath('installed')
|
||||||
|
|
||||||
|
def check_aegnux_installed():
|
||||||
|
return os.path.exists(get_aegnux_installed_flag_path())
|
||||||
|
|
||||||
|
def mark_aegnux_as_installed():
|
||||||
|
with open(get_aegnux_installed_flag_path(), 'w') as f:
|
||||||
|
f.write('have fun :)')
|
||||||
|
|
||||||
|
def get_wine_bin_path_env(old_path: str | None):
|
||||||
|
old_path = old_path if old_path is not None else os.getenv('PATH')
|
||||||
|
return f'{get_wine_runner_dir().as_posix()}/bin:{old_path}'
|
||||||
@@ -9,13 +9,16 @@
|
|||||||
margin-right: 3em;
|
margin-right: 3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#install_button {
|
#install_button,
|
||||||
margin-top: 30px;
|
#run_ae,
|
||||||
margin-bottom: 30px;
|
#toggle_logs_button,
|
||||||
padding-top: 20px;
|
#remove_aegnux_button,
|
||||||
padding-bottom: 20px;
|
#kill_ae,
|
||||||
margin-left: 3em;
|
#plugins_button,
|
||||||
margin-right: 3em;
|
#wineprefix_button {
|
||||||
|
width: 100%;
|
||||||
|
padding-top: 15px;
|
||||||
|
padding-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#footer_label {
|
#footer_label {
|
||||||
|
|||||||
36
styles/wine_dark_theme.reg
Normal file
36
styles/wine_dark_theme.reg
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
Windows Registry Editor Version 5.00
|
||||||
|
|
||||||
|
[HKEY_CURRENT_USER\Control Panel\Colors]
|
||||||
|
"ActiveBorder"="49 54 58"
|
||||||
|
"ActiveTitle"="49 54 58"
|
||||||
|
"AppWorkSpace"="60 64 72"
|
||||||
|
"Background"="49 54 58"
|
||||||
|
"ButtonAlternativeFace"="200 0 0"
|
||||||
|
"ButtonDkShadow"="154 154 154"
|
||||||
|
"ButtonFace"="49 54 58"
|
||||||
|
"ButtonHilight"="119 126 140"
|
||||||
|
"ButtonLight"="60 64 72"
|
||||||
|
"ButtonShadow"="60 64 72"
|
||||||
|
"ButtonText"="219 220 222"
|
||||||
|
"GradientActiveTitle"="49 54 58"
|
||||||
|
"GradientInactiveTitle"="49 54 58"
|
||||||
|
"GrayText"="155 155 155"
|
||||||
|
"Hilight"="119 126 140"
|
||||||
|
"HilightText"="255 255 255"
|
||||||
|
"InactiveBorder"="49 54 58"
|
||||||
|
"InactiveTitle"="49 54 58"
|
||||||
|
"InactiveTitleText"="219 220 222"
|
||||||
|
"InfoText"="159 167 180"
|
||||||
|
"InfoWindow"="49 54 58"
|
||||||
|
"Menu"="49 54 58"
|
||||||
|
"MenuBar"="49 54 58"
|
||||||
|
"MenuHilight"="119 126 140"
|
||||||
|
"MenuText"="219 220 222"
|
||||||
|
"Scrollbar"="73 78 88"
|
||||||
|
"TitleText"="219 220 222"
|
||||||
|
"Window"="35 38 41"
|
||||||
|
"WindowFrame"="49 54 58"
|
||||||
|
"WindowText"="219 220 222"
|
||||||
|
|
||||||
|
[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\ThemeManager]
|
||||||
|
"ThemeActive"=0
|
||||||
@@ -3,5 +3,14 @@ STRINGS = {
|
|||||||
'welcome_to_aegnux': 'Welcome to Aegnux!',
|
'welcome_to_aegnux': 'Welcome to Aegnux!',
|
||||||
'subtitle_text': 'A simpler way to get After Effects running on GNU/Linux',
|
'subtitle_text': 'A simpler way to get After Effects running on GNU/Linux',
|
||||||
'install': 'Install',
|
'install': 'Install',
|
||||||
'footer_text': 'Made with 💙 by Relative'
|
'footer_text': 'Made with 💙 by Relative',
|
||||||
|
'installation_method_title': 'Installation Method',
|
||||||
|
'installation_method_text': 'How would you like to install Aegnux?',
|
||||||
|
'offline_ae_zip_title': 'Select AE Zip File',
|
||||||
|
'run_ae': 'Run AE',
|
||||||
|
'kill_ae': 'Kill AE',
|
||||||
|
'toggle_logs': 'Toggle logs',
|
||||||
|
'remove_aegnux': 'Remove Aegnux',
|
||||||
|
'plugins': 'Plugins',
|
||||||
|
'wineprefix': 'Wine prefix'
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
from PySide6.QtWidgets import (
|
from PySide6.QtWidgets import (
|
||||||
QVBoxLayout, QWidget,
|
QVBoxLayout, QWidget, QHBoxLayout,
|
||||||
QLabel, QMainWindow, QPushButton,
|
QLabel, QMainWindow, QPushButton,
|
||||||
QSpacerItem, QSizePolicy
|
QSpacerItem, QSizePolicy, QTextEdit, QProgressBar
|
||||||
)
|
)
|
||||||
from PySide6.QtCore import Qt, QSize
|
from PySide6.QtCore import Qt, QSize
|
||||||
from PySide6.QtGui import QIcon, QPixmap
|
from PySide6.QtGui import QIcon, QPixmap
|
||||||
from translations import gls
|
from translations import gls
|
||||||
from src.config import AE_ICON_PATH, STYLES_PATH
|
from src.config import AE_ICON_PATH, STYLES_PATH
|
||||||
|
from src.utils import check_aegnux_installed
|
||||||
|
|
||||||
class MainWindowUI(QMainWindow):
|
class MainWindowUI(QMainWindow):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -24,6 +25,29 @@ class MainWindowUI(QMainWindow):
|
|||||||
QSpacerItem(1, 2, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding)
|
QSpacerItem(1, 2, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Expanding)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def add_fixed_vertical_sizer(self, height: int):
|
||||||
|
self.root_layout.addItem(
|
||||||
|
QSpacerItem(1, height, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
|
||||||
|
)
|
||||||
|
|
||||||
|
def init_installation(self):
|
||||||
|
if check_aegnux_installed():
|
||||||
|
self.install_button.hide()
|
||||||
|
self.run_button.show()
|
||||||
|
self.kill_button.show()
|
||||||
|
self.remove_aegnux_button.show()
|
||||||
|
|
||||||
|
self.plugins_button.show()
|
||||||
|
self.wineprefix_button.show()
|
||||||
|
else:
|
||||||
|
self.install_button.show()
|
||||||
|
self.run_button.hide()
|
||||||
|
self.kill_button.hide()
|
||||||
|
self.remove_aegnux_button.hide()
|
||||||
|
|
||||||
|
self.plugins_button.hide()
|
||||||
|
self.wineprefix_button.hide()
|
||||||
|
|
||||||
def _construct_ui(self):
|
def _construct_ui(self):
|
||||||
central_widget = QWidget()
|
central_widget = QWidget()
|
||||||
self.setCentralWidget(central_widget)
|
self.setCentralWidget(central_widget)
|
||||||
@@ -53,14 +77,81 @@ class MainWindowUI(QMainWindow):
|
|||||||
|
|
||||||
self.root_layout.addWidget(subtitle_label)
|
self.root_layout.addWidget(subtitle_label)
|
||||||
|
|
||||||
|
self.add_fixed_vertical_sizer(30)
|
||||||
|
|
||||||
|
action_row = QHBoxLayout()
|
||||||
|
action_col = QVBoxLayout()
|
||||||
|
|
||||||
self.install_button = QPushButton(gls('install'))
|
self.install_button = QPushButton(gls('install'))
|
||||||
self.install_button.setIcon(QIcon.fromTheme('install-symbolic'))
|
self.install_button.setIcon(QIcon.fromTheme('install-symbolic'))
|
||||||
self.install_button.setIconSize(QSize(35, 20))
|
self.install_button.setIconSize(QSize(25, 15))
|
||||||
self.install_button.setObjectName('install_button')
|
self.install_button.setObjectName('install_button')
|
||||||
self.root_layout.addWidget(self.install_button)
|
self.install_button.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Preferred)
|
||||||
|
action_col.addWidget(self.install_button)
|
||||||
|
|
||||||
|
|
||||||
|
self.run_button = QPushButton(gls('run_ae'))
|
||||||
|
self.run_button.setIcon(QIcon.fromTheme('media-playback-start'))
|
||||||
|
self.run_button.setIconSize(QSize(25, 15))
|
||||||
|
self.run_button.setObjectName('run_ae')
|
||||||
|
action_col.addWidget(self.run_button)
|
||||||
|
self.run_button.hide()
|
||||||
|
|
||||||
|
folders_row = QHBoxLayout()
|
||||||
|
self.plugins_button = QPushButton(gls('plugins'))
|
||||||
|
self.plugins_button.setIcon(QIcon.fromTheme('document-open-folder'))
|
||||||
|
self.plugins_button.setIconSize(QSize(25, 15))
|
||||||
|
self.plugins_button.setObjectName('plugins_button')
|
||||||
|
|
||||||
|
self.wineprefix_button = QPushButton(gls('wineprefix'))
|
||||||
|
self.wineprefix_button.setIcon(QIcon.fromTheme('document-open-folder'))
|
||||||
|
self.wineprefix_button.setIconSize(QSize(25, 15))
|
||||||
|
self.wineprefix_button.setObjectName('wineprefix_button')
|
||||||
|
|
||||||
|
self.toggle_logs_button = QPushButton(gls('toggle_logs'))
|
||||||
|
self.toggle_logs_button.setIcon(QIcon.fromTheme('view-list-text'))
|
||||||
|
self.toggle_logs_button.setIconSize(QSize(25, 15))
|
||||||
|
self.toggle_logs_button.setObjectName('toggle_logs_button')
|
||||||
|
action_col.addWidget(self.toggle_logs_button)
|
||||||
|
|
||||||
|
folders_row.addWidget(self.plugins_button)
|
||||||
|
folders_row.addWidget(self.wineprefix_button)
|
||||||
|
|
||||||
|
action_col.addLayout(folders_row)
|
||||||
|
|
||||||
|
destruction_row = QHBoxLayout()
|
||||||
|
|
||||||
|
self.kill_button = QPushButton(gls('kill_ae'))
|
||||||
|
self.kill_button.setObjectName('kill_ae')
|
||||||
|
destruction_row.addWidget(self.kill_button)
|
||||||
|
self.kill_button.hide()
|
||||||
|
|
||||||
|
|
||||||
|
self.remove_aegnux_button = QPushButton(gls('remove_aegnux'))
|
||||||
|
self.remove_aegnux_button.setObjectName('remove_aegnux_button')
|
||||||
|
destruction_row.addWidget(self.remove_aegnux_button)
|
||||||
|
self.remove_aegnux_button.hide()
|
||||||
|
|
||||||
|
action_col.addLayout(destruction_row)
|
||||||
|
|
||||||
|
|
||||||
|
self.logs_edit = QTextEdit()
|
||||||
|
self.logs_edit.setObjectName('logs_edit')
|
||||||
|
self.logs_edit.setFixedHeight(140)
|
||||||
|
self.logs_edit.setReadOnly(True)
|
||||||
|
self.logs_edit.hide()
|
||||||
|
action_col.addWidget(self.logs_edit)
|
||||||
|
|
||||||
|
self.progress_bar = QProgressBar(minimum=0, maximum=100, value=0)
|
||||||
|
self.progress_bar.hide()
|
||||||
|
action_col.addWidget(self.progress_bar)
|
||||||
|
|
||||||
|
action_row.addItem(QSpacerItem(50, 1, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed))
|
||||||
|
action_row.addLayout(action_col)
|
||||||
|
action_row.addItem(QSpacerItem(50, 1, QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed))
|
||||||
|
|
||||||
|
self.root_layout.addLayout(action_row)
|
||||||
|
|
||||||
self.add_expanding_vertical_sizer()
|
self.add_expanding_vertical_sizer()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user