Applied patch files
445
gui/__main__.py
|
@ -1,6 +1,6 @@
|
|||
from PyQt6.QtWidgets import QComboBox,QButtonGroup, QLineEdit,QMainWindow, QLabel, QWidget, QVBoxLayout, QStackedWidget, QApplication, QPushButton, QTextEdit, QFrame, QHBoxLayout, QVBoxLayout, QScrollArea, QSystemTrayIcon, QMessageBox, QGridLayout, QCheckBox, QStackedLayout, QGroupBox, QDialog
|
||||
from PyQt6.QtGui import QIcon, QPixmap,QIcon, QPixmap, QTransform, QPainter, QColor, QFont, QFontDatabase
|
||||
from PyQt6 import QtGui
|
||||
from PyQt6 import QtGui
|
||||
from PyQt6 import QtCore
|
||||
from PyQt6.QtCore import Qt,QSize,QThread,pyqtSignal, QTimer, QPointF, QRect, QMutex, QMutexLocker, QObject
|
||||
import os
|
||||
|
@ -9,7 +9,7 @@ import functools
|
|||
import threading
|
||||
import logging
|
||||
import traceback
|
||||
import random
|
||||
import random
|
||||
import subprocess
|
||||
from typing import Union
|
||||
from core.Errors import UnknownConnectionTypeError, CommandNotFoundError, MissingSubscriptionError, InvalidSubscriptionError, ProfileActivationError, UnsupportedApplicationVersionError, FileIntegrityError, ProfileModificationError, ProfileStateConflictError
|
||||
|
@ -35,6 +35,7 @@ from datetime import datetime, timezone, timedelta
|
|||
from core.Constants import Constants
|
||||
import atexit
|
||||
import json
|
||||
import shlex
|
||||
|
||||
|
||||
|
||||
|
@ -119,7 +120,7 @@ class WorkerThread(QThread):
|
|||
self.text_output.emit("Checking for updates...")
|
||||
ClientController.sync(client_observer=client_observer, connection_observer=connection_observer)
|
||||
update_available = ClientController.can_be_updated()
|
||||
if update_available:
|
||||
if update_available:
|
||||
self.text_output.emit("An update is available. Downloading...")
|
||||
self.finished.emit(True)
|
||||
else:
|
||||
|
@ -130,7 +131,7 @@ class WorkerThread(QThread):
|
|||
def install_package(self):
|
||||
try:
|
||||
self.text_output.emit(f"Installing {self.package_name}...")
|
||||
subprocess.run(self.package_command.split(), check=True)
|
||||
subprocess.run(shlex.split(self.package_command), check=True)
|
||||
self.text_output.emit(f"{self.package_name} installed!")
|
||||
self.finished.emit(True)
|
||||
except subprocess.CalledProcessError:
|
||||
|
@ -227,7 +228,7 @@ class WorkerThread(QThread):
|
|||
ConfigurationController.set_connection('tor')
|
||||
else:
|
||||
ConfigurationController.set_connection('system')
|
||||
ClientController.sync(client_observer=client_observer, connection_observer=connection_observer)
|
||||
self.check_for_update()
|
||||
locations = LocationController.get_all()
|
||||
all_location_codes = [f"{location.country_code}_{location.code}" for location in locations]
|
||||
self.sync_output.emit(all_location_codes, True, False, locations, False)
|
||||
|
@ -317,12 +318,15 @@ class CustomWindow(QMainWindow):
|
|||
|
||||
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||
self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)
|
||||
self.setWindowTitle("HydraVeil VPN")
|
||||
self.setWindowTitle('HydraVeil')
|
||||
self._data = {"Profile_1": {}}
|
||||
|
||||
self.gui_dir = os.path.join(Constants.HV_DATA_HOME, "gui")
|
||||
self.gui_config_file = os.path.join(self.gui_dir, "config.json")
|
||||
self.gui_log_file = os.path.join(self.gui_dir, "gui.log")
|
||||
self.gui_config_home = os.path.join(Constants.HV_CONFIG_HOME, 'gui')
|
||||
self.gui_config_file = os.path.join(self.gui_config_home, 'config.json')
|
||||
|
||||
self.gui_cache_home = os.path.join(Constants.HV_CACHE_HOME, 'gui')
|
||||
self.gui_log_file = os.path.join(self.gui_cache_home, 'gui.log')
|
||||
|
||||
self._setup_gui_directory()
|
||||
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
@ -330,10 +334,8 @@ class CustomWindow(QMainWindow):
|
|||
self.btn_path = os.getenv('BTN_PATH', os.path.join(current_dir, 'resources', 'images'))
|
||||
self.css_path = os.getenv('CSS_PATH', os.path.join(current_dir, 'resources', 'styles'))
|
||||
|
||||
icon_base = os.getenv('SYSTEM_TRAY_ICON', '')
|
||||
|
||||
self.tray = None
|
||||
self.init_system_tray(icon_base)
|
||||
self.init_system_tray(self.btn_path)
|
||||
|
||||
self.is_downloading = False
|
||||
self.current_profile_id = None
|
||||
|
@ -351,6 +353,12 @@ class CustomWindow(QMainWindow):
|
|||
profile_observer.subscribe('created', lambda event: self.update_status('Profile Created'))
|
||||
profile_observer.subscribe('destroyed', lambda event: self.update_status('Profile destroyed'))
|
||||
|
||||
client_observer.subscribe('synchronizing', lambda event: self.update_status('Sync in progress...'))
|
||||
client_observer.subscribe('synchronized', lambda event: self.update_status('Sync complete'))
|
||||
|
||||
client_observer.subscribe('updating', lambda event: self.update_status('Updating client...'))
|
||||
client_observer.subscribe('update_progressing', lambda event: self.update_status(f'Current progress: {event.meta.get('progress'):.2f}%'))
|
||||
client_observer.subscribe('updated', lambda event: self.update_status('Restart client to apply update.'))
|
||||
|
||||
application_version_observer.subscribe('downloading', lambda event: self.update_status(f'Downloading {ApplicationController.get(event.subject.application_code).name}'))
|
||||
application_version_observer.subscribe('download_progressing', lambda event: self.update_status(f'Downloading {ApplicationController.get(event.subject.application_code).name} {event.meta.get('progress'):.2f}%'))
|
||||
|
@ -389,7 +397,7 @@ class CustomWindow(QMainWindow):
|
|||
self.scroll_speed = 1
|
||||
|
||||
self.tor_text = QLabel("Billing & Browsers", self)
|
||||
self.tor_text.setGeometry(515, 542, 150, 20)
|
||||
self.tor_text.setGeometry(532, 541, 150, 20)
|
||||
self.tor_text.setStyleSheet("""
|
||||
QLabel {
|
||||
color: cyan;
|
||||
|
@ -401,31 +409,56 @@ class CustomWindow(QMainWindow):
|
|||
|
||||
|
||||
self.set_scroll_speed(7)
|
||||
|
||||
self.tor_icon = QLabel(self)
|
||||
self.tor_icon.setGeometry(705, 537, 25, 25)
|
||||
self.set_tor_icon()
|
||||
|
||||
|
||||
self.toggle_button = QPushButton(self)
|
||||
self.toggle_button.setGeometry(645, 541, 50, 22)
|
||||
self.toggle_button.setGeometry(670, 541, 50, 22)
|
||||
self.toggle_button.setCheckable(True)
|
||||
self.toggle_button.clicked.connect(self.toggle_mode)
|
||||
|
||||
|
||||
self.tor_icon = QLabel(self)
|
||||
self.tor_icon.setGeometry(730, 537, 25, 25)
|
||||
self.set_tor_icon()
|
||||
|
||||
self.create_toggle(self.is_tor_mode)
|
||||
self.animation_timer = QTimer()
|
||||
self.animation_timer.timeout.connect(self.animate_toggle)
|
||||
self.check_logging()
|
||||
|
||||
self.sync_button = QPushButton(self)
|
||||
self.sync_button.setGeometry(771, 541, 22, 22)
|
||||
self.sync_button.setObjectName('sync_button')
|
||||
|
||||
if CustomWindow.should_be_synchronized():
|
||||
sync_icon = QtGui.QIcon(os.path.join(self.btn_path, 'icon_sync_urgent.png'))
|
||||
else:
|
||||
sync_icon = QtGui.QIcon(os.path.join(self.btn_path, 'icon_sync.png'))
|
||||
|
||||
self.sync_button.setToolTip('Sync')
|
||||
self.sync_button.setIcon(sync_icon)
|
||||
self.sync_button.setIconSize(self.sync_button.size())
|
||||
self.sync_button.clicked.connect(self.sync)
|
||||
|
||||
self.init_ui()
|
||||
|
||||
current_connection = self.get_current_connection()
|
||||
self.is_tor_mode = current_connection == 'tor'
|
||||
self.set_toggle_state(self.is_tor_mode)
|
||||
|
||||
def perform_update_check(self):
|
||||
update_available = ClientController.can_be_updated()
|
||||
|
||||
if update_available:
|
||||
menu_page = self.page_stack.findChild(MenuPage)
|
||||
menu_page.on_update_check_finished()
|
||||
|
||||
|
||||
def sync(self):
|
||||
if ConfigurationController.get_connection() == 'tor':
|
||||
self.worker_thread = WorkerThread('SYNC_TOR')
|
||||
else:
|
||||
self.worker_thread = WorkerThread('SYNC')
|
||||
self.sync_button.setIcon(QtGui.QIcon(os.path.join(self.btn_path, 'icon_sync.png')))
|
||||
self.worker_thread.finished.connect(lambda: self.perform_update_check())
|
||||
self.worker_thread.start()
|
||||
|
||||
def _handle_exception(self, identifier, message, trace):
|
||||
if issubclass(identifier, UnknownConnectionTypeError):
|
||||
|
@ -515,7 +548,8 @@ class CustomWindow(QMainWindow):
|
|||
return ConfigurationController.get_connection()
|
||||
|
||||
def _setup_gui_directory(self):
|
||||
os.makedirs(self.gui_dir, exist_ok=True)
|
||||
os.makedirs(self.gui_cache_home, exist_ok=True)
|
||||
os.makedirs(self.gui_config_home, exist_ok=True)
|
||||
|
||||
if not os.path.exists(self.gui_config_file):
|
||||
default_config = {
|
||||
|
@ -562,7 +596,7 @@ class CustomWindow(QMainWindow):
|
|||
|
||||
|
||||
def _setup_gui_logging(self):
|
||||
os.makedirs(self.gui_dir, exist_ok=True)
|
||||
os.makedirs(self.gui_cache_home, exist_ok=True)
|
||||
|
||||
formatter = logging.Formatter(
|
||||
'%(asctime)s - %(levelname)s - %(message)s',
|
||||
|
@ -734,24 +768,25 @@ class CustomWindow(QMainWindow):
|
|||
|
||||
|
||||
|
||||
def init_system_tray(self, icon_base):
|
||||
def init_system_tray(self, icon_base_path):
|
||||
if QSystemTrayIcon.isSystemTrayAvailable():
|
||||
tray_icon = QIcon()
|
||||
window_icon = QIcon()
|
||||
|
||||
tray_sizes = [22, 24]
|
||||
for size in tray_sizes:
|
||||
icon_path = os.path.join(icon_base, f"{size}x{size}/apps/simplified-privacy.png")
|
||||
icon_path = os.path.join(icon_base_path, f"hv-icon-{size}x{size}.png")
|
||||
if os.path.exists(icon_path):
|
||||
tray_icon.addFile(icon_path, QSize(size, size))
|
||||
|
||||
window_sizes = [32, 48, 64, 128]
|
||||
for size in window_sizes:
|
||||
icon_path = os.path.join(icon_base, f"{size}x{size}/apps/simplified-privacy.png")
|
||||
icon_path = os.path.join(icon_base_path, f"hv-icon-{size}x{size}.png")
|
||||
if os.path.exists(icon_path):
|
||||
window_icon.addFile(icon_path, QSize(size, size))
|
||||
|
||||
|
||||
self.tray = QSystemTrayIcon(self)
|
||||
self.tray.setToolTip('HydraVeil')
|
||||
self.tray.setIcon(tray_icon)
|
||||
self.tray.setVisible(True)
|
||||
self.setWindowIcon(window_icon)
|
||||
|
@ -828,13 +863,13 @@ class CustomWindow(QMainWindow):
|
|||
self.editor_page = EditorPage(self.page_stack, self)
|
||||
self.pages = [MenuPage(self.page_stack, self),
|
||||
ProtocolPage(self.page_stack, self),
|
||||
WireGuardPage(self.page_stack, self),
|
||||
HidetorPage(self.page_stack, self),
|
||||
LocationPage(self.page_stack, self),
|
||||
BrowserPage(self.page_stack, self),
|
||||
ScreenPage(self.page_stack, self),
|
||||
ResumePage(self.page_stack, self),
|
||||
WireGuardPage(self.page_stack, self),
|
||||
ResidentialPage(self.page_stack, self),
|
||||
HidetorPage(self.page_stack, self),
|
||||
InstallSystemPackage(self.page_stack, self),
|
||||
WelcomePage(self.page_stack, self),
|
||||
PaymentPage(self.page_stack, self),
|
||||
|
@ -878,8 +913,8 @@ class CustomWindow(QMainWindow):
|
|||
self.disable_marquee()
|
||||
|
||||
def check_first_launch(self):
|
||||
config_file = os.path.join(self.gui_dir, "first_launch.txt")
|
||||
|
||||
|
||||
config_file = os.path.join(self.gui_config_home, '.first-run')
|
||||
os.makedirs(Constants.HV_CONFIG_HOME, exist_ok=True)
|
||||
|
||||
is_first_launch = not os.path.exists(config_file)
|
||||
|
@ -936,7 +971,23 @@ class CustomWindow(QMainWindow):
|
|||
def set_scroll_speed(self, speed):
|
||||
self.scroll_speed = speed
|
||||
|
||||
@staticmethod
|
||||
def should_be_synchronized():
|
||||
|
||||
current_datetime = datetime.today().astimezone(timezone.utc)
|
||||
last_synced_at = ConfigurationController.get_last_synced_at()
|
||||
|
||||
return last_synced_at is None or (current_datetime - last_synced_at) >= timedelta(days=30)
|
||||
|
||||
def page_changed(self, index):
|
||||
|
||||
if CustomWindow.should_be_synchronized():
|
||||
sync_icon = QtGui.QIcon(os.path.join(self.btn_path, 'icon_sync_urgent.png'))
|
||||
else:
|
||||
sync_icon = QtGui.QIcon(os.path.join(self.btn_path, 'icon_sync.png'))
|
||||
|
||||
self.sync_button.setIcon(sync_icon)
|
||||
|
||||
current_page = self.page_stack.widget(index)
|
||||
if self.page_history:
|
||||
previous_page = self.page_history[-1]
|
||||
|
@ -958,15 +1009,17 @@ class CustomWindow(QMainWindow):
|
|||
elif isinstance(current_page, LocationPage):
|
||||
self.update_status(None, clear=True)
|
||||
elif isinstance(current_page, InstallSystemPackage):
|
||||
self.update_status("Required tools are not installed. Please install them.")
|
||||
self.update_status('Please check package availability.')
|
||||
else:
|
||||
self.update_status(None)
|
||||
|
||||
if isinstance(current_page, MenuPage):
|
||||
self.sync_button.setVisible(True)
|
||||
self.tor_text.setVisible(True)
|
||||
self.toggle_button.setGeometry(645, 541, 50, 22)
|
||||
self.tor_icon.setGeometry(705, 537, 25, 25)
|
||||
self.toggle_button.setGeometry(670, 541, 50, 22)
|
||||
self.tor_icon.setGeometry(730, 537, 25, 25)
|
||||
else:
|
||||
self.sync_button.setVisible(False)
|
||||
self.tor_text.setVisible(False)
|
||||
self.toggle_button.setGeometry(540, 541, 50, 22)
|
||||
self.tor_icon.setGeometry(600, 537, 25, 25)
|
||||
|
@ -1104,7 +1157,7 @@ class Worker(QObject):
|
|||
except ProfileStateConflictError:
|
||||
self.update_signal.emit("The profile is already being enabled...", False, None, None, None)
|
||||
except CommandNotFoundError as e :
|
||||
self.update_signal.emit(str(e), False, -1, None, None)
|
||||
self.update_signal.emit(str(e.subject), False, -1, None, None)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.update_signal.emit("An unknown error occurred", False, None, None, None)
|
||||
|
@ -1198,7 +1251,7 @@ class MenuPage(Page):
|
|||
self.worker = WorkerThread('DOWNLOAD_UPDATE')
|
||||
self.worker.start()
|
||||
else:
|
||||
self.update_status.update_status("Update cancelled by user.")
|
||||
self.update_status.update_status('Update canceled')
|
||||
|
||||
|
||||
|
||||
|
@ -1328,6 +1381,9 @@ class MenuPage(Page):
|
|||
return new_dict
|
||||
|
||||
def create(self):
|
||||
self.boton_just.setEnabled(False)
|
||||
self.boton_just_session.setEnabled(False)
|
||||
self.boton_edit.setEnabled(False)
|
||||
self.profiles_data = self.match_core_profiles(ProfileController.get_all())
|
||||
self.number_of_profiles = len(self.profiles_data)
|
||||
self.profile_info.update(self.profiles_data)
|
||||
|
@ -1933,7 +1989,7 @@ class ConnectionManager:
|
|||
|
||||
def get_is_profile_being_enabled(self, profile_id: int) -> bool:
|
||||
return self._is_profile_being_enabled.get(profile_id, False)
|
||||
|
||||
|
||||
class InstallSystemPackage(Page):
|
||||
def __init__(self, page_stack, main_window=None, parent=None):
|
||||
super().__init__("InstallPackage", page_stack, main_window, parent)
|
||||
|
@ -1953,110 +2009,104 @@ class InstallSystemPackage(Page):
|
|||
|
||||
self.auto_install_package_commands = {
|
||||
'all': {
|
||||
'debian': 'pkexec apt install -y bubblewrap iproute2 microsocks proxychains4 ratpoison tor wireguard xserver-xephyr resolvconf',
|
||||
'arch': 'pkexec pacman -S bubblewrap iproute2 microsocks proxychains-ng tor wireguard-tools xorg-server-xephyr resolvconf',
|
||||
'fedora': 'pkexec dnf -y install bubblewrap iproute-doc proxychains4 ratpoison tor wireguard xorg-x11-server-Xephyr resolvconf'
|
||||
'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'
|
||||
},
|
||||
|
||||
'wireguard': {
|
||||
'debian': 'pkexec apt -y install wireguard resolvconf',
|
||||
'arch': 'pkexec pacman -S wireguard-tools resolvconf',
|
||||
'fedora': 'pkexec dnf -y install wireguard-tools resolvconf'
|
||||
'bwrap': {
|
||||
'debian': 'pkexec apt -y install bubblewrap',
|
||||
'arch': 'pkexec pacman -S bubblewrap',
|
||||
'fedora': 'pkexec dnf -y install bubblewrap'
|
||||
},
|
||||
'resolvconf': {
|
||||
'debian': 'pkexec apt -y install resolvconf',
|
||||
'arch': 'pkexec pacman -S resolvconf',
|
||||
'fedora': 'pkexec dnf -y install resolvconf'
|
||||
},
|
||||
'tor': {
|
||||
'debian': 'pkexec apt -y install tor proxychains4 microsocks',
|
||||
'arch': 'pkexec pacman -S tor proxychains4 microsocks',
|
||||
'fedora': 'pkexec dnf -y install tor proxychains4'
|
||||
},
|
||||
'tor_sync': {
|
||||
'debian': 'pkexec apt -y install tor',
|
||||
'arch': 'pkexec pacman -S tor',
|
||||
'fedora': 'pkexec dnf -y install tor'
|
||||
},
|
||||
'proxychains4': {
|
||||
'debian': 'pkexec apt -y install proxychains4',
|
||||
'arch': 'pkexec pacman -S proxychains-ng',
|
||||
'fedora': 'pkexec dnf -y install proxychains4'
|
||||
'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': 'pkexec dnf -y install 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."'
|
||||
},
|
||||
'xserver-xephyr': {
|
||||
'debian': 'pkexec apt -y install xserver-xephyr bubblewrap ratpoison',
|
||||
'arch': 'pkexec pacman -S xorg-server-xephyr bubblewrap ratpoison',
|
||||
'fedora': 'pkexec dnf -y install xorg-x11-server-Xephyr bubblewrap ratpoison'
|
||||
'proxychains4': {
|
||||
'debian': 'pkexec apt -y install proxychains4',
|
||||
'arch': 'pkexec pacman -S proxychains-ng',
|
||||
'fedora': 'pkexec dnf -y install proxychains4'
|
||||
},
|
||||
'bubblewrap': {
|
||||
'debian': 'pkexec apt -y install bubblewrap',
|
||||
'arch': 'pkexec pacman -S bubblewrap',
|
||||
'fedora': 'pkexec dnf -y install bubblewrap'
|
||||
'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 resolvconf',
|
||||
'arch': 'sudo pacman -S bubblewrap iproute2 microsocks proxychains-ng tor wireguard-tools xorg-server-xephyr resolvconf',
|
||||
'fedora': 'sudo dnf -y install bubblewrap iproute-doc proxychains4 ratpoison tor wireguard xorg-x11-server-Xephyr resolvconf'
|
||||
'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'
|
||||
},
|
||||
'polkit': {
|
||||
'debian': 'sudo apt -y install policykit-1',
|
||||
'arch': 'sudo pacman -S polkit',
|
||||
'fedora': 'sudo dnf -y install polkit'
|
||||
'bwrap': {
|
||||
'debian': 'sudo apt -y install bubblewrap',
|
||||
'arch': 'sudo pacman -S bubblewrap',
|
||||
'fedora': 'sudo dnf -y install bubblewrap'
|
||||
},
|
||||
'wireguard': {
|
||||
'debian': 'sudo apt -y install wireguard resolvconf',
|
||||
'arch': 'sudo pacman -S wireguard-tools resolvconf',
|
||||
'fedora': 'sudo dnf -y install wireguard-tools resolvconf'
|
||||
},
|
||||
'resolvconf': {
|
||||
'debian': 'sudo apt -y install resolvconf',
|
||||
'arch': 'sudo pacman -S resolvconf',
|
||||
'fedora': 'sudo dnf -y install resolvconf'
|
||||
},
|
||||
'tor': {
|
||||
'debian': 'sudo apt -y install tor proxychains4 microsocks',
|
||||
'arch': 'sudo pacman -S tor proxychains-ng microsocks',
|
||||
'fedora': 'sudo dnf -y install tor proxychains4'
|
||||
},
|
||||
'tor_sync': {
|
||||
'debian': 'sudo apt -y install tor',
|
||||
'arch': 'sudo pacman -S tor',
|
||||
'fedora': 'sudo dnf -y install tor'
|
||||
},
|
||||
'proxychains4': {
|
||||
'debian': 'sudo apt -y install proxychains4',
|
||||
'arch': 'sudo pacman -S proxychains-ng',
|
||||
'fedora': 'sudo dnf -y install proxychains4'
|
||||
'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': 'sudo dnf -y install microsocks'
|
||||
'fedora': '# Package not available.'
|
||||
},
|
||||
'xserver-xephyr': {
|
||||
'debian': 'sudo apt -y install xserver-xephyr bubblewrap ratpoison',
|
||||
'arch': 'sudo pacman -S xorg-server-xephyr bubblewrap ratpoison',
|
||||
'fedora': 'sudo dnf -y install xorg-x11-server-Xephyr bubblewrap ratpoison'
|
||||
'proxychains4': {
|
||||
'debian': 'sudo apt -y install proxychains4',
|
||||
'arch': 'sudo pacman -S proxychains-ng',
|
||||
'fedora': 'sudo dnf -y install proxychains4'
|
||||
},
|
||||
'bubblewrap': {
|
||||
'debian': 'sudo apt -y install bubblewrap',
|
||||
'arch': 'sudo pacman -S bubblewrap',
|
||||
'fedora': 'sudo dnf -y install bubblewrap'
|
||||
'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: 24px; font-weight: bold;")
|
||||
self.title.setStyleSheet("font-size: 22px; font-weight: bold;")
|
||||
self.permanent_elements.append(self.title)
|
||||
|
||||
self.display.setGeometry(QtCore.QRect(100, 160, 580, 435))
|
||||
|
@ -2147,6 +2197,7 @@ class InstallSystemPackage(Page):
|
|||
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)
|
||||
|
@ -2182,6 +2233,7 @@ class InstallSystemPackage(Page):
|
|||
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)
|
||||
|
||||
|
@ -2368,7 +2420,7 @@ class InstallSystemPackage(Page):
|
|||
def check_package(self):
|
||||
try:
|
||||
if self.package_name.lower() == 'all':
|
||||
packages = ['bwrap', 'ip', 'microsocks', 'proxychains4', 'ratpoison', 'tor', 'Xephyr', 'wg', 'resolvconf']
|
||||
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")
|
||||
|
@ -2397,120 +2449,6 @@ class InstallSystemPackage(Page):
|
|||
def go_next(self):
|
||||
self.page_stack.setCurrentIndex(self.page_stack.indexOf(self.page_stack.findChild(MenuPage)))
|
||||
|
||||
class WelcomePage(Page):
|
||||
def __init__(self, page_stack, main_window=None, parent=None):
|
||||
super().__init__("Welcome", page_stack, main_window, parent)
|
||||
self.btn_path = main_window.btn_path
|
||||
self.update_status = main_window
|
||||
self.ui_elements = []
|
||||
self.button_next.clicked.connect(self.go_to_install)
|
||||
self.button_next.setVisible(True)
|
||||
self._setup_welcome_ui()
|
||||
self._setup_stats_display()
|
||||
|
||||
def _setup_welcome_ui(self):
|
||||
welcome_title = QLabel("Welcome to HydraVeilVPN", self)
|
||||
welcome_title.setGeometry(20, 45, 760, 60)
|
||||
welcome_title.setStyleSheet("""
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
color: cyan;
|
||||
""")
|
||||
welcome_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
welcome_msg = QLabel(
|
||||
"Before we begin your journey, we need to set up a few essential components. "
|
||||
"Click 'Next' to take you to the installation page.", self)
|
||||
welcome_msg.setGeometry(40, 100, 720, 80)
|
||||
welcome_msg.setWordWrap(True)
|
||||
welcome_msg.setStyleSheet("""
|
||||
font-size: 16px;
|
||||
color: cyan;
|
||||
""")
|
||||
welcome_msg.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
def _setup_stats_display(self):
|
||||
stats_container = QWidget(self)
|
||||
stats_container.setGeometry(70, 200, 730, 240)
|
||||
|
||||
stats_title = QLabel(stats_container)
|
||||
stats_title.setText("Features & Services")
|
||||
stats_title.setGeometry(190, 10, 300, 30)
|
||||
stats_title.setStyleSheet("""
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: cyan;
|
||||
""")
|
||||
|
||||
grid_widget = QWidget(stats_container)
|
||||
grid_widget.setGeometry(0, 70, 700, 190)
|
||||
grid_layout = QGridLayout(grid_widget)
|
||||
grid_layout.setSpacing(30)
|
||||
|
||||
stats = [
|
||||
("Browsers", "browsers_mini.png", "10 Supported Browsers", True),
|
||||
("WireGuard", "wireguard_mini.png", "6 Global Locations", True),
|
||||
("Proxy", "just proxy_mini.png", "5 Proxy Locations", True),
|
||||
("Tor", "toricon_mini.png", "Tor Network Access", True)
|
||||
]
|
||||
|
||||
for i, (title, icon_name, count, available) in enumerate(stats):
|
||||
row = i // 2
|
||||
col = i % 2
|
||||
|
||||
stat_widget = QWidget()
|
||||
stat_layout = QHBoxLayout(stat_widget)
|
||||
stat_layout.setContentsMargins(20, 10, 2, 10)
|
||||
stat_layout.setSpacing(15)
|
||||
|
||||
icon_label = QLabel()
|
||||
icon_path = os.path.join(self.btn_path, icon_name)
|
||||
icon_label.setPixmap(QPixmap(icon_path).scaled(30, 30, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation))
|
||||
icon_label.setFixedSize(30, 30)
|
||||
stat_layout.addWidget(icon_label)
|
||||
|
||||
text_widget = QWidget()
|
||||
text_layout = QVBoxLayout(text_widget)
|
||||
text_layout.setSpacing(5)
|
||||
text_layout.setContentsMargins(0, 0, 30, 0)
|
||||
|
||||
title_widget = QWidget()
|
||||
title_layout = QHBoxLayout(title_widget)
|
||||
title_layout.setContentsMargins(0, 0, 0, 0)
|
||||
title_layout.setSpacing(10)
|
||||
|
||||
title_label = QLabel(title)
|
||||
title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #2c3e50;")
|
||||
title_layout.addWidget(title_label)
|
||||
|
||||
status_indicator = QLabel("●")
|
||||
status_indicator.setStyleSheet("color: #2ecc71; font-size: 16px;")
|
||||
title_layout.addWidget(status_indicator)
|
||||
|
||||
title_layout.addStretch()
|
||||
text_layout.addWidget(title_widget)
|
||||
|
||||
count_label = QLabel(count)
|
||||
count_label.setStyleSheet("color: #34495e;")
|
||||
text_layout.addWidget(count_label)
|
||||
|
||||
stat_layout.addWidget(text_widget, stretch=1)
|
||||
|
||||
stat_widget.setStyleSheet("""
|
||||
QWidget {
|
||||
background-color: transparent;
|
||||
border-radius: 10px;
|
||||
}
|
||||
""")
|
||||
|
||||
grid_layout.addWidget(stat_widget, row, col)
|
||||
|
||||
|
||||
def go_to_install(self):
|
||||
install_page = self.page_stack.findChild(InstallSystemPackage)
|
||||
install_page.configure(package_name='all', distro='debian')
|
||||
self.page_stack.setCurrentIndex(self.page_stack.indexOf(install_page))
|
||||
|
||||
class ProtocolPage(Page):
|
||||
def __init__(self, page_stack, main_window=None, parent=None):
|
||||
super().__init__("Protocol", page_stack,main_window, parent)
|
||||
|
@ -2538,8 +2476,7 @@ class ProtocolPage(Page):
|
|||
for j, (object_type,icon_name, page_class, geometry) in enumerate([
|
||||
(QPushButton,"wireguard", WireGuardPage, (585, 90,185,75)),
|
||||
(QPushButton,"residential", ResidentialPage, (585, 90+30+75,185,75)),
|
||||
(QPushButton,"hidetor", HidetorPage, (585, 90+30+75+30+75,185,75)),
|
||||
(QPushButton,"open", LocationPage, (585, 90+30+75+30+75+30+75,185,75))
|
||||
(QPushButton,"hidetor", HidetorPage, (585, 90+30+75+30+75,185,75))
|
||||
]):
|
||||
boton = object_type(self)
|
||||
boton.setGeometry(*geometry)
|
||||
|
@ -2559,6 +2496,7 @@ class ProtocolPage(Page):
|
|||
self.update_status.write_data({"protocol": self.selected_protocol_icon})
|
||||
|
||||
def show_protocol(self, page_class, protocol):
|
||||
self.update_status.clear_data()
|
||||
self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"{protocol}.png")).scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio))
|
||||
self.selected_protocol_icon = protocol
|
||||
self.selected_page_class = page_class
|
||||
|
@ -2767,7 +2705,7 @@ class HidetorPage(Page):
|
|||
boton.setVisible(False)
|
||||
boton.setIcon(QIcon(os.path.join(self.btn_path, f"button_{icon_name}.png")))
|
||||
if boton.icon().isNull():
|
||||
boton.setPixmap(QPixmap(os.path.join(self.btn_path, "default_location_button.png")))
|
||||
boton.setIcon(QIcon(os.path.join(self.btn_path, "default_location_button.png")))
|
||||
self.buttons.append(boton)
|
||||
self.buttonGroup.addButton(boton, j)
|
||||
boton.clicked.connect(lambda _, location=icon_name: self.show_location(location))
|
||||
|
@ -3075,7 +3013,7 @@ class LocationPage(Page):
|
|||
|
||||
|
||||
def show_location(self, location):
|
||||
self.initial_display.hide()
|
||||
self.initial_display.hide()
|
||||
self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"icon_{location}.png")).scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio))
|
||||
self.selected_location_icon = location
|
||||
self.button_next.setVisible(True)
|
||||
|
@ -3356,7 +3294,7 @@ class ResumePage(Page):
|
|||
|
||||
def create_interface_elements(self):
|
||||
for j, (object_type, icon_name, geometry) in enumerate([
|
||||
(QLineEdit, None, (130, 70, 300, 30)),
|
||||
(QLineEdit, None, (130, 73, 300, 24)),
|
||||
(QLabel, None, (0,0,0,0))
|
||||
]):
|
||||
|
||||
|
@ -3373,8 +3311,8 @@ class ResumePage(Page):
|
|||
self.line_edit.setGeometry(*geometry)
|
||||
self.line_edit.setMaxLength(13)
|
||||
self.line_edit.textChanged.connect(self.toggle_button_visibility)
|
||||
self.line_edit.setStyleSheet("background-color: rgba(22, 10, 30, 0.5);")
|
||||
self.line_edit.setPlaceholderText("Type a name here")
|
||||
self.line_edit.setStyleSheet("background-color: transparent; border: none; color: cyan;")
|
||||
self.line_edit.setPlaceholderText("Enter profile name")
|
||||
self.line_edit.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
self.create()
|
||||
|
||||
|
@ -3644,13 +3582,14 @@ class EditorPage(Page):
|
|||
self.button_back.clicked.connect(self.go_back)
|
||||
|
||||
self.name_handle = QLabel(self)
|
||||
self.name_handle.setGeometry(125, 70, 400, 30)
|
||||
self.name_handle.setGeometry(134, 70, 400, 30)
|
||||
self.name_handle.setStyleSheet('color: #cacbcb;')
|
||||
self.name_handle.setText("Profile Name:")
|
||||
|
||||
self.name = QLineEdit(self)
|
||||
self.name.setGeometry(275, 70, 400, 30)
|
||||
self.name.setGeometry(288, 70, 190, 30)
|
||||
|
||||
self.name.setStyleSheet("border: transparent;")
|
||||
self.name.setStyleSheet("color: cyan; border: transparent;")
|
||||
|
||||
self.temp_changes = {}
|
||||
self.original_values = {}
|
||||
|
@ -4144,7 +4083,7 @@ class Settings(Page):
|
|||
border: none;
|
||||
background: transparent;
|
||||
color: #808080;
|
||||
font-size: 15px;
|
||||
font-size: 14px;
|
||||
{self.font_style}
|
||||
}}
|
||||
QPushButton:checked {{
|
||||
|
@ -4477,7 +4416,7 @@ class Settings(Page):
|
|||
|
||||
value_label = ClickableValueLabel("N/A", stat_widget)
|
||||
value_label.setObjectName("value_label")
|
||||
value_label.setStyleSheet(f"color: #00ffff; font-size: 13px; font-weight: bold; {self.font_style}")
|
||||
value_label.setStyleSheet(f"color: #00ffff; font-size: 12px; font-family: 'Retro Gaming', sans-serif;")
|
||||
value_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
stat_widget.layout().insertWidget(0, value_label)
|
||||
|
||||
|
@ -4639,15 +4578,21 @@ class Settings(Page):
|
|||
layout.setSpacing(10)
|
||||
|
||||
config_path = Constants.HV_CONFIG_HOME
|
||||
|
||||
current_connection = self.update_status.get_current_connection();
|
||||
|
||||
if current_connection is not None:
|
||||
current_connection = current_connection.capitalize()
|
||||
|
||||
stats = [
|
||||
("Config Path", config_path),
|
||||
("Client Version", Constants.HV_CLIENT_VERSION_NUMBER),
|
||||
("Connection", self.update_status.get_current_connection()),
|
||||
("Connection", current_connection),
|
||||
]
|
||||
|
||||
for i, (label, value) in enumerate(stats):
|
||||
info_widget = QLabel(f"{label}: {value}")
|
||||
info_widget.setStyleSheet(f"color: white; padding: 5px; {self.font_style}")
|
||||
info_widget.setStyleSheet(f"font-size: 14px; color: white; padding: 5px; {self.font_style}")
|
||||
info_widget.setWordWrap(True)
|
||||
layout.addWidget(info_widget, i, 0)
|
||||
|
||||
|
@ -4667,7 +4612,7 @@ class Settings(Page):
|
|||
|
||||
value_label = QLabel(str(value))
|
||||
value_label.setObjectName("value_label")
|
||||
value_label.setStyleSheet("color: #00ffff; font-size: 24px; font-weight: bold")
|
||||
value_label.setStyleSheet("font-family: 'Retro Gaming', sans-serif; color: #00ffff; font-size: 22px; font-weight: bold")
|
||||
value_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
desc_label = QLabel(label)
|
||||
|
@ -4744,7 +4689,7 @@ class Settings(Page):
|
|||
title.setStyleSheet(f"color: #808080; font-size: 12px; font-weight: bold; {self.font_style}")
|
||||
layout.addWidget(title)
|
||||
|
||||
log_text = QLabel(f"If enabled, these Error Logs would be stored locally on your computer\nat {Constants.HV_DATA_HOME}/gui")
|
||||
log_text = QLabel(f"Enabling this feature means error logs will be stored on your local\nmachine at: '{Constants.HV_CACHE_HOME}/gui'.")
|
||||
log_text.setStyleSheet(f"color: white; font-size: 14px; {self.font_style}")
|
||||
layout.addWidget(log_text)
|
||||
|
||||
|
@ -4818,7 +4763,7 @@ class IdPage(Page):
|
|||
self.btn_path = main_window.btn_path
|
||||
self.buttonGroup = QButtonGroup(self)
|
||||
self.display.setGeometry(QtCore.QRect(-10, 50, 550, 460))
|
||||
self.display.setPixmap(QPixmap(os.path.join(self.btn_path, "wireguardx.png")).scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio))
|
||||
# self.display.setPixmap(QPixmap(os.path.join(self.btn_path, "wireguardx.png")).scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio))
|
||||
self.create_interface_elements()
|
||||
|
||||
|
||||
|
@ -5336,7 +5281,7 @@ class PaymentConfirmed(Page):
|
|||
self.update_status = main_window
|
||||
self.buttonGroup = QButtonGroup(self)
|
||||
self.display.setGeometry(QRect(-10, 50, 550, 460))
|
||||
self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"wireguardx.png")).scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio))
|
||||
# self.display.setPixmap(QPixmap(os.path.join(self.btn_path, f"wireguardx.png")).scaled(self.display.size(), Qt.AspectRatioMode.KeepAspectRatio))
|
||||
|
||||
self.button_reverse.setVisible(False)
|
||||
|
||||
|
@ -5652,7 +5597,7 @@ class SyncScreen(Page):
|
|||
def perform_sync(self):
|
||||
self.button_go.setEnabled(False)
|
||||
self.button_back.setEnabled(False)
|
||||
self.update_status.update_status('Syncing in progress...')
|
||||
self.update_status.update_status('Sync in progress...')
|
||||
if self.main_window.get_current_connection() == 'tor':
|
||||
self.worker_thread = WorkerThread('SYNC_TOR')
|
||||
else:
|
||||
|
@ -5754,7 +5699,7 @@ class WelcomePage(Page):
|
|||
self._setup_stats_display()
|
||||
|
||||
def _setup_welcome_ui(self):
|
||||
welcome_title = QLabel("Welcome to HydraVeilVPN", self)
|
||||
welcome_title = QLabel('Welcome to HydraVeil', self)
|
||||
welcome_title.setGeometry(20, 45, 760, 60)
|
||||
welcome_title.setStyleSheet("""
|
||||
font-size: 32px;
|
||||
|
@ -5836,7 +5781,7 @@ class WelcomePage(Page):
|
|||
text_layout.addWidget(title_widget)
|
||||
|
||||
count_label = QLabel(count)
|
||||
count_label.setStyleSheet("color: #34495e;")
|
||||
count_label.setStyleSheet("font-size: 16px; color: #34495e;")
|
||||
text_layout.addWidget(count_label)
|
||||
|
||||
stat_layout.addWidget(text_widget, stretch=1)
|
||||
|
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 985 B |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1,012 B |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 7.4 KiB |
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 1,000 B |
Before Width: | Height: | Size: 831 B After Width: | Height: | Size: 429 B |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 999 B |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 7 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 242 KiB After Width: | Height: | Size: 194 KiB |
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 5.7 KiB |
Before Width: | Height: | Size: 6 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 474 B |
Before Width: | Height: | Size: 624 B After Width: | Height: | Size: 339 B |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 3.5 KiB |
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 510 B After Width: | Height: | Size: 351 B |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 172 KiB After Width: | Height: | Size: 124 KiB |
Before Width: | Height: | Size: 632 KiB After Width: | Height: | Size: 517 KiB |
Before Width: | Height: | Size: 429 KiB After Width: | Height: | Size: 325 KiB |
Before Width: | Height: | Size: 32 KiB |
Before Width: | Height: | Size: 453 B After Width: | Height: | Size: 254 B |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 822 B After Width: | Height: | Size: 434 B |
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 962 B |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 962 B |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 622 B |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 6.6 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 547 B |
Before Width: | Height: | Size: 821 B After Width: | Height: | Size: 433 B |
Before Width: | Height: | Size: 797 B After Width: | Height: | Size: 433 B |
Before Width: | Height: | Size: 826 B After Width: | Height: | Size: 433 B |
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 9.9 KiB |
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 841 B |
Before Width: | Height: | Size: 717 B After Width: | Height: | Size: 375 B |
Before Width: | Height: | Size: 831 B After Width: | Height: | Size: 429 B |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 774 B |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 802 B |
Before Width: | Height: | Size: 1.7 KiB After Width: | Height: | Size: 808 B |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 6.4 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 996 B |
Before Width: | Height: | Size: 158 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 140 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 188 KiB After Width: | Height: | Size: 162 KiB |
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 134 KiB |