first version

This commit is contained in:
Andrew
2025-10-29 09:09:36 +03:00
parent 5cac29f4bd
commit 26495d17d2
16 changed files with 20501 additions and 14 deletions

BIN
assets/msxml3.zip Normal file

Binary file not shown.

BIN
bin/cabextract Executable file

Binary file not shown.

19627
bin/winetricks Executable file

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,25 @@
import os
LOG_THROTTLE_SECONDS=0.1
DESKTOP_FILE_NAME='com.relative.Aegnux'
BASE_DIR = os.getcwd()
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
View 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
View 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)

View File

@@ -1,6 +1,14 @@
import os
from ui.mainwindow import MainWindowUI
from translations import gls
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):
@@ -9,7 +17,93 @@ class MainWindow(MainWindowUI):
self.setWindowTitle(gls('welcome_win_title'))
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()
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
View 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
View 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
View 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
View File

@@ -0,0 +1,6 @@
from enum import Enum
class DownloadMethod(Enum):
ONLINE = 1
OFFLINE = 2
CANCEL = 3

140
src/utils.py Normal file
View 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}'

View File

@@ -9,13 +9,16 @@
margin-right: 3em;
}
#install_button {
margin-top: 30px;
margin-bottom: 30px;
padding-top: 20px;
padding-bottom: 20px;
margin-left: 3em;
margin-right: 3em;
#install_button,
#run_ae,
#toggle_logs_button,
#remove_aegnux_button,
#kill_ae,
#plugins_button,
#wineprefix_button {
width: 100%;
padding-top: 15px;
padding-bottom: 15px;
}
#footer_label {

View 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

View File

@@ -3,5 +3,14 @@ STRINGS = {
'welcome_to_aegnux': 'Welcome to Aegnux!',
'subtitle_text': 'A simpler way to get After Effects running on GNU/Linux',
'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'
}

View File

@@ -1,12 +1,13 @@
from PySide6.QtWidgets import (
QVBoxLayout, QWidget,
QVBoxLayout, QWidget, QHBoxLayout,
QLabel, QMainWindow, QPushButton,
QSpacerItem, QSizePolicy
QSpacerItem, QSizePolicy, QTextEdit, QProgressBar
)
from PySide6.QtCore import Qt, QSize
from PySide6.QtGui import QIcon, QPixmap
from translations import gls
from src.config import AE_ICON_PATH, STYLES_PATH
from src.utils import check_aegnux_installed
class MainWindowUI(QMainWindow):
def __init__(self):
@@ -24,6 +25,29 @@ class MainWindowUI(QMainWindow):
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):
central_widget = QWidget()
self.setCentralWidget(central_widget)
@@ -53,14 +77,81 @@ class MainWindowUI(QMainWindow):
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.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.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()