import os import subprocess from PyQt6.QtWidgets import QApplication, QPushButton, QLabel from PyQt6.QtGui import QIcon from PyQt6.QtCore import QSize, QTimer from PyQt6 import QtCore from gui.v2.ui.pages.Page import Page from gui.v2.workers.worker_thread import WorkerThread class InstallSystemPackage(Page): def __init__(self, page_stack, main_window=None, parent=None): super().__init__("InstallPackage", page_stack, main_window, parent) self.btn_path = main_window.btn_path self.update_status = main_window self.package_name = "" self.install_command = "" self.manual_install_command = "" self.is_sync = False self.ui_elements = [] self.permanent_elements = [] self.current_distro = "debian" self.distro_buttons = {} self._setup_permanent_ui() self._setup_distro_buttons() self.auto_install_package_commands = { 'all': { 'debian': 'pkexec apt install -y bubblewrap iproute2 microsocks proxychains4 ratpoison tor wireguard xserver-xephyr', 'arch': 'pkexec pacman -S bubblewrap iproute2 microsocks proxychains-ng tor wireguard-tools xorg-server-xephyr', 'fedora': 'pkexec dnf -y install bubblewrap iproute proxychains4 ratpoison tor wireguard-tools xorg-x11-server-Xephyr' }, 'bwrap': { 'debian': 'pkexec apt -y install bubblewrap', 'arch': 'pkexec pacman -S bubblewrap', 'fedora': 'pkexec dnf -y install bubblewrap' }, 'ip': { 'debian': 'pkexec apt -y install iproute2', 'arch': 'pkexec pacman -S iproute2', 'fedora': 'pkexec dnf -y install iproute' }, 'microsocks': { 'debian': 'pkexec apt -y install microsocks', 'arch': 'pkexec pacman -S microsocks', 'fedora': 'zenity --warning --text="An essential dependency (microsocks) is not included in your distribution\'s default repository. Please build it from source or contact support."' }, 'proxychains4': { 'debian': 'pkexec apt -y install proxychains4', 'arch': 'pkexec pacman -S proxychains-ng', 'fedora': 'pkexec dnf -y install proxychains4' }, 'ratpoison': { 'debian': 'pkexec apt -y install ratpoison', 'arch': 'zenity --warning --text="An essential dependency (ratpoison) is not included in your distribution\'s default repository. Please build it from source or contact support."', 'fedora': 'pkexec dnf -y install ratpoison' }, 'tor': { 'debian': 'pkexec apt -y install tor', 'arch': 'pkexec pacman -S tor', 'fedora': 'pkexec dnf -y install tor' }, 'wg-quick': { 'debian': 'pkexec apt -y install wireguard', 'arch': 'pkexec pacman -S wireguard-tools', 'fedora': 'pkexec dnf -y install wireguard-tools' }, 'Xephyr': { 'debian': 'pkexec apt -y install xserver-xephyr', 'arch': 'pkexec pacman -S xorg-server-xephyr', 'fedora': 'pkexec dnf -y install xorg-x11-server-Xephyr' } } self.manual_install_package_commands = { 'all': { 'debian': 'sudo apt install -y bubblewrap iproute2 microsocks proxychains4 ratpoison tor wireguard xserver-xephyr', 'arch': 'sudo pacman -S bubblewrap iproute2 microsocks proxychains-ng tor wireguard-tools xorg-server-xephyr', 'fedora': 'sudo dnf -y install bubblewrap iproute proxychains4 ratpoison tor wireguard-tools xorg-x11-server-Xephyr' }, 'bwrap': { 'debian': 'sudo apt -y install bubblewrap', 'arch': 'sudo pacman -S bubblewrap', 'fedora': 'sudo dnf -y install bubblewrap' }, 'ip': { 'debian': 'sudo apt -y install iproute2', 'arch': 'sudo pacman -S iproute2', 'fedora': 'sudo dnf -y install iproute' }, 'microsocks': { 'debian': 'sudo apt -y install microsocks', 'arch': 'sudo pacman -S microsocks', 'fedora': '# Package not available.' }, 'proxychains4': { 'debian': 'sudo apt -y install proxychains4', 'arch': 'sudo pacman -S proxychains-ng', 'fedora': 'sudo dnf -y install proxychains4' }, 'ratpoison': { 'debian': 'sudo apt -y install ratpoison', 'arch': '# Package not available.', 'fedora': 'sudo dnf -y install ratpoison' }, 'tor': { 'debian': 'sudo apt -y install tor', 'arch': 'sudo pacman -S tor', 'fedora': 'sudo dnf -y install tor' }, 'wg-quick': { 'debian': 'sudo apt -y install wireguard', 'arch': 'sudo pacman -S wireguard-tools', 'fedora': 'sudo dnf -y install wireguard-tools' }, 'Xephyr': { 'debian': 'sudo apt -y install xserver-xephyr', 'arch': 'sudo pacman -S xorg-server-xephyr', 'fedora': 'sudo dnf -y install xorg-x11-server-Xephyr' } } def _setup_permanent_ui(self): self.title.setGeometry(20, 50, 300, 60) self.title.setText("Package Installation") self.title.setStyleSheet("font-size: 22px; font-weight: bold;") self.permanent_elements.append(self.title) self.display.setGeometry(QtCore.QRect(100, 160, 580, 435)) self.permanent_elements.append(self.display) def _setup_distro_buttons(self): distros = ["debian", "arch", "fedora"] for i, distro in enumerate(distros): btn = QPushButton(self) btn.setGeometry(360 + i*140, 40, 120, 49) btn.setIcon(QIcon(os.path.join(self.btn_path, f"{distro}.png"))) btn.setIconSize(QSize(120, 49)) btn.setCheckable(True) btn.clicked.connect( lambda checked, d=distro: self.switch_distro(d)) self.distro_buttons[distro] = btn self.permanent_elements.append(btn) self.distro_buttons[self.current_distro].setChecked(True) def switch_distro(self, distro): self.current_distro = distro for d, btn in self.distro_buttons.items(): btn.setChecked(d == distro) if self.package_name: self.update_command() self.setup_ui() def update_command(self): if self.package_name in self.auto_install_package_commands: if self.is_sync: self.install_command = self.auto_install_package_commands[ 'tor_sync'][self.current_distro] self.manual_install_command = self.manual_install_package_commands[ 'tor_sync'][self.current_distro] else: self.install_command = self.auto_install_package_commands[ self.package_name][self.current_distro] self.manual_install_command = self.manual_install_package_commands[ self.package_name][self.current_distro] else: self.install_command = "" self.manual_install_command = "" def configure(self, package_name, distro, is_sync=False): self.package_name = package_name self.current_distro = distro self.is_sync = is_sync self.update_command() self.setup_ui() return self def setup_ui(self): self.button_back.setVisible(False) self.button_go.setVisible(True) if not self.package_name: return self.clear_ui() self._setup_auto_install() self._setup_manual_install() self._setup_status_and_nav() for element in self.ui_elements: element.setParent(self) element.setVisible(True) element.raise_() def clear_ui(self): for element in self.ui_elements: element.setParent(None) element.deleteLater() self.ui_elements.clear() self.command_box = None self.status_label = None self.install_button = None self.check_button = None def _setup_auto_install(self): auto_install_label = QLabel( f"Automatic Installation for {self.current_distro}", self) auto_install_label.setGeometry(80, 130, 580, 30) auto_install_label.setStyleSheet("font-size: 18px; font-weight: bold;") auto_install_label.setVisible(True) self.ui_elements.append(auto_install_label) auto_install_desc = QLabel( "Click the button below to automatically install the missing packages:", self) auto_install_desc.setStyleSheet("font-size: 14px; font-weight: bold;") auto_install_desc.setGeometry(80, 170, 650, 30) auto_install_desc.setVisible(True) self.ui_elements.append(auto_install_desc) self.install_button = QPushButton(self) self.install_button.setGeometry(135, 210, 100, 40) self.install_button.setIcon( QIcon(os.path.join(self.btn_path, "install.png"))) self.install_button.setIconSize(self.install_button.size()) self.install_button.clicked.connect(self.install_package) self.install_button.setVisible(True) self.ui_elements.append(self.install_button) if self.current_distro == 'arch': auto_install_label.setGeometry(50, 110, 580, 30) auto_install_desc.setGeometry(50, 140, 650, 30) self.install_button.setGeometry(150, 185, 100, 40) self.check_button = QPushButton(self) self.check_button.setGeometry(270, 185, 100, 40) self.check_button.setIcon( QIcon(os.path.join(self.btn_path, "check.png"))) self.check_button.setIconSize(self.check_button.size()) self.check_button.clicked.connect(self.check_package) self.check_button.setVisible(True) self.ui_elements.append(self.check_button) def _setup_manual_install(self): if self.current_distro != 'arch': manual_install_label = QLabel( f"Manual Installation for {self.current_distro}", self) manual_install_label.setGeometry(80, 290, 650, 30) manual_install_label.setStyleSheet( "font-size: 18px; font-weight: bold;") manual_install_label.setVisible(True) self.ui_elements.append(manual_install_label) manual_install_desc = QLabel( "Run this command in your terminal:", self) manual_install_desc.setStyleSheet( "font-size: 14px; font-weight: bold;") manual_install_desc.setGeometry(80, 330, 380, 30) manual_install_desc.setVisible(True) self.ui_elements.append(manual_install_desc) self.command_box = QLabel(self.manual_install_command, self) self.command_box.setGeometry(80, 370, 380, 40) if self.current_distro == 'arch': self.command_box.setGeometry(50, 280, 380, 40) metrics = self.command_box.fontMetrics() text_width = metrics.horizontalAdvance( self.manual_install_command) + 100 available_width = self.command_box.width() - 20 font_size = 18 MIN_FONT_SIZE = 10 while text_width > available_width: if font_size <= MIN_FONT_SIZE: break font_size -= 1 text_width -= 10 self.command_box.setStyleSheet(f""" background-color: #2b2b2b; color: #ffffff; padding: 10px; border-radius: 5px; font-family: monospace; font-size: {font_size}px; """) self.command_box.mousePressEvent = lambda _: self.copy_command() self.command_box.setVisible(True) self.ui_elements.append(self.command_box) if self.current_distro == 'arch': pacman_label = QLabel("Pacman Installation:", self) pacman_label.setGeometry(50, 240, 650, 30) pacman_label.setStyleSheet( "font-size: 18px; font-weight: bold; color: #00ffff;") pacman_label.setVisible(True) self.ui_elements.append(pacman_label) aur_label = QLabel("AUR / Yay Installation:", self) aur_label.setGeometry(50, 350, 300, 30) aur_label.setStyleSheet( "font-size: 18px; font-weight: bold; color: #00ffff;") self.ui_elements.append(aur_label) self.yay_command_box = QLabel("sudo yay -S ratpoison", self) self.yay_command_box.setGeometry(50, 390, 300, 40) self._setup_command_box_style(self.yay_command_box) self.yay_command_box.mousePressEvent = lambda _: self.copy_command( "sudo yay -S ratpoison", self.yay_command_box) self.ui_elements.append(self.yay_command_box) aur_command = "git clone https://aur.archlinux.org/ratpoison.git\ncd ratpoison\nmakepkg -si --skippgpcheck" self.aur_command_box = QLabel(aur_command, self) self.aur_command_box.setGeometry(370, 390, 380, 60) self._setup_command_box_style(self.aur_command_box) self.aur_command_box.setWordWrap(True) self.aur_command_box.mousePressEvent = lambda _: self.copy_command( aur_command, self.aur_command_box) self.ui_elements.append(self.aur_command_box) else: self.command_box = QLabel(self.manual_install_command, self) self.command_box.setGeometry(80, 370, 380, 40) metrics = self.command_box.fontMetrics() text_width = metrics.horizontalAdvance( self.manual_install_command) + 100 available_width = self.command_box.width() - 20 font_size = 18 MIN_FONT_SIZE = 10 while text_width > available_width: if font_size <= MIN_FONT_SIZE: break font_size -= 1 text_width -= 10 self.command_box.setStyleSheet(f""" background-color: #2b2b2b; color: #ffffff; padding: 10px; border-radius: 5px; font-family: monospace; font-size: {font_size}px; """) self.command_box.mousePressEvent = lambda _: self.copy_command() self.command_box.setVisible(True) self.ui_elements.append(self.command_box) def _setup_command_box_style(self, command_box): metrics = command_box.fontMetrics() text_width = metrics.horizontalAdvance(command_box.text()) + 100 available_width = command_box.width() - 20 font_size = 18 MIN_FONT_SIZE = 10 while text_width > available_width: if font_size <= MIN_FONT_SIZE: break font_size -= 1 text_width -= 10 command_box.setStyleSheet(f""" background-color: #2b2b2b; color: #ffffff; padding: 10px; border-radius: 5px; font-family: monospace; font-size: {font_size}px; """) command_box.setVisible(True) def copy_yay_command(self): clipboard = QApplication.clipboard() clipboard.setText("sudo yay -S ratpoison") original_style = self.yay_command_box.styleSheet() self.yay_command_box.setStyleSheet( original_style + "background-color: #27ae60;") QTimer.singleShot( 200, lambda: self.yay_command_box.setStyleSheet(original_style)) self.update_status.update_status("Yay command copied to clipboard") def _setup_status_and_nav(self): if self.current_distro != 'arch': self.check_button = QPushButton(self) self.check_button.setGeometry(130, 430, 100, 40) self.check_button.setIcon( QIcon(os.path.join(self.btn_path, "check.png"))) self.check_button.setIconSize(self.check_button.size()) self.check_button.clicked.connect(self.check_package) self.ui_elements.append(self.check_button) self.button_go.clicked.connect(self.go_next) self.button_back.clicked.connect(self.go_next) self.status_label = QLabel("", self) self.status_label.setGeometry(300, 430, 250, 40) if self.current_distro == 'arch': self.status_label.setGeometry(450, 185, 250, 40) self.ui_elements.append(self.status_label) def copy_command(self, command=None, command_box=None): if command_box is None: command_box = self.command_box clipboard = QApplication.clipboard() if command: clipboard.setText(command) else: clipboard.setText(self.manual_install_command) original_style = command_box.styleSheet() command_box.setStyleSheet( original_style + "background-color: #27ae60;") QTimer.singleShot( 200, lambda: command_box.setStyleSheet(original_style)) self.update_status.update_status("Command copied to clipboard") def install_package(self): if not self.install_command: self.update_status.update_status( f"Installation not supported for {self.current_distro}") return if subprocess.getstatusoutput('pkexec --help')[0] == 127: self.update_status.update_status("polkit toolkit is not installed") self.install_button.setDisabled(True) return self.worker_thread = WorkerThread('INSTALL_PACKAGE', package_command=self.install_command, package_name=self.package_name) self.worker_thread.text_output.connect( self.update_status.update_status) self.worker_thread.finished.connect(self.installation_complete) self.worker_thread.start() def installation_complete(self, success): if success: self.check_package() def check_package(self): try: if self.package_name.lower() == 'all': packages = ['bwrap', 'ip', 'microsocks', 'proxychains4', 'ratpoison', 'tor', 'Xephyr', 'wg'] for package in packages: if subprocess.getstatusoutput(f'{package} --help')[0] == 127: self.status_label.setText( f"{package} is not installed") self.status_label.setStyleSheet("color: red;") return self.status_label.setText("All packages installed!") self.status_label.setStyleSheet("color: green;") elif self.package_name.lower() == 'wireguard': self.install_name = 'wg' elif self.package_name.lower() == 'bubblewrap': self.install_name = 'bwrap' elif self.package_name.lower() == 'xserver-xephyr': self.install_name = 'Xephyr' else: self.install_name = self.package_name if subprocess.getstatusoutput(f'{self.install_name.lower()} --help')[0] == 127: self.status_label.setText( f"{self.package_name} is not installed") self.status_label.setStyleSheet("color: red;") else: self.status_label.setText( f"{self.package_name} is installed!") self.status_label.setStyleSheet("color: green;") except Exception: self.status_label.setText("Check failed") self.status_label.setStyleSheet("color: red;") def go_next(self): self.custom_window.navigator.navigate("menu")