project renamed as 'bauh'
5
bauh/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
__version__ = '0.5.1'
|
||||
__app_name__ = 'bauh'
|
||||
|
||||
import os
|
||||
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
144
bauh/app.py
Executable file
@@ -0,0 +1,144 @@
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import requests
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from colorama import Fore
|
||||
|
||||
from bauh import __version__, __app_name__
|
||||
from bauh.core import resource
|
||||
from bauh.core.controller import GenericApplicationManager
|
||||
from bauh.core.disk import DiskCacheLoaderFactory
|
||||
from bauh.core.flatpak.constants import FLATPAK_CACHE_PATH
|
||||
from bauh.core.flatpak.controller import FlatpakManager
|
||||
from bauh.core.flatpak.model import FlatpakApplication
|
||||
from bauh.core.snap.constants import SNAP_CACHE_PATH
|
||||
from bauh.core.snap.controller import SnapManager
|
||||
from bauh.core.snap.model import SnapApplication
|
||||
from bauh.util import util
|
||||
from bauh.util.cache import Cache
|
||||
from bauh.util.memory import CacheCleaner
|
||||
from bauh.view.qt.systray import TrayIcon
|
||||
from bauh.view.qt.window import ManageWindow
|
||||
|
||||
|
||||
def log_msg(msg: str, color: int = None):
|
||||
|
||||
if color is None:
|
||||
print('[{}] {}'.format(__app_name__, msg))
|
||||
else:
|
||||
print('{}[{}] {}{}'.format(color, __app_name__, msg, Fore.RESET))
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(prog=__app_name__, description="GUI for Flatpak applications management")
|
||||
parser.add_argument('-v', '--version', action='version', version='%(prog)s {}'.format(__version__))
|
||||
parser.add_argument('-e', '--cache-exp', action="store", default=int(os.getenv('BAUH_CACHE_EXPIRATION', 60 * 60)), type=int, help='cached API data expiration time in SECONDS. Default: %(default)s')
|
||||
parser.add_argument('-ie', '--icon-exp', action="store", default=int(os.getenv('BAUH_ICON_EXPIRATION', 60 * 5)), type=int, help='cached icons expiration time in SECONDS. Default: %(default)s')
|
||||
parser.add_argument('-l', '--locale', action="store", default=os.getenv('BAUH_LOCALE', 'en'), help='Translation key. Default: %(default)s')
|
||||
parser.add_argument('-i', '--check-interval', action="store", default=int(os.getenv('BAUH_CHECK_INTERVAL', 60)), type=int, help='Updates check interval in SECONDS. Default: %(default)s')
|
||||
parser.add_argument('-n', '--update-notification', action="store", choices=[0, 1], default=os.getenv('BAUH_UPDATE_NOTIFICATION', 1), type=int, help='Enables / disables system notifications for new updates. Default: %(default)s')
|
||||
parser.add_argument('-dc', '--disk-cache', action="store", choices=[0, 1], default=os.getenv('BAUH_DISK_CACHE', 1), type=int, help='Enables / disables disk cache. When disk cache is enabled, the installed applications data are loaded faster. Default: %(default)s')
|
||||
parser.add_argument('-di', '--download-icons', action="store", choices=[0, 1], default=os.getenv('BAUH_DOWNLOAD_ICONS', 1), type=int, help='Enables / disables app icons download. It may improve the application speed, depending of how applications data are retrieved by their extensions.')
|
||||
parser.add_argument('--flatpak', action="store", default=os.getenv('BAUH_FLATPAK', 1), choices=[0, 1], type=int, help='Enables / disables flatpak usage. Default: %(default)s')
|
||||
parser.add_argument('--snap', action="store", default=os.getenv('BAUH_SNAP', 1), choices=[0, 1], type=int, help='Enables / disables snap usage. Default: %(default)s')
|
||||
parser.add_argument('-co', '--check-packaging-once', action="store", default=os.getenv('BAUH_CHECK_PACKAGING_ONCE', 0), choices=[0, 1], type=int, help='If the available supported packaging types should be checked ONLY once. It improves the application speed if enabled, but can generate errors if you uninstall any packaging technology while using it, and every time a supported packaging type is installed it will only be available after a restart. Default: %(default)s')
|
||||
parser.add_argument('--tray', action="store", default=os.getenv('BAUH_TRAY', 0), choices=[0, 1], type=int, help='If the tray icon and update-check daemon should be created. Default: %(default)s')
|
||||
parser.add_argument('--sugs', action="store", default=os.getenv('BAUH_SUGGESTIONS', 1), choices=[0, 1], type=int, help='If app suggestions should be displayed if no app is installed (runtimes do not count as apps). Default: %(default)s')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.cache_exp < 0:
|
||||
log_msg("'cache-exp' set to '{}': cache will not expire.".format(args.cache_exp), Fore.YELLOW)
|
||||
|
||||
if args.icon_exp < 0:
|
||||
log_msg("'icon-exp' set to '{}': cache will not expire.".format(args.cache_exp), Fore.YELLOW)
|
||||
|
||||
if not args.locale.strip():
|
||||
log_msg("'locale' set as '{}'. You must provide a valid one. Aborting...".format(args.locale), Fore.RED)
|
||||
exit(1)
|
||||
|
||||
if args.check_interval <= 0:
|
||||
log_msg("'check-interval' set as '{}'. It must be >= 0. Aborting...".format(args.check_interval), Fore.RED)
|
||||
exit(1)
|
||||
|
||||
if not args.flatpak:
|
||||
log_msg("'flatpak' is disabled.", Fore.YELLOW)
|
||||
|
||||
if not args.snap:
|
||||
log_msg("'snap' is disabled.", Fore.YELLOW)
|
||||
|
||||
if args.update_notification == 0:
|
||||
log_msg('updates notifications are disabled', Fore.YELLOW)
|
||||
|
||||
if args.download_icons == 0:
|
||||
log_msg("'download-icons' is disabled", Fore.YELLOW)
|
||||
|
||||
if args.check_packaging_once == 1:
|
||||
log_msg("'check-packaging-once' is enabled", Fore.YELLOW)
|
||||
|
||||
if args.sugs == 0:
|
||||
log_msg("suggestions are disabled", Fore.YELLOW)
|
||||
|
||||
locale_keys = util.get_locale_keys(args.locale)
|
||||
|
||||
http_session = requests.Session()
|
||||
caches = []
|
||||
cache_map = {}
|
||||
managers = []
|
||||
|
||||
if args.flatpak:
|
||||
flatpak_api_cache = Cache(expiration_time=args.cache_exp)
|
||||
cache_map[FlatpakApplication] = flatpak_api_cache
|
||||
managers.append(FlatpakManager(app_args=args, api_cache=flatpak_api_cache, disk_cache=args.disk_cache, http_session=http_session, locale_keys=locale_keys))
|
||||
caches.append(flatpak_api_cache)
|
||||
|
||||
if args.disk_cache:
|
||||
Path(FLATPAK_CACHE_PATH).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if args.snap:
|
||||
snap_api_cache = Cache(expiration_time=args.cache_exp)
|
||||
cache_map[SnapApplication] = snap_api_cache
|
||||
managers.append(SnapManager(app_args=args, disk_cache=args.disk_cache, api_cache=snap_api_cache, http_session=http_session, locale_keys=locale_keys))
|
||||
caches.append(snap_api_cache)
|
||||
|
||||
if args.disk_cache:
|
||||
Path(SNAP_CACHE_PATH).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
icon_cache = Cache(expiration_time=args.icon_exp)
|
||||
caches.append(icon_cache)
|
||||
|
||||
disk_loader_factory = DiskCacheLoaderFactory(disk_cache=args.disk_cache, cache_map=cache_map)
|
||||
manager = GenericApplicationManager(managers, disk_loader_factory=disk_loader_factory, app_args=args)
|
||||
manager.prepare()
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName(__app_name__)
|
||||
app.setApplicationVersion(__version__)
|
||||
app.setWindowIcon(QIcon(resource.get_path('img/logo.svg')))
|
||||
|
||||
screen_size = app.primaryScreen().size()
|
||||
|
||||
manage_window = ManageWindow(locale_keys=locale_keys,
|
||||
manager=manager,
|
||||
icon_cache=icon_cache,
|
||||
disk_cache=args.disk_cache,
|
||||
download_icons=bool(args.download_icons),
|
||||
screen_size=screen_size,
|
||||
suggestions=args.sugs)
|
||||
|
||||
if args.tray:
|
||||
trayIcon = TrayIcon(locale_keys=locale_keys,
|
||||
manager=manager,
|
||||
manage_window=manage_window,
|
||||
check_interval=args.check_interval,
|
||||
update_notification=bool(args.update_notification))
|
||||
manage_window.tray_icon = trayIcon
|
||||
trayIcon.show()
|
||||
else:
|
||||
manage_window.refresh_apps()
|
||||
manage_window.show()
|
||||
|
||||
CacheCleaner(caches).start()
|
||||
sys.exit(app.exec_())
|
||||
1
bauh/core/__init__.py
Executable file
@@ -0,0 +1 @@
|
||||
|
||||
6
bauh/core/constants.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pathlib import Path
|
||||
|
||||
from bauh import __app_name__
|
||||
|
||||
HOME_PATH = Path.home()
|
||||
CACHE_PATH = '{}/.cache/{}'.format(HOME_PATH, __app_name__)
|
||||
335
bauh/core/controller.py
Executable file
@@ -0,0 +1,335 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from argparse import Namespace
|
||||
from threading import Thread
|
||||
from typing import List, Dict
|
||||
|
||||
from bauh.core.disk import DiskCacheLoader, DiskCacheLoaderFactory
|
||||
from bauh.core.model import Application, ApplicationUpdate
|
||||
from bauh.core.system import BauhProcess
|
||||
|
||||
|
||||
class ApplicationManager(ABC):
|
||||
|
||||
def __init__(self, app_args, locale_keys: dict):
|
||||
self.app_args = app_args
|
||||
self.locale_keys = locale_keys
|
||||
|
||||
@abstractmethod
|
||||
def search(self, word: str, disk_loader: DiskCacheLoader) -> Dict[str, List[Application]]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def read_installed(self, disk_loader: DiskCacheLoader) -> List[Application]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def downgrade_app(self, app: Application, root_password: str) -> BauhProcess:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def clean_cache_for(self, app: Application):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def can_downgrade(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def update_and_stream(self, app: Application) -> BauhProcess:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def uninstall_and_stream(self, app: Application, root_password: str) -> BauhProcess:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_app_type(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_info(self, app: Application) -> dict:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def get_history(self, app: Application) -> List[dict]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def install_and_stream(self, app: Application, root_password: str) -> BauhProcess:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def is_enabled(self) -> bool:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def cache_to_disk(self, app: Application, icon_bytes: bytes, only_icon: bool):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def requires_root(self, action: str, app: Application):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def refresh(self, app: Application, root_password: str) -> BauhProcess:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def prepare(self):
|
||||
"""
|
||||
Callback executed before the ApplicationManager starts to work.
|
||||
:return:
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def list_updates(self) -> List[ApplicationUpdate]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def list_warnings(self) -> List[str]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def list_suggestions(self, limit: int) -> List[Application]:
|
||||
pass
|
||||
|
||||
|
||||
class GenericApplicationManager(ApplicationManager):
|
||||
|
||||
def __init__(self, managers: List[ApplicationManager], disk_loader_factory: DiskCacheLoaderFactory, app_args: Namespace):
|
||||
super(ApplicationManager, self).__init__()
|
||||
self.managers = managers
|
||||
self.map = {m.get_app_type(): m for m in self.managers}
|
||||
self.disk_loader_factory = disk_loader_factory
|
||||
self._enabled_map = {} if app_args.check_packaging_once else None
|
||||
self.thread_prepare = None
|
||||
|
||||
def _wait_to_be_ready(self):
|
||||
if self.thread_prepare:
|
||||
self.thread_prepare.join()
|
||||
self.thread_prepare = None
|
||||
|
||||
def _sort(self, apps: List[Application], word: str) -> List[Application]:
|
||||
exact_name_matches, contains_name_matches, others = [], [], []
|
||||
|
||||
for app in apps:
|
||||
lower_name = app.base_data.name.lower()
|
||||
|
||||
if word == lower_name:
|
||||
exact_name_matches.append(app)
|
||||
elif word in lower_name:
|
||||
contains_name_matches.append(app)
|
||||
else:
|
||||
others.append(app)
|
||||
|
||||
res = []
|
||||
for app_list in (exact_name_matches, contains_name_matches, others):
|
||||
app_list.sort(key=lambda a: a.base_data.name.lower())
|
||||
res.extend(app_list)
|
||||
|
||||
return res
|
||||
|
||||
def _is_enabled(self, man: ApplicationManager):
|
||||
if self._enabled_map is not None:
|
||||
enabled = self._enabled_map.get(man.get_app_type())
|
||||
|
||||
if enabled is None:
|
||||
enabled = man.is_enabled()
|
||||
self._enabled_map[man.get_app_type()] = enabled
|
||||
|
||||
return enabled
|
||||
else:
|
||||
return man.is_enabled()
|
||||
|
||||
def _search(self, word: str, man: ApplicationManager, disk_loader, res: dict):
|
||||
if self._is_enabled(man):
|
||||
apps_found = man.search(word=word, disk_loader=disk_loader)
|
||||
res['installed'].extend(apps_found['installed'])
|
||||
res['new'].extend(apps_found['new'])
|
||||
|
||||
def search(self, word: str, disk_loader: DiskCacheLoader = None) -> Dict[str, List[Application]]:
|
||||
self._wait_to_be_ready()
|
||||
|
||||
res = {'installed': [], 'new': []}
|
||||
|
||||
norm_word = word.strip().lower()
|
||||
disk_loader = self.disk_loader_factory.new()
|
||||
disk_loader.start()
|
||||
|
||||
threads = []
|
||||
|
||||
for man in self.managers:
|
||||
t = Thread(target=self._search, args=(norm_word, man, disk_loader, res))
|
||||
t.start()
|
||||
threads.append(t)
|
||||
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
if disk_loader:
|
||||
disk_loader.stop = True
|
||||
disk_loader.join()
|
||||
|
||||
for key in res:
|
||||
res[key] = self._sort(res[key], norm_word)
|
||||
|
||||
return res
|
||||
|
||||
def read_installed(self, disk_loader: DiskCacheLoader = None) -> List[Application]:
|
||||
self._wait_to_be_ready()
|
||||
|
||||
installed = []
|
||||
|
||||
disk_loader = None
|
||||
|
||||
for man in self.managers:
|
||||
if self._is_enabled(man):
|
||||
if not disk_loader:
|
||||
disk_loader = self.disk_loader_factory.new()
|
||||
disk_loader.start()
|
||||
|
||||
installed.extend(man.read_installed(disk_loader=disk_loader))
|
||||
|
||||
if disk_loader:
|
||||
disk_loader.stop = True
|
||||
disk_loader.join()
|
||||
|
||||
installed.sort(key=lambda a: a.base_data.name.lower())
|
||||
|
||||
return installed
|
||||
|
||||
def can_downgrade(self):
|
||||
return True
|
||||
|
||||
def downgrade_app(self, app: Application, root_password: str) -> BauhProcess:
|
||||
man = self._get_manager_for(app)
|
||||
|
||||
if man and man.can_downgrade():
|
||||
return man.downgrade_app(app, root_password)
|
||||
else:
|
||||
raise Exception("downgrade is not possible for {}".format(app.__class__.__name__))
|
||||
|
||||
def clean_cache_for(self, app: Application):
|
||||
man = self._get_manager_for(app)
|
||||
|
||||
if man:
|
||||
return man.clean_cache_for(app)
|
||||
|
||||
def update_and_stream(self, app: Application) -> BauhProcess:
|
||||
man = self._get_manager_for(app)
|
||||
|
||||
if man:
|
||||
return man.update_and_stream(app)
|
||||
|
||||
def uninstall_and_stream(self, app: Application, root_password: str) -> BauhProcess:
|
||||
man = self._get_manager_for(app)
|
||||
|
||||
if man:
|
||||
return man.uninstall_and_stream(app, root_password)
|
||||
|
||||
def install_and_stream(self, app: Application, root_password: str) -> BauhProcess:
|
||||
man = self._get_manager_for(app)
|
||||
|
||||
if man:
|
||||
return man.install_and_stream(app, root_password)
|
||||
|
||||
def get_info(self, app: Application):
|
||||
man = self._get_manager_for(app)
|
||||
|
||||
if man:
|
||||
return man.get_info(app)
|
||||
|
||||
def get_history(self, app: Application):
|
||||
man = self._get_manager_for(app)
|
||||
|
||||
if man:
|
||||
return man.get_history(app)
|
||||
|
||||
def get_app_type(self):
|
||||
return None
|
||||
|
||||
def is_enabled(self):
|
||||
return True
|
||||
|
||||
def _get_manager_for(self, app: Application) -> ApplicationManager:
|
||||
man = self.map[app.__class__]
|
||||
return man if man and self._is_enabled(man) else None
|
||||
|
||||
def cache_to_disk(self, app: Application, icon_bytes: bytes, only_icon: bool):
|
||||
if self.disk_loader_factory.disk_cache and app.supports_disk_cache():
|
||||
man = self._get_manager_for(app)
|
||||
|
||||
if man:
|
||||
return man.cache_to_disk(app, icon_bytes=icon_bytes, only_icon=only_icon)
|
||||
|
||||
def requires_root(self, action: str, app: Application):
|
||||
man = self._get_manager_for(app)
|
||||
|
||||
if man:
|
||||
return man.requires_root(action, app)
|
||||
|
||||
def refresh(self, app: Application, root_password: str) -> BauhProcess:
|
||||
self._wait_to_be_ready()
|
||||
|
||||
man = self._get_manager_for(app)
|
||||
|
||||
if man:
|
||||
return man.refresh(app, root_password)
|
||||
|
||||
def _prepare(self):
|
||||
if self.managers:
|
||||
for man in self.managers:
|
||||
if self._is_enabled(man):
|
||||
man.prepare()
|
||||
|
||||
def prepare(self):
|
||||
self.thread_prepare = Thread(target=self._prepare)
|
||||
self.thread_prepare.start()
|
||||
|
||||
def list_updates(self) -> List[ApplicationUpdate]:
|
||||
self._wait_to_be_ready()
|
||||
|
||||
updates = []
|
||||
|
||||
if self.managers:
|
||||
for man in self.managers:
|
||||
if self._is_enabled(man):
|
||||
updates.extend(man.list_updates())
|
||||
|
||||
return updates
|
||||
|
||||
def list_warnings(self) -> List[str]:
|
||||
if self.managers:
|
||||
warnings = None
|
||||
|
||||
for man in self.managers:
|
||||
man_warnings = man.list_warnings()
|
||||
|
||||
if man_warnings:
|
||||
if warnings is None:
|
||||
warnings = []
|
||||
|
||||
warnings.extend(man_warnings)
|
||||
|
||||
return warnings
|
||||
|
||||
def _fill_suggestions(self, suggestions: list, man: ApplicationManager, limit: int):
|
||||
if self._is_enabled(man):
|
||||
man_sugs = man.list_suggestions(limit)
|
||||
|
||||
if man_sugs:
|
||||
suggestions.extend(man_sugs)
|
||||
|
||||
def list_suggestions(self, limit: int) -> List[Application]:
|
||||
if self.managers:
|
||||
suggestions, threads = [], []
|
||||
for man in self.managers:
|
||||
t = Thread(target=self._fill_suggestions, args=(suggestions, man, 6))
|
||||
t.start()
|
||||
threads.append(t)
|
||||
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
return suggestions
|
||||
75
bauh/core/disk.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from threading import Thread, Lock
|
||||
from typing import List, Dict
|
||||
|
||||
from bauh.core.model import Application
|
||||
from bauh.util.cache import Cache
|
||||
|
||||
|
||||
class DiskCacheLoader(Thread):
|
||||
|
||||
def __init__(self, enabled: bool, cache_map: Dict[type, Cache], apps: List[Application] = []):
|
||||
super(DiskCacheLoader, self).__init__(daemon=True)
|
||||
self.apps = apps
|
||||
self.stop = False
|
||||
self.lock = Lock()
|
||||
self.cache_map = cache_map
|
||||
self.enabled = enabled
|
||||
|
||||
def run(self):
|
||||
|
||||
if self.enabled:
|
||||
while True:
|
||||
if self.apps:
|
||||
self.lock.acquire()
|
||||
app = self.apps[0]
|
||||
del self.apps[0]
|
||||
self.lock.release()
|
||||
self.fill_cached_data(app)
|
||||
elif self.stop:
|
||||
break
|
||||
|
||||
def add(self, app: Application):
|
||||
if self.enabled:
|
||||
if app and app.supports_disk_cache():
|
||||
self.lock.acquire()
|
||||
self.apps.append(app)
|
||||
self.lock.release()
|
||||
|
||||
def fill_cached_data(self, app: Application):
|
||||
if self.enabled:
|
||||
if os.path.exists(app.get_disk_data_path()):
|
||||
with open(app.get_disk_data_path()) as f:
|
||||
cached_data = json.loads(f.read())
|
||||
app.fill_cached_data(cached_data)
|
||||
self.cache_map.get(app.__class__).add_non_existing(app.base_data.id, cached_data)
|
||||
|
||||
|
||||
class DiskCacheLoaderFactory:
|
||||
|
||||
def __init__(self, disk_cache: bool, cache_map: Dict[type, Cache]):
|
||||
self.disk_cache = disk_cache
|
||||
self.cache_map = cache_map
|
||||
|
||||
def new(self):
|
||||
return DiskCacheLoader(enabled=self.disk_cache, cache_map=self.cache_map)
|
||||
|
||||
|
||||
def save(app: Application, icon_bytes: bytes = None, only_icon: bool = False):
|
||||
|
||||
if app.supports_disk_cache():
|
||||
|
||||
if not only_icon:
|
||||
Path(app.get_disk_cache_path()).mkdir(parents=True, exist_ok=True)
|
||||
data = app.get_data_to_cache()
|
||||
|
||||
with open(app.get_disk_data_path(), 'w+') as f:
|
||||
f.write(json.dumps(data))
|
||||
|
||||
if icon_bytes:
|
||||
Path(app.get_disk_cache_path()).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with open(app.get_disk_icon_path(), 'wb+') as f:
|
||||
f.write(icon_bytes)
|
||||
3
bauh/core/exception.py
Normal file
@@ -0,0 +1,3 @@
|
||||
|
||||
class NoInternetException(Exception):
|
||||
pass
|
||||
0
bauh/core/flatpak/__init__.py
Normal file
5
bauh/core/flatpak/constants.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from bauh.core.constants import CACHE_PATH
|
||||
|
||||
FLATHUB_URL = 'https://flathub.org'
|
||||
FLATHUB_API_URL = FLATHUB_URL + '/api/v1'
|
||||
FLATPAK_CACHE_PATH = '{}/flatpak/installed'.format(CACHE_PATH)
|
||||
205
bauh/core/flatpak/controller.py
Normal file
@@ -0,0 +1,205 @@
|
||||
import os
|
||||
import shutil
|
||||
from argparse import Namespace
|
||||
from datetime import datetime
|
||||
from typing import List, Dict
|
||||
|
||||
from bauh.core import disk
|
||||
from bauh.core.controller import ApplicationManager
|
||||
from bauh.core.disk import DiskCacheLoader
|
||||
from bauh.core.flatpak import flatpak
|
||||
from bauh.core.flatpak.model import FlatpakApplication
|
||||
from bauh.core.flatpak.worker import FlatpakAsyncDataLoader, FlatpakUpdateLoader
|
||||
from bauh.core.model import ApplicationData, ApplicationUpdate
|
||||
from bauh.core.system import BauhProcess
|
||||
from bauh.util.cache import Cache
|
||||
|
||||
|
||||
class FlatpakManager(ApplicationManager):
|
||||
|
||||
def __init__(self, app_args: Namespace, api_cache: Cache, disk_cache: bool, http_session, locale_keys: dict):
|
||||
super(FlatpakManager, self).__init__(app_args=app_args, locale_keys=locale_keys)
|
||||
self.api_cache = api_cache
|
||||
self.http_session = http_session
|
||||
self.disk_cache = disk_cache
|
||||
|
||||
def get_app_type(self):
|
||||
return FlatpakApplication
|
||||
|
||||
def _map_to_model(self, app_json: dict, installed: bool, disk_loader: DiskCacheLoader) -> FlatpakApplication:
|
||||
|
||||
app = FlatpakApplication(arch=app_json.get('arch'),
|
||||
branch=app_json.get('branch'),
|
||||
origin=app_json.get('origin'),
|
||||
runtime=app_json.get('runtime'),
|
||||
ref=app_json.get('ref'),
|
||||
commit=app_json.get('commit'),
|
||||
base_data=ApplicationData(id=app_json.get('id'),
|
||||
name=app_json.get('name'),
|
||||
version=app_json.get('version'),
|
||||
latest_version=app_json.get('latest_version')))
|
||||
app.installed = installed
|
||||
|
||||
api_data = self.api_cache.get(app_json['id'])
|
||||
|
||||
expired_data = api_data and api_data.get('expires_at') and api_data['expires_at'] <= datetime.utcnow()
|
||||
|
||||
if not api_data or expired_data:
|
||||
if not app_json['runtime']:
|
||||
if disk_loader:
|
||||
disk_loader.add(app) # preloading cached disk data
|
||||
|
||||
FlatpakAsyncDataLoader(app=app, api_cache=self.api_cache, manager=self, http_session=self.http_session).start()
|
||||
|
||||
else:
|
||||
app.fill_cached_data(api_data)
|
||||
|
||||
return app
|
||||
|
||||
def search(self, word: str, disk_loader: DiskCacheLoader) -> Dict[str, List[FlatpakApplication]]:
|
||||
|
||||
res = {'installed': [], 'new': []}
|
||||
apps_found = flatpak.search(word)
|
||||
|
||||
if apps_found:
|
||||
already_read = set()
|
||||
installed_apps = self.read_installed(disk_loader=disk_loader)
|
||||
|
||||
if installed_apps:
|
||||
for app_found in apps_found:
|
||||
for installed_app in installed_apps:
|
||||
if app_found['id'] == installed_app.base_data.id:
|
||||
res['installed'].append(installed_app)
|
||||
already_read.add(app_found['id'])
|
||||
|
||||
if len(apps_found) > len(already_read):
|
||||
for app_found in apps_found:
|
||||
if app_found['id'] not in already_read:
|
||||
res['new'].append(self._map_to_model(app_found, False, disk_loader))
|
||||
|
||||
return res
|
||||
|
||||
def read_installed(self, disk_loader: DiskCacheLoader) -> List[FlatpakApplication]:
|
||||
installed = flatpak.list_installed()
|
||||
models = []
|
||||
|
||||
if installed:
|
||||
|
||||
available_updates = flatpak.list_updates_as_str()
|
||||
|
||||
for app_json in installed:
|
||||
model = self._map_to_model(app_json=app_json, installed=True, disk_loader=disk_loader)
|
||||
model.update = app_json['id'] in available_updates
|
||||
models.append(model)
|
||||
|
||||
return models
|
||||
|
||||
def can_downgrade(self):
|
||||
return True
|
||||
|
||||
def downgrade_app(self, app: FlatpakApplication, root_password: str) -> BauhProcess:
|
||||
|
||||
commits = flatpak.get_app_commits(app.ref, app.origin)
|
||||
|
||||
commit_idx = commits.index(app.commit)
|
||||
|
||||
# downgrade is not possible if the app current commit in the first one:
|
||||
if commit_idx == len(commits) - 1:
|
||||
return None
|
||||
|
||||
return BauhProcess(subproc=flatpak.downgrade_and_stream(app.ref, commits[commit_idx + 1], root_password),
|
||||
success_phrase='Updates complete.')
|
||||
|
||||
def clean_cache_for(self, app: FlatpakApplication):
|
||||
self.api_cache.delete(app.base_data.id)
|
||||
|
||||
if app.supports_disk_cache() and os.path.exists(app.get_disk_cache_path()):
|
||||
shutil.rmtree(app.get_disk_cache_path())
|
||||
|
||||
def update_and_stream(self, app: FlatpakApplication) -> BauhProcess:
|
||||
return BauhProcess(subproc=flatpak.update_and_stream(app.ref))
|
||||
|
||||
def uninstall_and_stream(self, app: FlatpakApplication, root_password: str = None) -> BauhProcess:
|
||||
return BauhProcess(subproc=flatpak.uninstall_and_stream(app.ref))
|
||||
|
||||
def get_info(self, app: FlatpakApplication) -> dict:
|
||||
app_info = flatpak.get_app_info_fields(app.base_data.id, app.branch)
|
||||
app_info['name'] = app.base_data.name
|
||||
app_info['type'] = 'runtime' if app.runtime else 'app'
|
||||
app_info['description'] = app.base_data.description
|
||||
return app_info
|
||||
|
||||
def get_history(self, app: FlatpakApplication) -> List[dict]:
|
||||
return flatpak.get_app_commits_data(app.ref, app.origin)
|
||||
|
||||
def install_and_stream(self, app: FlatpakApplication, root_password: str) -> BauhProcess:
|
||||
return BauhProcess(subproc=flatpak.install_and_stream(app.base_data.id, app.origin))
|
||||
|
||||
def is_enabled(self):
|
||||
return flatpak.is_installed()
|
||||
|
||||
def cache_to_disk(self, app: FlatpakApplication, icon_bytes: bytes, only_icon: bool):
|
||||
if self.disk_cache and app.supports_disk_cache():
|
||||
disk.save(app, icon_bytes, only_icon)
|
||||
|
||||
def requires_root(self, action: str, app: FlatpakApplication):
|
||||
return action == 'downgrade'
|
||||
|
||||
def refresh(self, app: FlatpakApplication, root_password: str) -> BauhProcess:
|
||||
raise Exception("'refresh' is not supported for {}".format(app.__class__.__name__))
|
||||
|
||||
def prepare(self):
|
||||
pass
|
||||
|
||||
def list_updates(self) -> List[ApplicationUpdate]:
|
||||
updates = []
|
||||
installed = flatpak.list_installed(extra_fields=False)
|
||||
|
||||
if installed:
|
||||
available_updates = flatpak.list_updates_as_str()
|
||||
|
||||
if available_updates:
|
||||
loaders = None
|
||||
|
||||
for app_json in installed:
|
||||
if app_json['id'] in available_updates:
|
||||
loader = FlatpakUpdateLoader(app=app_json, http_session=self.http_session)
|
||||
loader.start()
|
||||
|
||||
if loaders is None:
|
||||
loaders = []
|
||||
|
||||
loaders.append(loader)
|
||||
|
||||
if loaders:
|
||||
for loader in loaders:
|
||||
loader.join()
|
||||
app = loader.app
|
||||
updates.append(ApplicationUpdate(app_id='{}:{}'.format(app['id'], app['branch']),
|
||||
app_type='flatpak',
|
||||
version=app.get('version')))
|
||||
return updates
|
||||
|
||||
def list_warnings(self) -> List[str]:
|
||||
if flatpak.is_installed():
|
||||
if not flatpak.has_remotes_set():
|
||||
return [self.locale_keys['flatpak.notification.no_remotes']]
|
||||
|
||||
def list_suggestions(self, limit: int) -> List[FlatpakApplication]:
|
||||
|
||||
res = []
|
||||
|
||||
if limit != 0:
|
||||
|
||||
for app_id in ('com.spotify.Client', 'com.skype.Client', 'com.dropbox.Client', 'us.zoom.Zoom', 'com.visualstudio.code', 'org.telegram.desktop', 'org.inkscape.Inkscape', 'org.libretro.RetroArch', 'org.kde.kdenlive', 'org.videolan.VLC'):
|
||||
|
||||
app_json = flatpak.search(app_id, app_id=True)
|
||||
|
||||
if app_json:
|
||||
res.append(self._map_to_model(app_json[0], False, None))
|
||||
|
||||
if len(res) == limit:
|
||||
break
|
||||
|
||||
return res
|
||||
|
||||
245
bauh/core/flatpak/flatpak.py
Executable file
@@ -0,0 +1,245 @@
|
||||
import re
|
||||
import subprocess
|
||||
from typing import List
|
||||
|
||||
from bauh.core import system
|
||||
from bauh.core.exception import NoInternetException
|
||||
|
||||
BASE_CMD = 'flatpak'
|
||||
|
||||
|
||||
def app_str_to_json(line: str, version: str, extra_fields: bool = True) -> dict:
|
||||
|
||||
app_array = line.split('\t')
|
||||
|
||||
if version >= '1.3.0':
|
||||
app = {'name': app_array[0],
|
||||
'id': app_array[1],
|
||||
'version': app_array[2],
|
||||
'branch': app_array[3]}
|
||||
|
||||
elif '1.0' <= version < '1.1':
|
||||
|
||||
ref_data = app_array[0].split('/')
|
||||
|
||||
app = {'id': ref_data[0],
|
||||
'arch': ref_data[1],
|
||||
'name': ref_data[0].split('.')[-1],
|
||||
'ref': app_array[0],
|
||||
'options': app_array[1],
|
||||
'branch': ref_data[2],
|
||||
'version': None}
|
||||
elif '1.2' <= version < '1.3':
|
||||
app = {'id': app_array[1],
|
||||
'name': app_array[1].strip().split('.')[-1],
|
||||
'version': app_array[2],
|
||||
'branch': app_array[3],
|
||||
'arch': app_array[4],
|
||||
'origin': app_array[5]}
|
||||
else:
|
||||
raise Exception('Unsupported version')
|
||||
|
||||
if extra_fields:
|
||||
extra_fields = get_app_info_fields(app['id'], app['branch'], ['origin', 'arch', 'ref', 'commit'], check_runtime=True)
|
||||
app.update(extra_fields)
|
||||
|
||||
return app
|
||||
|
||||
|
||||
def get_app_info_fields(app_id: str, branch: str, fields: List[str] = [], check_runtime: bool = False):
|
||||
info = re.findall(r'\w+:\s.+', get_app_info(app_id, branch))
|
||||
data = {}
|
||||
fields_to_retrieve = len(fields) + (1 if check_runtime and 'ref' not in fields else 0)
|
||||
|
||||
for field in info:
|
||||
|
||||
if fields and fields_to_retrieve == 0:
|
||||
break
|
||||
|
||||
field_val = field.split(':')
|
||||
field_name = field_val[0].lower()
|
||||
|
||||
if not fields or field_name in fields or (check_runtime and field_name == 'ref'):
|
||||
data[field_name] = field_val[1].strip()
|
||||
|
||||
if fields:
|
||||
fields_to_retrieve -= 1
|
||||
|
||||
if check_runtime and field_name == 'ref':
|
||||
data['runtime'] = data['ref'].startswith('runtime/')
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def is_installed():
|
||||
version = get_version()
|
||||
return False if version is None else True
|
||||
|
||||
|
||||
def get_version():
|
||||
res = system.run_cmd('{} --version'.format(BASE_CMD), print_error=False)
|
||||
return res.split(' ')[1].strip() if res else None
|
||||
|
||||
|
||||
def get_app_info(app_id: str, branch: str):
|
||||
return system.run_cmd('{} info {} {}'.format(BASE_CMD, app_id, branch))
|
||||
|
||||
|
||||
def list_installed(extra_fields: bool = True) -> List[dict]:
|
||||
apps_str = system.run_cmd('{} list'.format(BASE_CMD))
|
||||
|
||||
if apps_str:
|
||||
version = get_version()
|
||||
app_lines = apps_str.split('\n')
|
||||
return [app_str_to_json(line, version, extra_fields=extra_fields) for line in app_lines if line]
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def update_and_stream(app_ref: str):
|
||||
"""
|
||||
Updates the app reference and streams Flatpak output,
|
||||
:param app_ref:
|
||||
:return:
|
||||
"""
|
||||
return system.cmd_to_subprocess([BASE_CMD, 'update', '-y', app_ref])
|
||||
|
||||
|
||||
def uninstall_and_stream(app_ref: str):
|
||||
"""
|
||||
Removes the app by its reference
|
||||
:param app_ref:
|
||||
:return:
|
||||
"""
|
||||
return system.cmd_to_subprocess([BASE_CMD, 'uninstall', app_ref, '-y'])
|
||||
|
||||
|
||||
def list_updates_as_str():
|
||||
return system.run_cmd('{} update'.format(BASE_CMD), ignore_return_code=True)
|
||||
|
||||
|
||||
def downgrade_and_stream(app_ref: str, commit: str, root_password: str) -> subprocess.Popen:
|
||||
return system.cmd_as_root([BASE_CMD, 'update', '--commit={}'.format(commit), app_ref, '-y'], root_password)
|
||||
|
||||
|
||||
def get_app_commits(app_ref: str, origin: str) -> List[str]:
|
||||
log = system.run_cmd('{} remote-info --log {} {}'.format(BASE_CMD, origin, app_ref))
|
||||
|
||||
if log:
|
||||
return re.findall(r'Commit+:\s(.+)', log)
|
||||
else:
|
||||
raise NoInternetException()
|
||||
|
||||
|
||||
def get_app_commits_data(app_ref: str, origin: str) -> List[dict]:
|
||||
log = system.run_cmd('{} remote-info --log {} {}'.format(BASE_CMD, origin, app_ref))
|
||||
|
||||
if not log:
|
||||
raise NoInternetException()
|
||||
|
||||
res = re.findall(r'(Commit|Subject|Date):\s(.+)', log)
|
||||
|
||||
commits = []
|
||||
|
||||
commit = {}
|
||||
|
||||
for idx, data in enumerate(res):
|
||||
commit[data[0].strip().lower()] = data[1].strip()
|
||||
|
||||
if (idx + 1) % 3 == 0:
|
||||
commits.append(commit)
|
||||
commit = {}
|
||||
|
||||
return commits
|
||||
|
||||
|
||||
def search(word: str, app_id: bool = False) -> List[dict]:
|
||||
cli_version = get_version()
|
||||
|
||||
res = system.run_cmd('{} search {}'.format(BASE_CMD, word))
|
||||
|
||||
found = []
|
||||
|
||||
split_res = res.split('\n')
|
||||
|
||||
if split_res and split_res[0].lower() != 'no matches found':
|
||||
for info in split_res:
|
||||
if info:
|
||||
info_list = info.split('\t')
|
||||
if cli_version >= '1.3.0':
|
||||
id_ = info_list[2].strip()
|
||||
|
||||
if app_id and id_ != word:
|
||||
continue
|
||||
|
||||
version = info_list[3].strip()
|
||||
app = {
|
||||
'name': info_list[0].strip(),
|
||||
'description': info_list[1].strip(),
|
||||
'id': id_,
|
||||
'version': version,
|
||||
'latest_version': version,
|
||||
'branch': info_list[4].strip(),
|
||||
'origin': info_list[5].strip(),
|
||||
'runtime': False,
|
||||
'arch': None, # unknown at this moment,
|
||||
'ref': None # unknown at this moment
|
||||
}
|
||||
elif cli_version >= '1.2.0':
|
||||
id_ = info_list[1].strip()
|
||||
|
||||
if app_id and id_ != word:
|
||||
continue
|
||||
|
||||
desc = info_list[0].split('-')
|
||||
version = info_list[2].strip()
|
||||
app = {
|
||||
'name': desc[0].strip(),
|
||||
'description': desc[1].strip(),
|
||||
'id': id_,
|
||||
'version': version,
|
||||
'latest_version': version,
|
||||
'branch': info_list[3].strip(),
|
||||
'origin': info_list[4].strip(),
|
||||
'runtime': False,
|
||||
'arch': None, # unknown at this moment,
|
||||
'ref': None # unknown at this moment
|
||||
}
|
||||
else:
|
||||
id_ = info_list[0].strip()
|
||||
|
||||
if app_id and id_ != word:
|
||||
continue
|
||||
|
||||
version = info_list[1].strip()
|
||||
app = {
|
||||
'name': '',
|
||||
'description': info_list[4].strip(),
|
||||
'id': id_,
|
||||
'version': version,
|
||||
'latest_version': version,
|
||||
'branch': info_list[2].strip(),
|
||||
'origin': info_list[3].strip(),
|
||||
'runtime': False,
|
||||
'arch': None, # unknown at this moment,
|
||||
'ref': None # unknown at this moment
|
||||
}
|
||||
|
||||
found.append(app)
|
||||
|
||||
if app_id and len(found) > 0:
|
||||
break
|
||||
|
||||
return found
|
||||
|
||||
|
||||
def install_and_stream(app_id: str, origin: str):
|
||||
return system.cmd_to_subprocess([BASE_CMD, 'install', origin, app_id, '-y'])
|
||||
|
||||
|
||||
def set_default_remotes():
|
||||
system.run_cmd('flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo')
|
||||
|
||||
|
||||
def has_remotes_set() -> bool:
|
||||
return bool(system.run_cmd('{} remotes'.format(BASE_CMD)).strip())
|
||||
62
bauh/core/flatpak/model.py
Normal file
@@ -0,0 +1,62 @@
|
||||
from bauh.core import resource
|
||||
from bauh.core.flatpak.constants import FLATPAK_CACHE_PATH
|
||||
from bauh.core.model import Application, ApplicationData
|
||||
|
||||
|
||||
class FlatpakApplication(Application):
|
||||
|
||||
def __init__(self, base_data: ApplicationData, branch: str, arch: str, origin: str, runtime: bool, ref: str, commit: str):
|
||||
super(FlatpakApplication, self).__init__(base_data=base_data)
|
||||
self.ref = ref
|
||||
self.branch = branch
|
||||
self.arch = arch
|
||||
self.origin = origin
|
||||
self.runtime = runtime
|
||||
self.commit = commit
|
||||
|
||||
def is_incomplete(self):
|
||||
return self.base_data.description is None and self.base_data.icon_url
|
||||
|
||||
def has_history(self):
|
||||
return self.installed
|
||||
|
||||
def has_info(self):
|
||||
return self.installed
|
||||
|
||||
def can_be_downgraded(self):
|
||||
return self.installed
|
||||
|
||||
def can_be_uninstalled(self):
|
||||
return self.installed
|
||||
|
||||
def can_be_installed(self):
|
||||
return not self.installed
|
||||
|
||||
def get_type(self):
|
||||
return 'flatpak'
|
||||
|
||||
def can_be_refreshed(self):
|
||||
return False
|
||||
|
||||
def get_default_icon_path(self):
|
||||
return resource.get_path('img/flathub.svg')
|
||||
|
||||
def is_library(self):
|
||||
return self.runtime
|
||||
|
||||
def get_disk_cache_path(self):
|
||||
return '{}/{}'.format(FLATPAK_CACHE_PATH, self.base_data.id)
|
||||
|
||||
def get_data_to_cache(self):
|
||||
return {
|
||||
'description': self.base_data.description,
|
||||
'icon_url': self.base_data.icon_url,
|
||||
'latest_version': self.base_data.latest_version,
|
||||
'version': self.base_data.version,
|
||||
'name': self.base_data.name
|
||||
}
|
||||
|
||||
def fill_cached_data(self, data: dict):
|
||||
for attr in self.get_data_to_cache().keys():
|
||||
if not getattr(self.base_data, attr):
|
||||
setattr(self.base_data, attr, data[attr])
|
||||
120
bauh/core/flatpak/worker.py
Normal file
@@ -0,0 +1,120 @@
|
||||
import time
|
||||
import traceback
|
||||
from threading import Thread
|
||||
|
||||
from colorama import Fore
|
||||
|
||||
from bauh.core.controller import ApplicationManager
|
||||
from bauh.core.flatpak import flatpak
|
||||
from bauh.core.flatpak.constants import FLATHUB_API_URL, FLATHUB_URL
|
||||
from bauh.core.flatpak.model import FlatpakApplication
|
||||
from bauh.core.model import ApplicationStatus
|
||||
from bauh.core.worker import AsyncDataLoader
|
||||
from bauh.util.cache import Cache
|
||||
|
||||
|
||||
class FlatpakAsyncDataLoader(AsyncDataLoader):
|
||||
|
||||
def __init__(self, app: FlatpakApplication, manager: ApplicationManager, http_session, api_cache: Cache, attempts: int = 2, timeout: int = 30):
|
||||
super(FlatpakAsyncDataLoader, self).__init__(app=app)
|
||||
self.manager = manager
|
||||
self.http_session = http_session
|
||||
self.attempts = attempts
|
||||
self.api_cache = api_cache
|
||||
self.persist = False
|
||||
self.timeout = timeout
|
||||
|
||||
def run(self):
|
||||
if self.app:
|
||||
self.app.status = ApplicationStatus.LOADING_DATA
|
||||
|
||||
for _ in range(0, self.attempts):
|
||||
try:
|
||||
res = self.http_session.get('{}/apps/{}'.format(FLATHUB_API_URL, self.app.base_data.id), timeout=self.timeout)
|
||||
|
||||
if res.status_code == 200 and res.text:
|
||||
data = res.json()
|
||||
|
||||
if not self.app.base_data.version:
|
||||
self.app.base_data.version = data.get('version')
|
||||
|
||||
if not self.app.base_data.name:
|
||||
self.app.base_data.name = data.get('name')
|
||||
|
||||
self.app.base_data.description = data.get('description', data.get('summary', None))
|
||||
self.app.base_data.icon_url = data.get('iconMobileUrl', None)
|
||||
self.app.base_data.latest_version = data.get('currentReleaseVersion', self.app.base_data.version)
|
||||
|
||||
if not self.app.base_data.version and self.app.base_data.latest_version:
|
||||
self.app.base_data.version = self.app.base_data.latest_version
|
||||
|
||||
if not self.app.installed and self.app.base_data.latest_version:
|
||||
self.app.base_data.version = self.app.base_data.latest_version
|
||||
|
||||
if self.app.base_data.icon_url and self.app.base_data.icon_url.startswith('/'):
|
||||
self.app.base_data.icon_url = FLATHUB_URL + self.app.base_data.icon_url
|
||||
|
||||
loaded_data = self.app.get_data_to_cache()
|
||||
|
||||
self.api_cache.add(self.app.base_data.id, loaded_data)
|
||||
self.app.status = ApplicationStatus.READY
|
||||
self.persist = self.app.supports_disk_cache()
|
||||
break
|
||||
else:
|
||||
self.log_msg("Could not retrieve app data for id '{}'. Server response: {}. Body: {}".format(
|
||||
self.app.base_data.id, res.status_code, res.content.decode()), Fore.RED)
|
||||
except:
|
||||
self.log_msg("Could not retrieve app data for id '{}'".format(self.app.base_data.id), Fore.YELLOW)
|
||||
traceback.print_exc()
|
||||
time.sleep(0.5)
|
||||
|
||||
self.app.status = ApplicationStatus.READY
|
||||
|
||||
if self.persist:
|
||||
self.manager.cache_to_disk(app=self.app, icon_bytes=None, only_icon=False)
|
||||
|
||||
def clone(self) -> "FlatpakAsyncDataLoader":
|
||||
return FlatpakAsyncDataLoader(manager=self.manager,
|
||||
api_cache=self.api_cache,
|
||||
attempts=self.attempts,
|
||||
http_session=self.http_session,
|
||||
timeout=self.timeout,
|
||||
app=self.app)
|
||||
|
||||
|
||||
class FlatpakUpdateLoader(Thread):
|
||||
|
||||
def __init__(self, app: dict, http_session, attempts: int = 2, timeout: int = 20):
|
||||
super(FlatpakUpdateLoader, self).__init__(daemon=True)
|
||||
self.app = app
|
||||
self.http_session = http_session
|
||||
self.attempts = attempts
|
||||
self.timeout = timeout
|
||||
|
||||
def run(self):
|
||||
|
||||
if self.app.get('ref') is None:
|
||||
self.app.update(flatpak.get_app_info_fields(self.app['id'], self.app['branch'], fields=['ref'], check_runtime=True))
|
||||
else:
|
||||
self.app['runtime'] = self.app['ref'].startswith('runtime/')
|
||||
|
||||
if not self.app['runtime']:
|
||||
current_attempts = 0
|
||||
|
||||
while current_attempts < self.attempts:
|
||||
|
||||
current_attempts += 1
|
||||
|
||||
try:
|
||||
res = self.http_session.get('{}/apps/{}'.format(FLATHUB_API_URL, self.app['id']), timeout=self.timeout)
|
||||
|
||||
if res.status_code == 200 and res.text:
|
||||
data = res.json()
|
||||
|
||||
if data.get('currentReleaseVersion'):
|
||||
self.app['version'] = data['currentReleaseVersion']
|
||||
|
||||
break
|
||||
except:
|
||||
traceback.print_exc()
|
||||
time.sleep(0.5)
|
||||
99
bauh/core/model.py
Normal file
@@ -0,0 +1,99 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from enum import Enum
|
||||
|
||||
from bauh.core import resource
|
||||
|
||||
|
||||
class ApplicationStatus(Enum):
|
||||
READY = 1
|
||||
LOADING_DATA = 2
|
||||
|
||||
|
||||
class ApplicationData:
|
||||
|
||||
def __init__(self, id: str, version: str, name: str = None, description: str = None, latest_version: str = None, icon_url: str = None):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.version = version
|
||||
self.description = description
|
||||
self.latest_version = latest_version
|
||||
self.icon_url = icon_url
|
||||
|
||||
|
||||
class Application(ABC):
|
||||
|
||||
def __init__(self, base_data: ApplicationData, status: ApplicationStatus = ApplicationStatus.READY, installed: bool = False, update: bool = False):
|
||||
self.base_data = base_data
|
||||
self.status = status
|
||||
self.installed = installed
|
||||
self.update = update
|
||||
|
||||
@abstractmethod
|
||||
def has_history(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def has_info(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def can_be_downgraded(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def can_be_uninstalled(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def can_be_installed(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def can_be_refreshed(self):
|
||||
return self.installed
|
||||
|
||||
@abstractmethod
|
||||
def get_type(self):
|
||||
pass
|
||||
|
||||
def get_default_icon_path(self):
|
||||
return resource.get_path('img/logo.svg')
|
||||
|
||||
@abstractmethod
|
||||
def is_library(self):
|
||||
pass
|
||||
|
||||
def supports_disk_cache(self):
|
||||
return self.installed and not self.is_library()
|
||||
|
||||
@abstractmethod
|
||||
def get_disk_cache_path(self):
|
||||
pass
|
||||
|
||||
def get_disk_icon_path(self):
|
||||
return '{}/icon.png'.format(self.get_disk_cache_path())
|
||||
|
||||
def get_disk_data_path(self):
|
||||
return '{}/data.json'.format(self.get_disk_cache_path())
|
||||
|
||||
@abstractmethod
|
||||
def get_data_to_cache(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def fill_cached_data(self, data: dict):
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return '{} (id={}, name={})'.format(self.__class__.__name__, self.base_data.id, self.base_data.name)
|
||||
|
||||
|
||||
class ApplicationUpdate:
|
||||
|
||||
def __init__(self, app_id: str, version: str, app_type: str):
|
||||
self.id = app_id
|
||||
self.version = version
|
||||
self.type = app_type
|
||||
|
||||
def __str__(self):
|
||||
return '{} (id={}, type={}, new_version={})'.format(self.__class__.__name__, self.id, self.type, self.type)
|
||||
6
bauh/core/resource.py
Executable file
@@ -0,0 +1,6 @@
|
||||
|
||||
from bauh import ROOT_DIR
|
||||
|
||||
|
||||
def get_path(resource_path):
|
||||
return ROOT_DIR + '/resources/' + resource_path
|
||||
0
bauh/core/snap/__init__.py
Normal file
4
bauh/core/snap/constants.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from bauh.core.constants import CACHE_PATH
|
||||
|
||||
SNAP_API_URL = 'https://search.apps.ubuntu.com/api/v1'
|
||||
SNAP_CACHE_PATH = '{}/snap/installed'.format(CACHE_PATH)
|
||||
154
bauh/core/snap/controller.py
Normal file
@@ -0,0 +1,154 @@
|
||||
import os
|
||||
import shutil
|
||||
from argparse import Namespace
|
||||
from datetime import datetime
|
||||
from typing import Dict, List
|
||||
|
||||
from bauh.core import disk
|
||||
from bauh.core.controller import ApplicationManager
|
||||
from bauh.core.disk import DiskCacheLoader
|
||||
from bauh.core.model import ApplicationData, Application, ApplicationUpdate
|
||||
from bauh.core.snap import snap
|
||||
from bauh.core.snap.model import SnapApplication
|
||||
from bauh.core.snap.worker import SnapAsyncDataLoader
|
||||
from bauh.core.system import BauhProcess
|
||||
from bauh.util.cache import Cache
|
||||
|
||||
|
||||
class SnapManager(ApplicationManager):
|
||||
|
||||
def __init__(self, app_args: Namespace, api_cache: Cache, disk_cache: bool, http_session, locale_keys: dict):
|
||||
super(SnapManager, self).__init__(app_args=app_args, locale_keys=locale_keys)
|
||||
self.api_cache = api_cache
|
||||
self.http_session = http_session
|
||||
self.disk_cache = disk_cache
|
||||
|
||||
def map_json(self, app_json: dict, installed: bool, disk_loader: DiskCacheLoader) -> SnapApplication:
|
||||
app = SnapApplication(publisher=app_json.get('publisher'),
|
||||
rev=app_json.get('rev'),
|
||||
notes=app_json.get('notes'),
|
||||
app_type=app_json.get('type'),
|
||||
base_data=ApplicationData(id=app_json.get('name'),
|
||||
name=app_json.get('name'),
|
||||
version=app_json.get('version'),
|
||||
latest_version=app_json.get('version'),
|
||||
description=app_json.get('description')
|
||||
))
|
||||
|
||||
if app.publisher:
|
||||
app.publisher = app.publisher.replace('*', '')
|
||||
|
||||
app.installed = installed
|
||||
|
||||
api_data = self.api_cache.get(app_json['name'])
|
||||
expired_data = api_data and api_data.get('expires_at') and api_data['expires_at'] <= datetime.utcnow()
|
||||
|
||||
if (not api_data or expired_data) and not app.is_library():
|
||||
if disk_loader and app.installed:
|
||||
disk_loader.add(app)
|
||||
|
||||
SnapAsyncDataLoader(app=app, api_cache=self.api_cache, manager=self, http_session=self.http_session, download_icons=self.app_args.download_icons).start()
|
||||
else:
|
||||
app.fill_cached_data(api_data)
|
||||
|
||||
return app
|
||||
|
||||
def search(self, word: str, disk_loader: DiskCacheLoader) -> Dict[str, List[SnapApplication]]:
|
||||
installed = self.read_installed(disk_loader)
|
||||
|
||||
res = {'installed': [], 'new': []}
|
||||
|
||||
for app_json in snap.search(word):
|
||||
|
||||
already_installed = None
|
||||
|
||||
if installed:
|
||||
already_installed = [i for i in installed if i.base_data.id == app_json.get('name')]
|
||||
already_installed = already_installed[0] if already_installed else None
|
||||
|
||||
if already_installed:
|
||||
res['installed'].append(already_installed)
|
||||
else:
|
||||
res['new'].append(self.map_json(app_json, installed=False, disk_loader=disk_loader))
|
||||
|
||||
return res
|
||||
|
||||
def read_installed(self, disk_loader: DiskCacheLoader) -> List[SnapApplication]:
|
||||
return [self.map_json(app_json, installed=True, disk_loader=disk_loader) for app_json in snap.read_installed()]
|
||||
|
||||
def downgrade_app(self, app: Application, root_password: str) -> BauhProcess:
|
||||
return BauhProcess(subproc=snap.downgrade_and_stream(app.base_data.name, root_password), wrong_error_phrase=None)
|
||||
|
||||
def clean_cache_for(self, app: SnapApplication):
|
||||
self.api_cache.delete(app.base_data.name)
|
||||
|
||||
if app.supports_disk_cache() and os.path.exists(app.get_disk_cache_path()):
|
||||
shutil.rmtree(app.get_disk_cache_path())
|
||||
|
||||
def can_downgrade(self):
|
||||
return True
|
||||
|
||||
def update_and_stream(self, app: SnapApplication) -> BauhProcess:
|
||||
pass
|
||||
|
||||
def uninstall_and_stream(self, app: SnapApplication, root_password: str) -> BauhProcess:
|
||||
return BauhProcess(subproc=snap.uninstall_and_stream(app.base_data.name, root_password))
|
||||
|
||||
def get_app_type(self):
|
||||
return SnapApplication
|
||||
|
||||
def get_info(self, app: SnapApplication) -> dict:
|
||||
info = snap.get_info(app.base_data.name, attrs=('license', 'contact', 'commands', 'snap-id', 'tracking', 'installed'))
|
||||
info['description'] = app.base_data.description
|
||||
info['publisher'] = app.publisher
|
||||
info['revision'] = app.rev
|
||||
info['name'] = app.base_data.name
|
||||
|
||||
if info.get('commands'):
|
||||
info['commands'] = ' '.join(info['commands'])
|
||||
|
||||
return info
|
||||
|
||||
def get_history(self, app: Application) -> List[dict]:
|
||||
return []
|
||||
|
||||
def install_and_stream(self, app: SnapApplication, root_password: str) -> BauhProcess:
|
||||
return BauhProcess(subproc=snap.install_and_stream(app.base_data.name, app.confinement, root_password))
|
||||
|
||||
def is_enabled(self) -> bool:
|
||||
return snap.is_installed()
|
||||
|
||||
def cache_to_disk(self, app: Application, icon_bytes: bytes, only_icon: bool):
|
||||
if self.disk_cache and app.supports_disk_cache():
|
||||
disk.save(app, icon_bytes, only_icon)
|
||||
|
||||
def requires_root(self, action: str, app: SnapApplication):
|
||||
return action != 'search'
|
||||
|
||||
def refresh(self, app: SnapApplication, root_password: str) -> BauhProcess:
|
||||
return BauhProcess(subproc=snap.refresh_and_stream(app.base_data.name, root_password))
|
||||
|
||||
def prepare(self):
|
||||
pass
|
||||
|
||||
def list_updates(self) -> List[ApplicationUpdate]:
|
||||
return []
|
||||
|
||||
def list_warnings(self) -> List[str]:
|
||||
if snap.get_snapd_version() == 'unavailable':
|
||||
return [self.locale_keys['snap.notification.snapd_unavailable']]
|
||||
|
||||
def list_suggestions(self, limit: int) -> List[SnapApplication]:
|
||||
|
||||
suggestions = []
|
||||
|
||||
if limit != 0:
|
||||
for name in ('whatsdesk', 'slack', 'yakyak', 'instagraph', 'pycharm-professional', 'eclipse', 'gimp', 'supertuxkart'):
|
||||
res = snap.search(name, exact_name=True)
|
||||
if res:
|
||||
suggestions.append(self.map_json(res[0], installed=False, disk_loader=None))
|
||||
|
||||
if len(suggestions) == limit:
|
||||
break
|
||||
|
||||
return suggestions
|
||||
60
bauh/core/snap/model.py
Normal file
@@ -0,0 +1,60 @@
|
||||
from bauh.core import resource
|
||||
from bauh.core.model import Application, ApplicationData
|
||||
from bauh.core.snap.constants import SNAP_CACHE_PATH
|
||||
|
||||
|
||||
class SnapApplication(Application):
|
||||
|
||||
def __init__(self, base_data: ApplicationData, publisher: str, rev: str, notes: str, app_type: str, confinement: str = None):
|
||||
super(SnapApplication, self).__init__(base_data=base_data)
|
||||
self.publisher = publisher
|
||||
self.rev = rev
|
||||
self.notes = notes
|
||||
self.type = app_type
|
||||
self.confinement = confinement
|
||||
|
||||
def has_history(self):
|
||||
return False
|
||||
|
||||
def has_info(self):
|
||||
return True
|
||||
|
||||
def can_be_downgraded(self):
|
||||
return self.installed
|
||||
|
||||
def can_be_uninstalled(self):
|
||||
return self.installed
|
||||
|
||||
def can_be_installed(self):
|
||||
return not self.installed
|
||||
|
||||
def can_be_refreshed(self):
|
||||
return self.installed
|
||||
|
||||
def get_type(self):
|
||||
return 'snap'
|
||||
|
||||
def get_default_icon_path(self):
|
||||
return resource.get_path('img/snapcraft.png')
|
||||
|
||||
def is_library(self):
|
||||
return self.type in ('core', 'base', 'snapd') or self.base_data.name.startswith('gtk-') or self.base_data.name.startswith('gnome-')
|
||||
|
||||
def get_disk_cache_path(self):
|
||||
return '{}/{}'.format(SNAP_CACHE_PATH, self.base_data.name)
|
||||
|
||||
def get_data_to_cache(self):
|
||||
return {
|
||||
"icon_url": self.base_data.icon_url,
|
||||
'confinement': self.confinement,
|
||||
'description': self.base_data.description
|
||||
}
|
||||
|
||||
def fill_cached_data(self, data: dict):
|
||||
if data:
|
||||
for base_attr in ('icon_url', 'description'):
|
||||
if data.get(base_attr):
|
||||
setattr(self.base_data, base_attr, data[base_attr])
|
||||
|
||||
if data.get('confinement'):
|
||||
self.confinement = data['confinement']
|
||||
141
bauh/core/snap/snap.py
Normal file
@@ -0,0 +1,141 @@
|
||||
import re
|
||||
import subprocess
|
||||
from typing import List
|
||||
|
||||
from bauh.core import system
|
||||
|
||||
BASE_CMD = 'snap'
|
||||
|
||||
|
||||
def is_installed():
|
||||
version = get_snapd_version()
|
||||
return False if version is None or version == 'unavailable' else True
|
||||
|
||||
|
||||
def get_version():
|
||||
res = system.run_cmd('{} --version'.format(BASE_CMD), print_error=False)
|
||||
return res.split('\n')[0].split(' ')[-1].strip() if res else None
|
||||
|
||||
|
||||
def get_snapd_version():
|
||||
res = system.run_cmd('{} --version'.format(BASE_CMD), print_error=False)
|
||||
|
||||
if not res:
|
||||
return None
|
||||
else:
|
||||
lines = res.split('\n')
|
||||
|
||||
if lines and len(lines) >= 2:
|
||||
version = lines[1].split(' ')[-1].strip()
|
||||
return version.lower() if version else None
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def app_str_to_json(app: str) -> dict:
|
||||
app_data = [word for word in app.split(' ') if word]
|
||||
app_json = {
|
||||
'name': app_data[0],
|
||||
'version': app_data[1],
|
||||
'rev': app_data[2],
|
||||
'tracking': app_data[3],
|
||||
'publisher': app_data[4] if len(app_data) >= 5 else None,
|
||||
'notes': app_data[5] if len(app_data) >= 6 else None
|
||||
}
|
||||
|
||||
app_json.update(get_info(app_json['name'], ('summary', 'type', 'description')))
|
||||
return app_json
|
||||
|
||||
|
||||
def get_info(app_name: str, attrs: tuple = None):
|
||||
full_info_lines = system.run_cmd('{} info {}'.format(BASE_CMD, app_name))
|
||||
|
||||
data = {}
|
||||
|
||||
if full_info_lines:
|
||||
re_attrs = r'\w+' if not attrs else '|'.join(attrs)
|
||||
info_map = re.findall(r'({}):\s+(.+)'.format(re_attrs), full_info_lines)
|
||||
|
||||
for info in info_map:
|
||||
data[info[0]] = info[1].strip()
|
||||
|
||||
if not attrs or 'description' in attrs:
|
||||
desc = re.findall(r'\|\n+((\s+.+\n+)+)', full_info_lines)
|
||||
data['description'] = ''.join([w.strip() for w in desc[0][0].strip().split('\n')]).replace('.', '.\n') if desc else None
|
||||
|
||||
if not attrs or 'commands' in attrs:
|
||||
commands = re.findall(r'commands:\s*\n*((\s+-\s.+\s*\n)+)', full_info_lines)
|
||||
data['commands'] = commands[0][0].replace('-', '').strip().split('\n') if commands else None
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def read_installed() -> List[dict]:
|
||||
res = system.run_cmd('{} list'.format(BASE_CMD), print_error=False)
|
||||
|
||||
apps = []
|
||||
|
||||
if res and len(res) > 0:
|
||||
lines = res.split('\n')
|
||||
|
||||
if not lines[0].startswith('error'):
|
||||
for idx, app_str in enumerate(lines):
|
||||
if idx > 0 and app_str:
|
||||
apps.append(app_str_to_json(app_str))
|
||||
|
||||
return apps
|
||||
|
||||
|
||||
def search(word: str, exact_name: bool = False) -> List[dict]:
|
||||
apps = []
|
||||
|
||||
res = system.run_cmd('{} find "{}"'.format(BASE_CMD, word), print_error=False)
|
||||
|
||||
if res:
|
||||
res = res.split('\n')
|
||||
|
||||
if not res[0].startswith('No matching'):
|
||||
for idx, app_str in enumerate(res):
|
||||
if idx > 0 and app_str:
|
||||
app_data = [word for word in app_str.split(' ') if word]
|
||||
|
||||
if exact_name and app_data[0] != word:
|
||||
continue
|
||||
|
||||
apps.append({
|
||||
'name': app_data[0],
|
||||
'version': app_data[1],
|
||||
'publisher': app_data[2],
|
||||
'notes': app_data[3] if app_data[3] != '-' else None,
|
||||
'summary': app_data[4] if len(app_data) == 5 else '',
|
||||
'rev': None,
|
||||
'tracking': None,
|
||||
'type': None
|
||||
})
|
||||
|
||||
if exact_name and len(apps) > 0:
|
||||
break
|
||||
|
||||
return apps
|
||||
|
||||
|
||||
def uninstall_and_stream(app_name: str, root_password: str):
|
||||
return system.cmd_as_root([BASE_CMD, 'remove', app_name], root_password)
|
||||
|
||||
|
||||
def install_and_stream(app_name: str, confinement: str, root_password: str) -> subprocess.Popen:
|
||||
|
||||
install_cmd = [BASE_CMD, 'install', app_name] # default
|
||||
|
||||
if confinement == 'classic':
|
||||
install_cmd.append('--classic')
|
||||
|
||||
return system.cmd_as_root(install_cmd, root_password)
|
||||
|
||||
|
||||
def downgrade_and_stream(app_name: str, root_password: str) -> subprocess.Popen:
|
||||
return system.cmd_as_root([BASE_CMD, 'revert', app_name], root_password)
|
||||
|
||||
|
||||
def refresh_and_stream(app_name: str, root_password: str) -> subprocess.Popen:
|
||||
return system.cmd_as_root([BASE_CMD, 'refresh', app_name], root_password)
|
||||
85
bauh/core/snap/worker.py
Normal file
@@ -0,0 +1,85 @@
|
||||
import time
|
||||
import traceback
|
||||
|
||||
from colorama import Fore
|
||||
|
||||
from bauh.core.controller import ApplicationManager
|
||||
from bauh.core.model import ApplicationStatus
|
||||
from bauh.core.snap import snap
|
||||
from bauh.core.snap.constants import SNAP_API_URL
|
||||
from bauh.core.snap.model import SnapApplication
|
||||
from bauh.core.worker import AsyncDataLoader
|
||||
from bauh.util.cache import Cache
|
||||
|
||||
|
||||
class SnapAsyncDataLoader(AsyncDataLoader):
|
||||
|
||||
def __init__(self, app: SnapApplication, manager: ApplicationManager, http_session, api_cache: Cache, download_icons: bool, attempts: int = 2, timeout: int = 30):
|
||||
super(SnapAsyncDataLoader, self).__init__(app=app)
|
||||
self.manager = manager
|
||||
self.http_session = http_session
|
||||
self.attempts = attempts
|
||||
self.api_cache = api_cache
|
||||
self.timeout = timeout
|
||||
self.persist = False
|
||||
self.download_icons = download_icons
|
||||
|
||||
def run(self):
|
||||
if self.app:
|
||||
self.app.status = ApplicationStatus.LOADING_DATA
|
||||
|
||||
for _ in range(0, self.attempts):
|
||||
try:
|
||||
res = self.http_session.get('{}/search?q={}'.format(SNAP_API_URL, self.app.base_data.name), timeout=self.timeout)
|
||||
|
||||
if res.status_code == 200 and res.text:
|
||||
|
||||
try:
|
||||
snap_list = res.json()['_embedded']['clickindex:package']
|
||||
except:
|
||||
self.log_msg('Snap API response responded differently from expected for app: {}'.format(self.app.base_data.name))
|
||||
break
|
||||
|
||||
if not snap_list:
|
||||
break
|
||||
|
||||
snap_data = snap_list[0]
|
||||
|
||||
api_data = {
|
||||
'confinement': snap_data.get('confinement'),
|
||||
'description': snap_data.get('description'),
|
||||
'icon_url': snap_data.get('icon_url') if self.download_icons else None
|
||||
}
|
||||
|
||||
self.api_cache.add(self.app.base_data.id, api_data)
|
||||
self.app.confinement = api_data['confinement']
|
||||
self.app.base_data.icon_url = api_data['icon_url']
|
||||
|
||||
if not api_data.get('description'):
|
||||
api_data['description'] = snap.get_info(self.app.base_data.name, ('description',)).get('description')
|
||||
|
||||
self.app.base_data.description = api_data['description']
|
||||
|
||||
self.app.status = ApplicationStatus.READY
|
||||
self.persist = self.app.supports_disk_cache()
|
||||
break
|
||||
else:
|
||||
self.log_msg("Could not retrieve app data for id '{}'. Server response: {}. Body: {}".format(self.app.base_data.id, res.status_code, res.content.decode()), Fore.RED)
|
||||
except:
|
||||
self.log_msg("Could not retrieve app data for id '{}'".format(self.app.base_data.id), Fore.YELLOW)
|
||||
traceback.print_exc()
|
||||
time.sleep(0.5)
|
||||
|
||||
self.app.status = ApplicationStatus.READY
|
||||
|
||||
if self.persist:
|
||||
self.manager.cache_to_disk(app=self.app, icon_bytes=None, only_icon=False)
|
||||
|
||||
def clone(self) -> "SnapAsyncDataLoader":
|
||||
return SnapAsyncDataLoader(manager=self.manager,
|
||||
api_cache=self.api_cache,
|
||||
attempts=self.attempts,
|
||||
http_session=self.http_session,
|
||||
timeout=self.timeout,
|
||||
app=self.app)
|
||||
|
||||
51
bauh/core/system.py
Normal file
@@ -0,0 +1,51 @@
|
||||
import os
|
||||
import subprocess
|
||||
from typing import List
|
||||
|
||||
from bauh import __app_name__
|
||||
from bauh.core import resource
|
||||
|
||||
|
||||
class BauhProcess:
|
||||
|
||||
def __init__(self, subproc: subprocess.Popen, success_phrase: str = None, wrong_error_phrase: str = '[sudo] password for'):
|
||||
self.subproc = subproc
|
||||
self.success_pgrase = success_phrase
|
||||
self.wrong_error_phrase = wrong_error_phrase
|
||||
|
||||
|
||||
def run_cmd(cmd: str, expected_code: int = 0, ignore_return_code: bool = False, print_error: bool = True) -> str:
|
||||
args = {
|
||||
"shell": True,
|
||||
"stdout": subprocess.PIPE,
|
||||
"env": {'LANG': 'en'}
|
||||
}
|
||||
|
||||
if not print_error:
|
||||
args["stderr"] = subprocess.DEVNULL
|
||||
|
||||
res = subprocess.run(cmd, **args)
|
||||
return res.stdout.decode() if ignore_return_code or res.returncode == expected_code else None
|
||||
|
||||
|
||||
def stream_cmd(cmd: List[str]):
|
||||
return cmd_to_subprocess(cmd).stdout
|
||||
|
||||
|
||||
def cmd_to_subprocess(cmd: List[str]):
|
||||
return subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env={'LANG': 'en'})
|
||||
|
||||
|
||||
def notify_user(msg: str, icon_path: str = resource.get_path('img/logo.svg')):
|
||||
os.system("notify-send -a {} {} '{}'".format(__app_name__, "-i {}".format(icon_path) if icon_path else '', msg))
|
||||
|
||||
|
||||
def cmd_as_root(cmd: List[str], root_password: str) -> subprocess.Popen:
|
||||
pwdin, final_cmd = None, []
|
||||
|
||||
if root_password is not None:
|
||||
final_cmd.extend(['sudo', '-S'])
|
||||
pwdin = stream_cmd(['echo', root_password])
|
||||
|
||||
final_cmd.extend(cmd)
|
||||
return subprocess.Popen(final_cmd, stdin=pwdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
31
bauh/core/worker.py
Normal file
@@ -0,0 +1,31 @@
|
||||
from io import StringIO
|
||||
from threading import Thread
|
||||
|
||||
from colorama import Fore
|
||||
|
||||
from bauh.core.model import Application
|
||||
|
||||
|
||||
class AsyncDataLoader(Thread):
|
||||
|
||||
def __init__(self, app: Application):
|
||||
super(AsyncDataLoader, self).__init__(daemon=True)
|
||||
self.id_ = '{}#{}'.format(self.__class__.__name__, id(self))
|
||||
self.app = app
|
||||
|
||||
def log_msg(self, msg: str, color: int = None):
|
||||
final_msg = StringIO()
|
||||
|
||||
if color:
|
||||
final_msg.write(str(color))
|
||||
|
||||
final_msg.write('[{}] '.format(self.id_))
|
||||
|
||||
final_msg.write(msg)
|
||||
|
||||
if color:
|
||||
final_msg.write(Fore.RESET)
|
||||
|
||||
final_msg.seek(0)
|
||||
|
||||
print(final_msg.read())
|
||||
142
bauh/resources/img/about.svg
Executable file
@@ -0,0 +1,142 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="23.99629"
|
||||
height="24.016451"
|
||||
viewBox="0 0 23.99629 24.016451"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="about.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"><metadata
|
||||
id="metadata933"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs931"><linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient1618"><stop
|
||||
style="stop-color:#0088aa;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1614" /><stop
|
||||
style="stop-color:#0088aa;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop1616" /></linearGradient>
|
||||
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1618"
|
||||
id="linearGradient1620"
|
||||
x1="-22.25"
|
||||
y1="-4.4919429"
|
||||
x2="-28.25"
|
||||
y2="9.7580566"
|
||||
gradientUnits="userSpaceOnUse" /></defs><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="739"
|
||||
id="namedview929"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="0.50000001"
|
||||
inkscape:cx="-197.75436"
|
||||
inkscape:cy="-20.194417"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="432"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Capa_1" />
|
||||
|
||||
<g
|
||||
id="g898"
|
||||
transform="translate(239.5083,-10.347359)">
|
||||
</g>
|
||||
<g
|
||||
id="g900"
|
||||
transform="translate(239.5083,-10.347359)">
|
||||
</g>
|
||||
<g
|
||||
id="g902"
|
||||
transform="translate(239.5083,-10.347359)">
|
||||
</g>
|
||||
<g
|
||||
id="g904"
|
||||
transform="translate(239.5083,-10.347359)">
|
||||
</g>
|
||||
<g
|
||||
id="g906"
|
||||
transform="translate(239.5083,-10.347359)">
|
||||
</g>
|
||||
<g
|
||||
id="g908"
|
||||
transform="translate(239.5083,-10.347359)">
|
||||
</g>
|
||||
<g
|
||||
id="g910"
|
||||
transform="translate(239.5083,-10.347359)">
|
||||
</g>
|
||||
<g
|
||||
id="g912"
|
||||
transform="translate(239.5083,-10.347359)">
|
||||
</g>
|
||||
<g
|
||||
id="g914"
|
||||
transform="translate(239.5083,-10.347359)">
|
||||
</g>
|
||||
<g
|
||||
id="g916"
|
||||
transform="translate(239.5083,-10.347359)">
|
||||
</g>
|
||||
<g
|
||||
id="g918"
|
||||
transform="translate(239.5083,-10.347359)">
|
||||
</g>
|
||||
<g
|
||||
id="g920"
|
||||
transform="translate(239.5083,-10.347359)">
|
||||
</g>
|
||||
<g
|
||||
id="g922"
|
||||
transform="translate(239.5083,-10.347359)">
|
||||
</g>
|
||||
<g
|
||||
id="g924"
|
||||
transform="translate(239.5083,-10.347359)">
|
||||
</g>
|
||||
<g
|
||||
id="g926"
|
||||
transform="translate(239.5083,-10.347359)">
|
||||
</g>
|
||||
<g
|
||||
id="g1624"
|
||||
transform="matrix(1.4998882,0,0,1.5002743,1.245458e-5,-1.4011516e-5)"><ellipse
|
||||
ry="7.9742656"
|
||||
rx="7.969605"
|
||||
cy="8.0040293"
|
||||
cx="7.999351"
|
||||
id="path939"
|
||||
style="fill:url(#linearGradient1620);fill-opacity:1;stroke:#4d4d4d;stroke-width:0.05950872" /><path
|
||||
d="m 7.8854557,3.7948064 c 1.4552891,0 2.8880403,0.6706192 2.8880403,2.2746822 0,1.479228 -1.6940807,2.0480998 -2.057903,2.5826085 C 8.442458,9.04969 8.5336818,9.6083603 7.7832313,9.6083603 c -0.4888527,0 -0.7276446,-0.3978613 -0.7276446,-0.7618964 0,-1.3546614 1.9892172,-1.6612455 1.9892172,-2.7767069 0,-0.6139735 -0.408361,-0.9780086 -1.0909303,-0.9780086 -1.4552891,0 -0.887018,1.5012419 -1.9892171,1.5012419 -0.3978971,0 -0.73945,-0.2389316 -0.73945,-0.6937069 -2.683e-4,-1.1157299 1.2728414,-2.104477 2.6602492,-2.104477 z M 7.8288432,10.3469 c 0.5108538,0 0.9323617,0.420413 0.9323617,0.933176 0,0.512763 -0.420703,0.933175 -0.9323617,0.933175 -0.5116586,0 -0.9326299,-0.419875 -0.9326299,-0.933175 0,-0.512495 0.4209713,-0.933176 0.9326299,-0.933176 z"
|
||||
id="path894"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff;stroke-width:0.26838395" /></g></svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
126
bauh/resources/img/app_info.svg
Executable file
@@ -0,0 +1,126 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 10.048382 24.046875"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="info3.svg"
|
||||
width="10.048382"
|
||||
height="24.046875"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"><metadata
|
||||
id="metadata856"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs854" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="739"
|
||||
id="namedview852"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="4.2578125"
|
||||
inkscape:cx="72.471146"
|
||||
inkscape:cy="14.340367"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="432"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" />
|
||||
<g
|
||||
id="g819"
|
||||
transform="matrix(0.04838117,0,0,0.046875,-7.3613885,0.0234375)"
|
||||
style="fill:#ffffff;stroke:#333333">
|
||||
<circle
|
||||
style="fill:#ffffff;stroke:#333333"
|
||||
cx="255.99899"
|
||||
cy="75.469002"
|
||||
r="75.469002"
|
||||
id="circle815" />
|
||||
<path
|
||||
style="fill:#ffffff;stroke:#333333"
|
||||
d="M 359.345,230.952 V 185.078 H 152.654 v 45.874 c 15.395,0 27.874,12.479 27.874,27.873 v 179.426 c 0,15.394 -12.48,27.874 -27.874,27.874 V 512 h 206.692 v -45.873 c -15.395,0 -27.874,-12.48 -27.874,-27.874 V 258.825 c -10e-4,-15.394 12.479,-27.873 27.873,-27.873 z"
|
||||
id="path817"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="g821"
|
||||
transform="translate(-152.62981,-487.97656)">
|
||||
</g>
|
||||
<g
|
||||
id="g823"
|
||||
transform="translate(-152.62981,-487.97656)">
|
||||
</g>
|
||||
<g
|
||||
id="g825"
|
||||
transform="translate(-152.62981,-487.97656)">
|
||||
</g>
|
||||
<g
|
||||
id="g827"
|
||||
transform="translate(-152.62981,-487.97656)">
|
||||
</g>
|
||||
<g
|
||||
id="g829"
|
||||
transform="translate(-152.62981,-487.97656)">
|
||||
</g>
|
||||
<g
|
||||
id="g831"
|
||||
transform="translate(-152.62981,-487.97656)">
|
||||
</g>
|
||||
<g
|
||||
id="g833"
|
||||
transform="translate(-152.62981,-487.97656)">
|
||||
</g>
|
||||
<g
|
||||
id="g835"
|
||||
transform="translate(-152.62981,-487.97656)">
|
||||
</g>
|
||||
<g
|
||||
id="g837"
|
||||
transform="translate(-152.62981,-487.97656)">
|
||||
</g>
|
||||
<g
|
||||
id="g839"
|
||||
transform="translate(-152.62981,-487.97656)">
|
||||
</g>
|
||||
<g
|
||||
id="g841"
|
||||
transform="translate(-152.62981,-487.97656)">
|
||||
</g>
|
||||
<g
|
||||
id="g843"
|
||||
transform="translate(-152.62981,-487.97656)">
|
||||
</g>
|
||||
<g
|
||||
id="g845"
|
||||
transform="translate(-152.62981,-487.97656)">
|
||||
</g>
|
||||
<g
|
||||
id="g847"
|
||||
transform="translate(-152.62981,-487.97656)">
|
||||
</g>
|
||||
<g
|
||||
id="g849"
|
||||
transform="translate(-152.62981,-487.97656)">
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
63
bauh/resources/img/app_settings.svg
Executable file
@@ -0,0 +1,63 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="svg1069"
|
||||
width="23.995125"
|
||||
height="24.004246"
|
||||
viewBox="0 0 23.995125 24.004246"
|
||||
sodipodi:docname="settings_3svg.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14">
|
||||
<metadata
|
||||
id="metadata1075">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs1073" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="739"
|
||||
id="namedview1071"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="3.6875"
|
||||
inkscape:cx="-9.476931"
|
||||
inkscape:cy="-2.2075679"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1069" />
|
||||
<path
|
||||
style="opacity:0.95;fill:#ffffff;fill-opacity:1;stroke:#333333;stroke-width:0.18215948;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.037667,0.09769312 c -0.72043,0.0637 -1.845694,-0.17097 -2.124115,0.73052 -0.08137,0.50962998 0.01515,1.11913998 -0.387221,1.48686998 -0.15736,0.21026 -0.218698,0.60361 -0.5623,0.63599 -0.231156,0.1825 -0.482289,0.4504 -0.805519,0.37943 -0.33519,0.2264 -0.769547,0.14283 -1.104717,-0.0131 -0.637006,0.0746 -1.03032,-0.57076 -1.534985,-0.83449 -0.885643,-0.28862 -1.407803,0.72336 -2.066783,1.10551 -0.25976,0.24133 -0.32452,0.57765 -0.62658,0.79417 -0.43543,0.45528 -0.40057,1.2532 0.17952,1.56982 0.43169,0.5212 0.62511,1.299 0.50516,1.9344 -0.16048,0.28253 -0.0479,0.67728 -0.35903,0.91801 -0.26574,0.43697 -0.71175,0.56803 -1.12615,0.76234 -0.49764,0.39004 -1.38447003,0.0415 -1.77627003,0.5985699 -0.25861,0.96381 -0.12396,2.0252 -0.10444,3.02506 0.0509,0.34442 0.16638,0.76134 0.54628,0.8123 0.39988003,0.23671 0.91536003,0.015 1.32806003,0.27835 1.07706,0.33161 1.66943,1.62912 1.41067,2.66993 -0.16334,0.19954 -0.0658,0.46544 -0.29147,0.64159 -0.17812,0.26318 -0.30377,0.55768 -0.54204,0.80265 -0.47979,0.64849 0.33827,1.30054 0.7343,1.74015 0.54098,0.48923 1.03633,1.14605 1.70793,1.42206 0.23833,-0.12983 0.561824,-0.003 0.682754,-0.29128 0.406567,-0.31367 0.790035,-0.72877 1.343883,-0.71643 0.670515,-0.32887 1.506075,0.0717 2.058292,0.51022 0.156853,0.14124 0.119739,0.39965 0.359038,0.4728 0.497527,0.61075 0.0069,1.60837 0.705143,2.08371 0.874823,0.43838 1.919873,0.26337 2.864969,0.24421 0.659557,0.0141 1.164474,-0.57496 1.060705,-1.22434 0.136064,-0.91849 0.774338,-1.93921 1.77666,-2.0214 0.363739,-0.22429 0.812774,-0.0589 1.166487,0.0473 0.759153,0.0202 1.164413,1.0138 1.994591,0.87519 0.4616,-0.16783 0.782731,-0.57119 1.168225,-0.87538 0.50432,-0.48329 1.01909,-0.97579 1.28636,-1.62094 0.144599,-0.71118 -0.730744,-1.06353 -0.890067,-1.70137 -0.100989,-0.22757 -0.0049,-0.49343 -0.179905,-0.69117 -0.04673,-0.84613 0.412368,-1.65862 1.11958,-2.10125 0.305264,0.0339 0.411929,-0.37354 0.763246,-0.3152 0.591382,0.0245 1.440793,-0.10369 1.514329,-0.83661 0.05748,-1.0154 0.169703,-2.08796 -0.111765,-3.07367 -0.246291,-0.5252799 -0.897473,-0.31832 -1.285587,-0.5717499 -0.232806,0.0157 -0.388499,-0.16382 -0.634686,-0.13137 -0.42064,-0.20941 -0.754834,-0.56503 -0.952802,-0.96218 -0.424334,-0.53524 -0.424871,-1.41725 -0.12354,-1.9942 0.231967,-0.27004 0.338997,-0.6292 0.631791,-0.88078 0.499512,-0.69929 -0.151216,-1.47883 -0.657434,-1.94804 -0.499489,-0.48119 -0.952777,-1.06089 -1.592345,-1.33975 -0.659633,-0.16168 -0.98362,0.57179 -1.534211,0.74575 -0.125378,0.13168 -0.358502,0.0285 -0.45652,0.20544 -0.334935,-0.0571 -0.579653,0.23864 -0.92597,0.071 -0.681696,-0.0739 -1.347245,-0.54339 -1.630535,-1.13561 -0.102377,-0.27247 -0.348146,-0.49327 -0.331433,-0.83892 0.03397,-0.5178 -0.09363,-1.33088998 -0.748962,-1.35222998 -0.449202,-0.1603 -0.970143,-0.0669 -1.440591,-0.0918 z m 7.358542,2.90314998 c 0.09152,-0.13657 0.180927,-0.13633 0,0 z m -7.380163,5.3821 c 0.391366,-0.0266 0.744155,0.13023 1.08889,0.2282 0.620271,0.0714 1.143014,0.59122 1.609108,0.96643 0.332946,0.16294 0.165699,0.5772199 0.500916,0.7067799 0.221257,0.23884 0.09768,0.58093 0.319466,0.79533 0.07607,1.0594 0.150917,2.20776 -0.611329,3.06518 -0.47404,0.62985 -1.178494,1.1764 -1.950002,1.33815 -0.35881,3e-5 -0.689909,0.20436 -1.078465,0.1302 -0.307061,0.0394 -0.57734,-0.14573 -0.847792,-0.16049 -0.201682,-0.11921 -0.435966,-0.0458 -0.596853,-0.26311 -0.569432,-0.0889 -0.972318,-0.61227 -1.328054,-1.03395 -0.01774,-0.31189 -0.432129,-0.39167 -0.438374,-0.75366 -0.338183,-0.85301 -0.406908,-1.899 0.01158,-2.72529 0.246346,-0.36263 0.317564,-0.8099699 0.679663,-1.1234499 0.674731,-0.73941 1.661428,-1.11848 2.641244,-1.17032 z m -1.846149,6.8379199 c 6.6e-4,-0.004 0.01304,-0.006 0,0 z m -0.08223,0.13213 c -0.0079,-0.0194 0.04552,-0.0747 0,0 z m 1.435381,0.35725 c 0.0025,0.0119 -0.0022,-0.007 0,0 z m -8.415191,2.52892 c -0.003,0.003 -0.0212,-0.009 0,0 z m 17.973515,1.20852 c 0.08741,0.13502 -0.194278,-0.16337 0,0 z m -5.019582,0.98456 c -0.01137,0.0188 0.01702,-0.0484 0,0 z"
|
||||
id="path1626"
|
||||
inkscape:connector-curvature="0" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
76
bauh/resources/img/app_update.svg
Executable file
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
viewBox="0 0 48.000008 47.999886"
|
||||
enable-background="new 0 0 26 26"
|
||||
id="svg8"
|
||||
sodipodi:docname="update_white.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||
width="48.000008"
|
||||
height="47.999886">
|
||||
<metadata
|
||||
id="metadata14">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs12" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="739"
|
||||
id="namedview10"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
inkscape:zoom="3.2091769"
|
||||
inkscape:cx="11.492348"
|
||||
inkscape:cy="-1.3206439"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="432"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg8"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<g
|
||||
id="g6"
|
||||
transform="matrix(1.8424663,0,0,1.9956679,0.04794249,-1.943739)"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.05204165">
|
||||
<path
|
||||
d="m 25,17 h -2 c -0.6,0 -1,0.4 -1,1 v 2.5 C 22,20.8 21.8,21 21.5,21 H 4.5 C 4.2,21 4,20.8 4,20.5 V 18 C 4,17.4 3.6,17 3,17 H 1 c -0.6,0 -1,0.4 -1,1 v 6 c 0,0.6 0.4,1 1,1 h 24 c 0.6,0 1,-0.4 1,-1 v -6 c 0,-0.6 -0.4,-1 -1,-1 z"
|
||||
id="path2"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.05204165" />
|
||||
<path
|
||||
d="m 12.3,16.7 c 0.2,0.2 0.5,0.3 0.7,0.3 0.2,0 0.5,-0.1 0.7,-0.3 l 6,-6 C 19.9,10.5 20,10.3 20,10 20,9.7 19.9,9.5 19.7,9.3 L 18.3,7.9 C 18.1,7.7 17.9,7.6 17.6,7.6 c -0.3,0 -0.5,0.1 -0.7,0.3 l -1,1 C 15.6,9.2 15,9 15,8.5 V 2 C 15,1.4 14.6,1 14,1 H 12 C 11.4,1 11,1.4 11,2 V 8.6 C 11,9 10.5,9.3 10.1,9 L 9.1,8 C 8.9,7.8 8.7,7.7 8.4,7.7 8.1,7.7 7.9,7.8 7.7,8 L 6.3,9.4 C 6.1,9.6 6,9.8 6,10.1 c 0,0.3 0.1,0.5 0.3,0.7 z"
|
||||
id="path4"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.05204165" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(3.7795276,0,0,3.7795276,668.44512,-480.86859)"
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
62
bauh/resources/img/checked.svg
Executable file
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
viewBox="0 0 11.999997 12"
|
||||
enable-background="new 0 0 26 26"
|
||||
id="svg817"
|
||||
sodipodi:docname="checked.svg"
|
||||
width="11.999997"
|
||||
height="12"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<metadata
|
||||
id="metadata823">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs821" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="703"
|
||||
id="namedview819"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="4.7692308"
|
||||
inkscape:cx="-42.8741"
|
||||
inkscape:cy="25.96043"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="260"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg817" />
|
||||
<path
|
||||
d="M 0.13846154,6.6 C 0.04615385,6.48 0,6.3 0,6.18 0,6.06 0.04615385,5.88 0.13846154,5.76 L 0.78461536,4.92 c 0.1846154,-0.24 0.46153844,-0.24 0.64615384,0 l 0.046153,0.06 2.5384614,3.54 c 0.092308,0.12 0.2307693,0.12 0.323077,0 L 10.523077,0.18 h 0.04615 v 0 c 0.184616,-0.24 0.461539,-0.24 0.646154,0 l 0.646154,0.84 c 0.184616,0.24 0.184616,0.6 0,0.84 v 0 L 4.4769229,11.82 C 4.3846154,11.94 4.2923076,12 4.1538462,12 4.0153844,12 3.923077,11.94 3.8307691,11.82 L 0.23076922,6.78 Z"
|
||||
id="path815"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00ff00;stroke-width:0.52623481" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
72
bauh/resources/img/downgrade.svg
Executable file
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
viewBox="0 0 24 24"
|
||||
enable-background="new 0 0 26 26"
|
||||
id="svg8"
|
||||
sodipodi:docname="downgrade.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
width="24"
|
||||
height="24">
|
||||
<metadata
|
||||
id="metadata14">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs12" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="703"
|
||||
id="namedview10"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
inkscape:zoom="4.5384616"
|
||||
inkscape:cx="-20.270304"
|
||||
inkscape:cy="12.13426"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="260"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg8"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<g
|
||||
id="g6"
|
||||
transform="matrix(0.92307692,0,0,1,0,-1)"
|
||||
style="fill:#ff0000">
|
||||
<path
|
||||
d="m 25,17 h -2 c -0.6,0 -1,0.4 -1,1 v 2.5 C 22,20.8 21.8,21 21.5,21 H 4.5 C 4.2,21 4,20.8 4,20.5 V 18 C 4,17.4 3.6,17 3,17 H 1 c -0.6,0 -1,0.4 -1,1 v 6 c 0,0.6 0.4,1 1,1 h 24 c 0.6,0 1,-0.4 1,-1 v -6 c 0,-0.6 -0.4,-1 -1,-1 z"
|
||||
id="path2"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ff0000" />
|
||||
<path
|
||||
d="m 12.3,16.7 c 0.2,0.2 0.5,0.3 0.7,0.3 0.2,0 0.5,-0.1 0.7,-0.3 l 6,-6 C 19.9,10.5 20,10.3 20,10 20,9.7 19.9,9.5 19.7,9.3 L 18.3,7.9 C 18.1,7.7 17.9,7.6 17.6,7.6 c -0.3,0 -0.5,0.1 -0.7,0.3 l -1,1 C 15.6,9.2 15,9 15,8.5 V 2 C 15,1.4 14.6,1 14,1 H 12 C 11.4,1 11,1.4 11,2 V 8.6 C 11,9 10.5,9.3 10.1,9 L 9.1,8 C 8.9,7.8 8.7,7.7 8.4,7.7 8.1,7.7 7.9,7.8 7.7,8 L 6.3,9.4 C 6.1,9.6 6,9.8 6,10.1 c 0,0.3 0.1,0.5 0.3,0.7 z"
|
||||
id="path4"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ff0000" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
144
bauh/resources/img/exclamation.svg
Executable file
@@ -0,0 +1,144 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 24 24"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="exclamation2.svg"
|
||||
width="24"
|
||||
height="24"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"><metadata
|
||||
id="metadata949"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs947"><linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient955"><stop
|
||||
style="stop-color:#d4aa00;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop951" /><stop
|
||||
style="stop-color:#d4aa00;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop953" /></linearGradient><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient955"
|
||||
id="linearGradient957"
|
||||
x1="-5.0336413"
|
||||
y1="-1.8872991"
|
||||
x2="-22.291842"
|
||||
y2="17.528175"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient955"
|
||||
id="linearGradient1210"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-5.0336413"
|
||||
y1="-1.8872991"
|
||||
x2="-22.291842"
|
||||
y2="17.528175" /></defs><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="739"
|
||||
id="namedview945"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
inkscape:zoom="16"
|
||||
inkscape:cx="3.6611245"
|
||||
inkscape:cy="14.576667"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="260"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Capa_1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
|
||||
<g
|
||||
id="g914">
|
||||
</g>
|
||||
<g
|
||||
id="g916">
|
||||
</g>
|
||||
<g
|
||||
id="g918">
|
||||
</g>
|
||||
<g
|
||||
id="g920">
|
||||
</g>
|
||||
<g
|
||||
id="g922">
|
||||
</g>
|
||||
<g
|
||||
id="g924">
|
||||
</g>
|
||||
<g
|
||||
id="g926">
|
||||
</g>
|
||||
<g
|
||||
id="g928">
|
||||
</g>
|
||||
<g
|
||||
id="g930">
|
||||
</g>
|
||||
<g
|
||||
id="g932">
|
||||
</g>
|
||||
<g
|
||||
id="g934">
|
||||
</g>
|
||||
<g
|
||||
id="g936">
|
||||
</g>
|
||||
<g
|
||||
id="g938">
|
||||
</g>
|
||||
<g
|
||||
id="g940">
|
||||
</g>
|
||||
<g
|
||||
id="g942">
|
||||
</g>
|
||||
<g
|
||||
id="g1217"
|
||||
transform="translate(10.429681,16.353516)"><g
|
||||
transform="translate(-10.429681,-16.353516)"
|
||||
style="fill:url(#linearGradient957);fill-opacity:1"
|
||||
id="g912">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path910"
|
||||
d="M 12,0 C 5.373,0 0,5.373 0,12 0,18.627 5.373,24 12,24 18.627,24 24,18.627 24,12 24,5.373 18.627,0 12,0 Z m 0,19.66 c -0.938,0 -1.58,-0.723 -1.58,-1.66 0,-0.964 0.669,-1.66 1.58,-1.66 0.963,0 1.58,0.696 1.58,1.66 0,0.938 -0.617,1.66 -1.58,1.66 z m 0.622,-6.339 c -0.239,0.815 -0.992,0.829 -1.243,0 -0.289,-0.956 -1.316,-4.585 -1.316,-6.942 0,-3.11 3.891,-3.125 3.891,0 -0.001,2.371 -1.083,6.094 -1.332,6.942 z"
|
||||
style="fill:url(#linearGradient1210);fill-opacity:1" />
|
||||
</g><path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path1198"
|
||||
d="m 1.3648508,-2.489113 c -0.250891,-0.124189 -0.327144,-0.271469 -0.55064598,-1.063559 -0.664465,-2.354862 -1.04293,-4.2896531 -1.147198,-5.8647013 -0.0767,-1.1585747 0.09689,-1.7887227 0.643137,-2.3346987 0.4799,-0.479662 1.07478698,-0.648108 1.71268798,-0.48496 0.564701,0.144426 0.973861,0.498107 1.24114,1.072852 0.194111,0.41741 0.235782,0.660945 0.231159,1.3509478 -0.0094,1.4036771 -0.421836,3.6124492 -1.172557,6.2796362 -0.174505,0.619988 -0.250011,0.795473 -0.406612,0.94501 -0.186151,0.177754 -0.337702,0.205108 -0.551111,0.09947 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.03125" /><path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path1200"
|
||||
d="m 1.5625068,-12.296875 c -0.133701,-4.9e-5 -0.257218,0.01288 -0.375,0.03711 -0.01713,0.0034 -0.04774,0.0019 -0.0625,0.0059 -0.04019,0.01083 -0.07595,0.03503 -0.115235,0.04883 -0.07496,0.02544 -0.14937098,0.05778 -0.22070298,0.0957 -0.0693,0.03572 -0.136178,0.07245 -0.201172,0.117187 -0.395912,0.272524 -0.712194,0.695841 -0.861328,1.210938 -0.06573,0.22717 -0.07016,0.309243 -0.06836,0.9374996 2.45e-4,0.086621 0.01357,0.1776107 0.01563,0.2636719 0.0078,0.2791513 0.0217,0.567286 0.05469,0.8886719 0.02567,0.2619407 0.07197,0.5785227 0.117188,0.8730468 0.02898,0.1904904 0.0596,0.3838721 0.0957,0.5859376 0.02975,0.1646911 0.04058,0.2865674 0.07617,0.4667968 0.02253,0.1140684 0.06671,0.2792068 0.09375,0.40625 0.08083,0.3833354 0.176637,0.7912044 0.279296,1.2089844 0.246077,1.01458 0.501767,1.987896 0.61914098,2.261719 0.03892,0.0908 0.09578,0.1787 0.160156,0.253906 0.0136,0.01632 0.0326,0.02725 0.04687,0.04102 0.04125,0.0418 0.08219,0.08717 0.125,0.113281 0.06319,0.03853 0.132973,0.05138 0.205078,0.05273 0.05796,0.0011 0.116036,-0.008 0.173829,-0.0293 0.0075,-0.0027 0.01406,-0.0086 0.02148,-0.01172 0.05163,-0.02164 0.101553,-0.04945 0.148437,-0.08789 0.0063,-0.0052 0.01142,-0.01207 0.01758,-0.01758 0.03276,-0.02895 0.06728,-0.05485 0.0957,-0.0918 0.0047,-0.0061 0.01259,-0.03336 0.01758,-0.04102 0.0052,-0.0076 0.01256,-0.01166 0.01758,-0.01953 0.0081,-0.01277 0.02602,-0.07759 0.03516,-0.0957 0.06526,-0.134622 0.143353,-0.353611 0.236328,-0.652344 0.0061,-0.02016 0.01142,-0.03393 0.01758,-0.05469 0.08547,-0.278648 0.179338,-0.659826 0.277344,-1.03711 0.07187,-0.280521 0.135865,-0.545447 0.207032,-0.847656 0.07372,-0.310588 0.144002,-0.546795 0.216796,-0.882812 0.07915,-0.3653757 0.116018,-0.6348283 0.173828,-0.9511722 0.04874,-0.2610293 0.108943,-0.5530308 0.140626,-0.7675782 10e-4,-0.00687 9.46e-4,-0.012662 0.002,-0.019531 0.07245,-0.4988018 0.120186,-0.9630243 0.136718,-1.3847656 0.004,-0.069734 0.01867,-0.1529162 0.02149,-0.2207032 0.01305,-0.3147671 -0.006,-0.5821898 -0.04102,-0.8300778 -0.0308,-0.279799 -0.08807,-0.50458 -0.222656,-0.773438 -0.01126,-0.02248 -0.0271,-0.0406 -0.03906,-0.0625 -0.02477,-0.04414 -0.05401,-0.08682 -0.08203,-0.128906 -0.06991,-0.10808 -0.147597,-0.206648 -0.234375,-0.296875 -0.0099,-0.01034 -0.01524,-0.02297 -0.02539,-0.0332 -0.0097,-0.0098 -0.02143,-0.01589 -0.03125,-0.02539 -0.06451,-0.06161 -0.13129,-0.118543 -0.203124,-0.169922 -0.03372,-0.02443 -0.06852,-0.0466 -0.103516,-0.06836 -0.06898,-0.04263 -0.138762,-0.08215 -0.212891,-0.115234 -0.03375,-0.01482 -0.06876,-0.02643 -0.103515,-0.03906 -0.05821,-0.02181 -0.113151,-0.0506 -0.173828,-0.06641 -0.01977,-0.0052 -0.057,-0.0035 -0.08008,-0.0078 -0.124433,-0.02492 -0.253549,-0.03901 -0.388671,-0.03906 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.03125" /><path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path1205"
|
||||
d="m 1.5800848,0.015625 c -0.0178,0 -0.03152,0.0036 -0.04883,0.0039 -0.08654,0.002 -0.170637,0.0024 -0.25586,0.01758 -0.13064,0.01899 -0.248148,0.05582 -0.35742198,0.105469 -0.01495,0.0066 -0.03215,0.0066 -0.04687,0.01367 -0.01917,0.0092 -0.03902,0.03191 -0.05859,0.04297 -0.08499,0.04925 -0.168809,0.110204 -0.25,0.183594 -0.07098,0.06223 -0.123346,0.120203 -0.183594,0.191406 -0.04461,0.05354 -0.08918,0.107632 -0.125,0.164063 -0.01932,0.0304 -0.05333,0.06019 -0.06836,0.08984 -0.02046,0.04038 -0.03184,0.09021 -0.04883,0.134765 -0.007,0.01713 -0.01317,0.03525 -0.01953,0.05273 -0.02632,0.07746 -0.04829,0.155268 -0.06445,0.240234 -0.0059,0.03085 -0.0093,0.06368 -0.01367,0.0957 -0.0137,0.09626 -0.02276,0.191001 -0.02344,0.289063 -1.3e-5,0.0034 0,0.0063 0,0.0098 0,0.0038 0.0019,0.0078 0.002,0.01172 4.07e-4,0.136442 0.01441,0.266964 0.04101,0.392578 0.0035,0.01807 0.0021,0.04995 0.0059,0.06445 0.0034,0.01335 0.01187,0.02392 0.01563,0.03711 0.0062,0.0218 0.0066,0.04736 0.01367,0.06836 0.02672,0.07959 0.06612,0.147112 0.101562,0.21875 0.01461,0.02886 0.02478,0.06019 0.04102,0.08789 0.202316,0.355898 0.515811,0.606046 0.92578098,0.71875 0.114715,0.03154 0.285952,0.03886 0.457031,0.03516 0.03918,-0.0012 0.07647,-0.0058 0.115234,-0.0098 0.06575,-0.005 0.140201,-0.0026 0.19336,-0.01367 0.06811,-0.0142 0.134222,-0.04299 0.201172,-0.06836 0.333189,-0.126241 0.641516,-0.374039 0.800781,-0.669922 0.02144,-0.03983 0.0249,-0.08731 0.04297,-0.128906 0.0434,-0.09308 0.07854,-0.19749 0.103515,-0.304688 0.01416,-0.06169 0.02721,-0.12056 0.03516,-0.183593 0.02001,-0.144005 0.02205,-0.286726 0.0098,-0.427735 -0.0047,-0.05246 -0.01464,-0.100691 -0.02344,-0.152343 -0.0201,-0.117318 -0.05021,-0.229314 -0.0918,-0.337891 -0.02117,-0.05719 -0.03975,-0.112112 -0.06641,-0.166016 -0.04827,-0.0948 -0.11061,-0.177917 -0.175782,-0.259765 -0.03204,-0.04093 -0.04978,-0.09412 -0.08594,-0.13086 -0.02601,-0.02643 -0.06342,-0.04035 -0.0918,-0.06445 -0.08406,-0.07533 -0.172768,-0.138308 -0.265625,-0.1875 -0.01534,-0.0078 -0.02927,-0.01619 -0.04492,-0.02344 -0.118257,-0.0564 -0.246131,-0.09834 -0.390625,-0.119141 -0.08008,-0.01479 -0.160229,-0.01418 -0.242187,-0.01758 -0.02205,-5.53e-4 -0.03973,-0.0039 -0.0625,-0.0039 z"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:0.03125" /></g></svg>
|
||||
|
After Width: | Height: | Size: 9.2 KiB |
179
bauh/resources/img/flathub.svg
Executable file
@@ -0,0 +1,179 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="47.919052"
|
||||
height="48.110374"
|
||||
viewBox="0 0 12.678582 12.729203"
|
||||
version="1.1"
|
||||
id="svg3871"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="flathub.svg">
|
||||
<defs
|
||||
id="defs3865" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.35"
|
||||
inkscape:cx="8.4698372"
|
||||
inkscape:cy="118.04149"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="703"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="260"
|
||||
inkscape:window-maximized="1"
|
||||
fit-margin-bottom="-0.2"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
units="px" />
|
||||
<metadata
|
||||
id="metadata3868">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-12.122116,-167.33593)">
|
||||
<g
|
||||
transform="matrix(0.08875425,0,0,0.11490723,-57.513376,72.576168)"
|
||||
id="g10674"
|
||||
style="stroke-width:0.77710575">
|
||||
<path
|
||||
sodipodi:nodetypes="ccccccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path10642"
|
||||
d="m 856.01397,830.22631 -66.76365,43.44891 v 34.93973 l 33.38182,21.72315 33.37926,-21.72315 v -0.005 l 0.003,0.003 33.38183,21.72574 33.37925,-21.72315 v -34.93979 z"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#204a87;fill-opacity:1;fill-rule:nonzero;stroke:#204a87;stroke-width:9.3252697;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<g
|
||||
style="stroke-width:0.58728963"
|
||||
transform="matrix(0.50221552,0,0,0.50636711,429.63205,432.90461)"
|
||||
id="g10652">
|
||||
<rect
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#204a87;fill-opacity:1;fill-rule:nonzero;stroke:#204a87;stroke-width:2.4608953;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect10644"
|
||||
width="79.110413"
|
||||
height="79.110413"
|
||||
x="1289.9076"
|
||||
y="279.42584"
|
||||
rx="0"
|
||||
ry="0"
|
||||
transform="matrix(0.84019328,0.54228706,-0.84019328,0.54228706,0,0)" />
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#204a87;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:2.34915853;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 915.46811,824.92983 -5e-5,68.99997 -66.46803,42.90055 5e-5,-68.99997 z"
|
||||
id="path10648"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path10650"
|
||||
d="m 783.00003,824.92983 5e-5,68.99997 66.46803,42.90055 -5e-5,-68.99997 z"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#000000;stroke-width:2.34915853;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<rect
|
||||
transform="matrix(0.84019328,0.54228706,-0.84019328,0.54228706,0,0)"
|
||||
ry="0"
|
||||
rx="0"
|
||||
y="215.8064"
|
||||
x="1226.2882"
|
||||
height="79.110413"
|
||||
width="79.110413"
|
||||
id="rect10646"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#204a87;stroke-width:2.4608953;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
</g>
|
||||
<g
|
||||
style="stroke-width:0.58728963"
|
||||
transform="matrix(0.50221552,0,0,0.50636711,463.01368,454.62849)"
|
||||
id="g10662">
|
||||
<rect
|
||||
transform="matrix(0.84019328,0.54228706,-0.84019328,0.54228706,0,0)"
|
||||
ry="0"
|
||||
rx="0"
|
||||
y="279.42584"
|
||||
x="1289.9076"
|
||||
height="79.110413"
|
||||
width="79.110413"
|
||||
id="rect10654"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#204a87;fill-opacity:1;fill-rule:nonzero;stroke:#204a87;stroke-width:2.4608953;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<rect
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#204a87;stroke-width:2.4608953;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect10656"
|
||||
width="79.110413"
|
||||
height="79.110413"
|
||||
x="1226.2882"
|
||||
y="215.8064"
|
||||
rx="0"
|
||||
ry="0"
|
||||
transform="matrix(0.84019328,0.54228706,-0.84019328,0.54228706,0,0)" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path10658"
|
||||
d="m 915.46811,824.92983 -5e-5,68.99997 -66.46803,42.90055 5e-5,-68.99997 z"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#204a87;fill-opacity:1;fill-rule:nonzero;stroke:#204a87;stroke-width:2.34915853;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#204a87;stroke-width:2.34915853;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 783.00003,826.54445 5e-5,68.99997 66.46803,42.90055 -5e-5,-68.99997 z"
|
||||
id="path10660"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
style="stroke-width:0.58728963"
|
||||
id="g10672"
|
||||
transform="matrix(0.50221552,0,0,0.50636711,396.25042,454.62849)">
|
||||
<rect
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#204a87;fill-opacity:1;fill-rule:nonzero;stroke:#204a87;stroke-width:2.4608953;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect10664"
|
||||
width="79.110413"
|
||||
height="79.110413"
|
||||
x="1289.9076"
|
||||
y="279.42584"
|
||||
rx="0"
|
||||
ry="0"
|
||||
transform="matrix(0.84019328,0.54228706,-0.84019328,0.54228706,0,0)" />
|
||||
<rect
|
||||
transform="matrix(0.84019328,0.54228706,-0.84019328,0.54228706,0,0)"
|
||||
ry="0"
|
||||
rx="0"
|
||||
y="215.8064"
|
||||
x="1226.2882"
|
||||
height="79.110413"
|
||||
width="79.110413"
|
||||
id="rect10666"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#204a87;stroke-width:2.4608953;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#204a87;fill-opacity:1;fill-rule:nonzero;stroke:#204a87;stroke-width:2.34915853;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 915.46811,824.92983 -5e-5,68.99997 -66.46803,42.90055 5e-5,-68.99997 z"
|
||||
id="path10668"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path10670"
|
||||
d="m 783.00003,824.92983 5e-5,68.99997 66.46803,42.90055 -5e-5,-68.99997 z"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#204a87;stroke-width:2.34915853;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 14 KiB |
220
bauh/resources/img/history.svg
Executable file
|
After Width: | Height: | Size: 40 KiB |
120
bauh/resources/img/install.svg
Executable file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="12"
|
||||
height="12"
|
||||
viewBox="0 0 12 12"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="install.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"><metadata
|
||||
id="metadata135"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs133" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="703"
|
||||
id="namedview131"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="1.9124627"
|
||||
inkscape:cx="58.262905"
|
||||
inkscape:cy="42.640588"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="260"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Capa_1" />
|
||||
<g
|
||||
id="g98"
|
||||
transform="scale(0.034381)"
|
||||
style="fill:#00ff00">
|
||||
<path
|
||||
d="m 349.03,141.226 v 66.579 c 0,5.012 -4.061,9.079 -9.079,9.079 H 216.884 v 123.067 c 0,5.019 -4.067,9.079 -9.079,9.079 h -66.579 c -5.009,0 -9.079,-4.061 -9.079,-9.079 V 216.884 H 9.079 C 4.063,216.884 0,212.817 0,207.805 v -66.579 c 0,-5.013 4.063,-9.079 9.079,-9.079 H 132.147 V 9.079 C 132.147,4.061 136.216,0 141.226,0 h 66.579 c 5.012,0 9.079,4.061 9.079,9.079 v 123.068 h 123.067 c 5.019,0 9.079,4.066 9.079,9.079 z"
|
||||
id="path96"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#00ff00" />
|
||||
</g>
|
||||
<g
|
||||
id="g100"
|
||||
transform="translate(0,-337.03)">
|
||||
</g>
|
||||
<g
|
||||
id="g102"
|
||||
transform="translate(0,-337.03)">
|
||||
</g>
|
||||
<g
|
||||
id="g104"
|
||||
transform="translate(0,-337.03)">
|
||||
</g>
|
||||
<g
|
||||
id="g106"
|
||||
transform="translate(0,-337.03)">
|
||||
</g>
|
||||
<g
|
||||
id="g108"
|
||||
transform="translate(0,-337.03)">
|
||||
</g>
|
||||
<g
|
||||
id="g110"
|
||||
transform="translate(0,-337.03)">
|
||||
</g>
|
||||
<g
|
||||
id="g112"
|
||||
transform="translate(0,-337.03)">
|
||||
</g>
|
||||
<g
|
||||
id="g114"
|
||||
transform="translate(0,-337.03)">
|
||||
</g>
|
||||
<g
|
||||
id="g116"
|
||||
transform="translate(0,-337.03)">
|
||||
</g>
|
||||
<g
|
||||
id="g118"
|
||||
transform="translate(0,-337.03)">
|
||||
</g>
|
||||
<g
|
||||
id="g120"
|
||||
transform="translate(0,-337.03)">
|
||||
</g>
|
||||
<g
|
||||
id="g122"
|
||||
transform="translate(0,-337.03)">
|
||||
</g>
|
||||
<g
|
||||
id="g124"
|
||||
transform="translate(0,-337.03)">
|
||||
</g>
|
||||
<g
|
||||
id="g126"
|
||||
transform="translate(0,-337.03)">
|
||||
</g>
|
||||
<g
|
||||
id="g128"
|
||||
transform="translate(0,-337.03)">
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
BIN
bauh/resources/img/logo.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
477
bauh/resources/img/logo.svg
Executable file
|
After Width: | Height: | Size: 75 KiB |
BIN
bauh/resources/img/logo_update.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
473
bauh/resources/img/logo_update.svg
Executable file
|
After Width: | Height: | Size: 74 KiB |
115
bauh/resources/img/red_cross.svg
Executable file
@@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 12 12"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="cross_red.svg"
|
||||
width="12"
|
||||
height="12"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"><metadata
|
||||
id="metadata1018"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs1016" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="703"
|
||||
id="namedview1014"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
showborder="false"
|
||||
inkscape:zoom="1.7430481"
|
||||
inkscape:cx="39.29923"
|
||||
inkscape:cy="73.435604"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="260"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Layer_1" />
|
||||
<path
|
||||
id="XMLID_90_"
|
||||
d="M 0.18530763,10.025545 1.9744745,11.814702 C 2.093058,11.933327 2.2540227,12 2.4217347,12 2.5894888,12 2.7504115,11.93333 2.8689948,11.814702 L 5.9999843,8.6837266 9.1309735,11.814702 C 9.2495992,11.933327 9.4105217,12 9.5782337,12 c 0.167754,0 0.3286768,-0.06667 0.4473023,-0.185298 l 1.789167,-1.789157 c 0.247034,-0.2470354 0.247034,-0.6475284 0,-0.8945604 L 8.6837558,6.0000066 11.814703,2.8689866 C 11.933371,2.7503616 12,2.5894836 12,2.4217286 c 0,-0.167795 -0.06663,-0.328675 -0.185297,-0.4473 L 10.025578,0.18526863 c -0.2469922,-0.247032 -0.6475697,-0.247032 -0.8945622,4.3e-5 L 6.0000263,3.3162466 2.8689948,0.18531163 c -0.2469925,-0.247035 -0.6475699,-0.247035 -0.8945624,0 L 0.18530763,1.9744286 C 0.0666399,2.0930536 1.054262e-5,2.2539336 1.054262e-5,2.4216866 c 0,0.167755 0.06662935738,0.328635 0.18529708738,0.44726 l 3.13094717,3.131018 -3.13094717,3.13102 c -0.24707684,0.247032 -0.24707684,0.647525 0,0.8945604 z"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ff0000;stroke-width:0.0421704" />
|
||||
<g
|
||||
id="g983"
|
||||
transform="translate(2.5e-4,-272.56001)">
|
||||
</g>
|
||||
<g
|
||||
id="g985"
|
||||
transform="translate(2.5e-4,-272.56001)">
|
||||
</g>
|
||||
<g
|
||||
id="g987"
|
||||
transform="translate(2.5e-4,-272.56001)">
|
||||
</g>
|
||||
<g
|
||||
id="g989"
|
||||
transform="translate(2.5e-4,-272.56001)">
|
||||
</g>
|
||||
<g
|
||||
id="g991"
|
||||
transform="translate(2.5e-4,-272.56001)">
|
||||
</g>
|
||||
<g
|
||||
id="g993"
|
||||
transform="translate(2.5e-4,-272.56001)">
|
||||
</g>
|
||||
<g
|
||||
id="g995"
|
||||
transform="translate(2.5e-4,-272.56001)">
|
||||
</g>
|
||||
<g
|
||||
id="g997"
|
||||
transform="translate(2.5e-4,-272.56001)">
|
||||
</g>
|
||||
<g
|
||||
id="g999"
|
||||
transform="translate(2.5e-4,-272.56001)">
|
||||
</g>
|
||||
<g
|
||||
id="g1001"
|
||||
transform="translate(2.5e-4,-272.56001)">
|
||||
</g>
|
||||
<g
|
||||
id="g1003"
|
||||
transform="translate(2.5e-4,-272.56001)">
|
||||
</g>
|
||||
<g
|
||||
id="g1005"
|
||||
transform="translate(2.5e-4,-272.56001)">
|
||||
</g>
|
||||
<g
|
||||
id="g1007"
|
||||
transform="translate(2.5e-4,-272.56001)">
|
||||
</g>
|
||||
<g
|
||||
id="g1009"
|
||||
transform="translate(2.5e-4,-272.56001)">
|
||||
</g>
|
||||
<g
|
||||
id="g1011"
|
||||
transform="translate(2.5e-4,-272.56001)">
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.6 KiB |
146
bauh/resources/img/refresh.svg
Executable file
@@ -0,0 +1,146 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 47.999999 47.999999"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="refresh_2.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"><metadata
|
||||
id="metadata45"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs43"><linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient540"><stop
|
||||
style="stop-color:#0088aa;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop536" /><stop
|
||||
style="stop-color:#0088aa;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop538" /></linearGradient><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient540"
|
||||
id="linearGradient542"
|
||||
x1="-130.74721"
|
||||
y1="-214.80638"
|
||||
x2="-556.49438"
|
||||
y2="110.01643"
|
||||
gradientUnits="userSpaceOnUse" /></defs><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="703"
|
||||
id="namedview41"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
inkscape:zoom="1.9374833"
|
||||
inkscape:cx="165.58246"
|
||||
inkscape:cy="-10.288149"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="260"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Capa_1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<g
|
||||
id="g8"
|
||||
transform="matrix(0.10547014,0,0,0.0985161,-1.6940777,0)"
|
||||
style="fill:url(#linearGradient542);fill-opacity:1">
|
||||
<g
|
||||
id="g6"
|
||||
style="fill:url(#linearGradient542);fill-opacity:1">
|
||||
<path
|
||||
d="m 55.323,203.641 c 15.664,0 29.813,-9.405 35.872,-23.854 25.017,-59.604 83.842,-101.61 152.42,-101.61 37.797,0 72.449,12.955 100.23,34.442 l -21.775,3.371 c -7.438,1.153 -13.224,7.054 -14.232,14.512 -1.01,7.454 3.008,14.686 9.867,17.768 l 119.746,53.872 c 5.249,2.357 11.33,1.904 16.168,-1.205 4.83,-3.114 7.764,-8.458 7.796,-14.208 l 0.621,-131.943 c 0.042,-7.506 -4.851,-14.144 -12.024,-16.332 -7.185,-2.188 -14.947,0.589 -19.104,6.837 L 414.403,70.096 C 370.398,26.778 310.1,0 243.615,0 142.806,0 56.133,61.562 19.167,149.06 c -5.134,12.128 -3.84,26.015 3.429,36.987 7.269,10.976 19.556,17.594 32.727,17.594 z"
|
||||
id="path2"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:url(#linearGradient542);fill-opacity:1" />
|
||||
<path
|
||||
d="m 464.635,301.184 c -7.27,-10.977 -19.558,-17.594 -32.728,-17.594 -15.664,0 -29.813,9.405 -35.872,23.854 -25.018,59.604 -83.843,101.61 -152.42,101.61 -37.798,0 -72.45,-12.955 -100.232,-34.442 l 21.776,-3.369 c 7.437,-1.153 13.223,-7.055 14.233,-14.514 1.009,-7.453 -3.008,-14.686 -9.867,-17.768 L 49.779,285.089 c -5.25,-2.356 -11.33,-1.905 -16.169,1.205 -4.829,3.114 -7.764,8.458 -7.795,14.207 l -0.622,131.943 c -0.042,7.506 4.85,14.144 12.024,16.332 7.185,2.188 14.948,-0.59 19.104,-6.839 l 16.505,-24.805 c 44.004,43.32 104.303,70.098 170.788,70.098 100.811,0 187.481,-61.561 224.446,-149.059 5.137,-12.128 3.843,-26.014 -3.425,-36.987 z"
|
||||
id="path4"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:url(#linearGradient542);fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g10"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g12"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g14"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g16"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g18"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g20"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g22"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g24"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g26"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g28"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g30"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g32"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g34"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g36"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g38"
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.8 KiB |
120
bauh/resources/img/search.svg
Executable file
@@ -0,0 +1,120 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 12 12"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="search.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
width="12"
|
||||
height="12"><metadata
|
||||
id="metadata126"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs124" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="739"
|
||||
id="namedview122"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
inkscape:zoom="0.53133476"
|
||||
inkscape:cx="834.65593"
|
||||
inkscape:cy="-101.88082"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="260"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Capa_1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<g
|
||||
id="Search"
|
||||
transform="scale(0.04794017)"
|
||||
style="fill:#999999">
|
||||
<path
|
||||
style="clip-rule:evenodd;fill:#999999;fill-rule:evenodd"
|
||||
d="m 244.186,214.604 -54.379,-54.378 c -0.289,-0.289 -0.628,-0.491 -0.93,-0.76 10.7,-16.231 16.945,-35.66 16.945,-56.554 C 205.822,46.075 159.747,0 102.911,0 46.075,0 0,46.075 0,102.911 c 0,56.835 46.074,102.911 102.91,102.911 20.895,0 40.323,-6.245 56.554,-16.945 0.269,0.301 0.47,0.64 0.759,0.929 l 54.38,54.38 c 8.169,8.168 21.413,8.168 29.583,0 8.168,-8.169 8.168,-21.413 0,-29.582 z M 102.911,170.146 c -37.134,0 -67.236,-30.102 -67.236,-67.235 0,-37.134 30.103,-67.236 67.236,-67.236 37.132,0 67.235,30.103 67.235,67.236 0,37.133 -30.103,67.235 -67.235,67.235 z"
|
||||
id="path88"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
id="g91"
|
||||
transform="translate(0,-238.312)">
|
||||
</g>
|
||||
<g
|
||||
id="g93"
|
||||
transform="translate(0,-238.312)">
|
||||
</g>
|
||||
<g
|
||||
id="g95"
|
||||
transform="translate(0,-238.312)">
|
||||
</g>
|
||||
<g
|
||||
id="g97"
|
||||
transform="translate(0,-238.312)">
|
||||
</g>
|
||||
<g
|
||||
id="g99"
|
||||
transform="translate(0,-238.312)">
|
||||
</g>
|
||||
<g
|
||||
id="g101"
|
||||
transform="translate(0,-238.312)">
|
||||
</g>
|
||||
<g
|
||||
id="g103"
|
||||
transform="translate(0,-238.312)">
|
||||
</g>
|
||||
<g
|
||||
id="g105"
|
||||
transform="translate(0,-238.312)">
|
||||
</g>
|
||||
<g
|
||||
id="g107"
|
||||
transform="translate(0,-238.312)">
|
||||
</g>
|
||||
<g
|
||||
id="g109"
|
||||
transform="translate(0,-238.312)">
|
||||
</g>
|
||||
<g
|
||||
id="g111"
|
||||
transform="translate(0,-238.312)">
|
||||
</g>
|
||||
<g
|
||||
id="g113"
|
||||
transform="translate(0,-238.312)">
|
||||
</g>
|
||||
<g
|
||||
id="g115"
|
||||
transform="translate(0,-238.312)">
|
||||
</g>
|
||||
<g
|
||||
id="g117"
|
||||
transform="translate(0,-238.312)">
|
||||
</g>
|
||||
<g
|
||||
id="g119"
|
||||
transform="translate(0,-238.312)">
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
BIN
bauh/resources/img/snapcraft.png
Executable file
|
After Width: | Height: | Size: 1.1 KiB |
96
bauh/resources/img/uninstall.svg
Executable file
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
height="24"
|
||||
viewBox="-40 0 18 18.000056"
|
||||
width="24"
|
||||
version="1.1"
|
||||
id="svg72"
|
||||
sodipodi:docname="uninstall.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<metadata
|
||||
id="metadata78">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs76" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="703"
|
||||
id="namedview74"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="5.7611241"
|
||||
inkscape:cx="40.648821"
|
||||
inkscape:cy="17.781204"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="260"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg72"
|
||||
units="px" />
|
||||
<g
|
||||
id="g5907"
|
||||
transform="matrix(0.50000001,0,0,0.49999997,-20.000045,0)">
|
||||
<g
|
||||
transform="matrix(0.10380557,0,0,0.08430914,-35.847729,0)"
|
||||
id="g84">
|
||||
<path
|
||||
d="m 192.40098,154.70443 c -5.52344,0 -10,4.47657 -10,10 v 189 c 0,5.51954 4.47656,10 10,10 5.52343,0 10,-4.48046 10,-10 v -189 c 0,-5.52343 -4.47657,-10 -10,-10 z m 0,0"
|
||||
id="path64"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 74.400978,154.70443 c -5.52344,0 -10,4.47657 -10,10 v 189 c 0,5.51954 4.47656,10 10,10 5.52344,0 10,-4.48046 10,-10 v -189 c 0,-5.52343 -4.47656,-10 -10,-10 z m 0,0"
|
||||
id="path66"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m -11.599024,127.1224 v 246.37891 c 0,14.5625 5.3398433,28.23828 14.6679683,38.05078 9.2851557,9.83984 22.2070317,15.42578 35.7304687,15.44922 H 228.00254 c 13.52734,-0.0234 26.44922,-5.60938 35.73047,-15.44922 9.32812,-9.8125 14.66797,-23.48828 14.66797,-38.05078 V 127.1224 C 296.94395,122.20053 308.95957,104.28647 306.4791,85.259126 303.99473,66.235689 287.7877,52.00522 268.6002,52.001314 h -51.19922 v -12.5 c 0.0586,-10.511719 -4.09766,-20.605469 -11.53907,-28.03125 -7.4414,-7.4218751 -17.55078,-11.55468758 -28.0625,-11.46875008 H 89.002538 C 78.490818,-0.08462358 68.381448,4.0481889 60.940038,11.470064 53.498632,18.895845 49.342382,28.989595 49.400976,39.501314 v 12.5 H -1.7982427 c -19.1875003,0.0039 -35.3945313,14.234375 -37.8789073,33.257812 -2.480468,19.027344 9.535157,36.941404 28.078126,41.863274 z M 228.00254,407.00131 H 38.799413 c -17.097656,0 -30.3984367,-14.6875 -30.3984367,-33.5 v -245.5 H 258.40098 v 245.5 c 0,18.8125 -13.30078,33.5 -30.39844,33.5 z M 69.400978,39.501314 c -0.0664,-5.207031 1.98047,-10.21875 5.67578,-13.894531 3.69141,-3.675781 8.71484,-5.695313 13.92578,-5.605469 h 88.796872 c 5.21094,-0.08984 10.23438,1.929688 13.92579,5.605469 3.69531,3.671875 5.74218,8.6875 5.67578,13.894531 v 12.5 H 69.400978 Z m -71.1992207,32.5 H 268.6002 c 9.9414,0 18,8.058594 18,18 0,9.941406 -8.0586,17.999996 -18,17.999996 H -1.7982427 c -9.9414073,0 -18.0000003,-8.05859 -18.0000003,-17.999996 0,-9.941406 8.058593,-18 18.0000003,-18 z m 0,0"
|
||||
id="path68"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 133.40098,154.70443 c -5.52344,0 -10,4.47657 -10,10 v 189 c 0,5.51954 4.47656,10 10,10 5.52343,0 10,-4.48046 10,-10 v -189 c 0,-5.52343 -4.47657,-10 -10,-10 z m 0,0"
|
||||
id="path70"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3791"
|
||||
d="m -32.347465,34.215765 c -1.136967,-0.263564 -1.85228,-0.770702 -2.285878,-1.620623 -0.244025,-0.478328 -0.252697,-0.85963 -0.252697,-11.110455 V 10.869559 h 12.888149 12.8881497 l -0.0028,10.577396 c -0.0027,10.017853 -0.01562,10.604942 -0.243951,11.098129 -0.295923,0.639162 -0.8228787,1.147628 -1.5106647,1.457656 -0.486498,0.219295 -1.201157,0.235997 -10.870308,0.254055 -5.692267,0.01062 -10.46674,-0.0078 -10.609942,-0.04103 z m 4.983091,-3.760996 0.289334,-0.202658 v -8.369266 c 0,-8.31107 -0.0019,-8.370969 -0.270765,-8.614305 -0.358608,-0.324535 -1.182796,-0.324535 -1.541404,0 -0.268882,0.243336 -0.270765,0.303235 -0.270765,8.614305 v 8.369266 l 0.289334,0.202658 c 0.159133,0.11146 0.497593,0.202656 0.752133,0.202656 0.25454,0 0.593,-0.0912 0.752133,-0.202656 z m 6.118617,0 0.289333,-0.202658 v -8.369266 c 0,-8.31107 -0.0019,-8.370969 -0.270764,-8.614305 -0.358608,-0.324535 -1.182797,-0.324535 -1.541404,0 -0.268883,0.243336 -0.270765,0.303235 -0.270765,8.614305 v 8.369266 l 0.289333,0.202658 c 0.159134,0.11146 0.497593,0.202656 0.752133,0.202656 0.25454,0 0.593001,-0.0912 0.752134,-0.202656 z m 6.118616,0 0.289334,-0.202658 v -8.369266 c 0,-8.31107 -0.0019,-8.370969 -0.270765,-8.614305 -0.358608,-0.324535 -1.182796,-0.324535 -1.541404,0 -0.268882,0.243336 -0.270765,0.303235 -0.270765,8.614305 v 8.369266 l 0.289334,0.202658 c 0.159132,0.11146 0.497593,0.202656 0.752133,0.202656 0.254539,0 0.593,-0.0912 0.752133,-0.202656 z"
|
||||
style="fill:#b3b3b3;stroke-width:0.13018332" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3793"
|
||||
d="m -36.877845,8.8644087 c -0.540224,-0.2384603 -0.844092,-0.6148864 -0.931583,-1.1540278 -0.06403,-0.3945707 -0.01083,-0.5268359 0.386094,-0.9599757 l 0.460354,-0.5023539 14.753284,-0.034506 c 10.894353,-0.025481 14.8480787,0.00467 15.1156987,0.115259 0.791461,0.3270622 1.121594,1.3085049 0.671312,1.9957216 -0.490508,0.7486088 0.06973,0.7229272 -15.6494407,0.7173712 -11.671083,-0.00413 -14.489515,-0.037913 -14.805719,-0.1774884 z"
|
||||
style="fill:#b3b3b3;stroke-width:0.13018332" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path3795"
|
||||
d="m -28.586127,3.581076 c 0.04302,-0.5947544 0.131512,-0.8607897 0.373651,-1.1233362 0.63458,-0.6880588 0.750066,-0.7010134 6.249312,-0.7010134 4.435032,0 5.151954,0.027404 5.540514,0.2117885 0.718185,0.3408012 0.959962,0.7509159 0.959962,1.6283345 V 4.360393 h -6.589905 -6.589904 z"
|
||||
style="fill:#b3b3b3;stroke-width:0.13018332" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.6 KiB |
117
bauh/resources/img/update_green.svg
Executable file
@@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
viewBox="0 0 48 48"
|
||||
enable-background="new 0 0 26 26"
|
||||
id="svg8"
|
||||
sodipodi:docname="update_green_2.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
width="48"
|
||||
height="48">
|
||||
<metadata
|
||||
id="metadata14">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs12">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient309">
|
||||
<stop
|
||||
style="stop-color:#55d400;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop305" />
|
||||
<stop
|
||||
style="stop-color:#55d400;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop307" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient309"
|
||||
id="linearGradient311"
|
||||
x1="-12.490223"
|
||||
y1="5.5246015"
|
||||
x2="-20.59199"
|
||||
y2="18.300463"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient309"
|
||||
id="linearGradient313"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-12.490223"
|
||||
y1="5.5246015"
|
||||
x2="-20.59199"
|
||||
y2="18.300463" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient309"
|
||||
id="linearGradient315"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-12.490223"
|
||||
y1="5.5246015"
|
||||
x2="-20.59199"
|
||||
y2="18.300463" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="703"
|
||||
id="namedview10"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
inkscape:zoom="3.2091769"
|
||||
inkscape:cx="11.132699"
|
||||
inkscape:cy="36.020085"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="260"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg8"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<g
|
||||
id="g6"
|
||||
transform="matrix(1.8461538,0,0,2,0,-2)"
|
||||
style="fill:url(#linearGradient311);fill-opacity:1">
|
||||
<path
|
||||
d="m 25,17 h -2 c -0.6,0 -1,0.4 -1,1 v 2.5 C 22,20.8 21.8,21 21.5,21 H 4.5 C 4.2,21 4,20.8 4,20.5 V 18 C 4,17.4 3.6,17 3,17 H 1 c -0.6,0 -1,0.4 -1,1 v 6 c 0,0.6 0.4,1 1,1 h 24 c 0.6,0 1,-0.4 1,-1 v -6 c 0,-0.6 -0.4,-1 -1,-1 z"
|
||||
id="path2"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:url(#linearGradient313);fill-opacity:1" />
|
||||
<path
|
||||
d="m 12.3,16.7 c 0.2,0.2 0.5,0.3 0.7,0.3 0.2,0 0.5,-0.1 0.7,-0.3 l 6,-6 C 19.9,10.5 20,10.3 20,10 20,9.7 19.9,9.5 19.7,9.3 L 18.3,7.9 C 18.1,7.7 17.9,7.6 17.6,7.6 c -0.3,0 -0.5,0.1 -0.7,0.3 l -1,1 C 15.6,9.2 15,9 15,8.5 V 2 C 15,1.4 14.6,1 14,1 H 12 C 11.4,1 11,1.4 11,2 V 8.6 C 11,9 10.5,9.3 10.1,9 L 9.1,8 C 8.9,7.8 8.7,7.7 8.4,7.7 8.1,7.7 7.9,7.8 7.7,8 L 6.3,9.4 C 6.1,9.6 6,9.8 6,10.1 c 0,0.3 0.1,0.5 0.3,0.7 z"
|
||||
id="path4"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:url(#linearGradient315);fill-opacity:1" />
|
||||
</g>
|
||||
<g
|
||||
transform="matrix(3.7795276,0,0,3.7795276,668.39708,-480.81644)"
|
||||
id="layer1"
|
||||
inkscape:label="Layer 1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
179
bauh/resources/img/update_logo.svg
Normal file
@@ -0,0 +1,179 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="43.449291"
|
||||
height="41.737198"
|
||||
viewBox="0 0 11.495958 11.042967"
|
||||
version="1.1"
|
||||
id="svg3871"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||
sodipodi:docname="update_logo.svg">
|
||||
<defs
|
||||
id="defs3865" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="1.4"
|
||||
inkscape:cx="301.87613"
|
||||
inkscape:cy="-17.251705"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
inkscape:window-width="1360"
|
||||
inkscape:window-height="707"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
fit-margin-bottom="-0.2"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
units="px" />
|
||||
<metadata
|
||||
id="metadata3868">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-12.111406,-169.00616)">
|
||||
<g
|
||||
transform="matrix(0.08047549,0,0,0.0997485,-51.028672,86.747244)"
|
||||
id="g10674"
|
||||
style="fill:#ff2a2a;stroke:#800000;stroke-width:0.77710575">
|
||||
<path
|
||||
sodipodi:nodetypes="ccccccccccc"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path10642"
|
||||
d="m 856.01397,830.22631 -66.76365,43.44891 v 34.93973 l 33.38182,21.72315 33.37926,-21.72315 v -0.005 l 0.003,0.003 33.38183,21.72574 33.37925,-21.72315 v -34.93979 z"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ff2a2a;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:9.3252697;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<g
|
||||
style="fill:#ff2a2a;stroke:#800000;stroke-width:0.58728963"
|
||||
transform="matrix(0.50221552,0,0,0.50636711,429.63205,432.90461)"
|
||||
id="g10652">
|
||||
<rect
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ff2a2a;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:2.4608953;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect10644"
|
||||
width="79.110413"
|
||||
height="79.110413"
|
||||
x="1289.9076"
|
||||
y="279.42584"
|
||||
rx="0"
|
||||
ry="0"
|
||||
transform="matrix(0.84019328,0.54228706,-0.84019328,0.54228706,0,0)" />
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ff2a2a;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:2.34915853;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 915.46811,824.92983 -5e-5,68.99997 -66.46803,42.90055 5e-5,-68.99997 z"
|
||||
id="path10648"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path10650"
|
||||
d="m 783.00003,824.92983 5e-5,68.99997 66.46803,42.90055 -5e-5,-68.99997 z"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ff2a2a;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:2.34915853;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<rect
|
||||
transform="matrix(0.84019328,0.54228706,-0.84019328,0.54228706,0,0)"
|
||||
ry="0"
|
||||
rx="0"
|
||||
y="215.8064"
|
||||
x="1226.2882"
|
||||
height="79.110413"
|
||||
width="79.110413"
|
||||
id="rect10646"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ff2a2a;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:2.4608953;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
</g>
|
||||
<g
|
||||
style="fill:#ff2a2a;stroke:#800000;stroke-width:0.58728963"
|
||||
transform="matrix(0.50221552,0,0,0.50636711,463.01368,454.62849)"
|
||||
id="g10662">
|
||||
<rect
|
||||
transform="matrix(0.84019328,0.54228706,-0.84019328,0.54228706,0,0)"
|
||||
ry="0"
|
||||
rx="0"
|
||||
y="279.42584"
|
||||
x="1289.9076"
|
||||
height="79.110413"
|
||||
width="79.110413"
|
||||
id="rect10654"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ff2a2a;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:2.4608953;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<rect
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ff2a2a;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:2.4608953;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect10656"
|
||||
width="79.110413"
|
||||
height="79.110413"
|
||||
x="1226.2882"
|
||||
y="215.8064"
|
||||
rx="0"
|
||||
ry="0"
|
||||
transform="matrix(0.84019328,0.54228706,-0.84019328,0.54228706,0,0)" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path10658"
|
||||
d="m 915.46811,824.92983 -5e-5,68.99997 -66.46803,42.90055 5e-5,-68.99997 z"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ff2a2a;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:2.34915853;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ff2a2a;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:2.34915853;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 783.00003,826.54445 5e-5,68.99997 66.46803,42.90055 -5e-5,-68.99997 z"
|
||||
id="path10660"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
<g
|
||||
style="fill:#ff2a2a;stroke:#800000;stroke-width:0.58728963"
|
||||
id="g10672"
|
||||
transform="matrix(0.50221552,0,0,0.50636711,396.25042,454.62849)">
|
||||
<rect
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ff2a2a;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:2.4608953;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
id="rect10664"
|
||||
width="79.110413"
|
||||
height="79.110413"
|
||||
x="1289.9076"
|
||||
y="279.42584"
|
||||
rx="0"
|
||||
ry="0"
|
||||
transform="matrix(0.84019328,0.54228706,-0.84019328,0.54228706,0,0)" />
|
||||
<rect
|
||||
transform="matrix(0.84019328,0.54228706,-0.84019328,0.54228706,0,0)"
|
||||
ry="0"
|
||||
rx="0"
|
||||
y="215.8064"
|
||||
x="1226.2882"
|
||||
height="79.110413"
|
||||
width="79.110413"
|
||||
id="rect10666"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ff2a2a;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:2.4608953;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
<path
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ff2a2a;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:2.34915853;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate"
|
||||
d="m 915.46811,824.92983 -5e-5,68.99997 -66.46803,42.90055 5e-5,-68.99997 z"
|
||||
id="path10668"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path10670"
|
||||
d="m 783.00003,824.92983 5e-5,68.99997 66.46803,42.90055 -5e-5,-68.99997 z"
|
||||
style="color:#000000;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#ff2a2a;fill-opacity:1;fill-rule:nonzero;stroke:#800000;stroke-width:2.34915853;stroke-linecap:square;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 14 KiB |
114
bauh/resources/locale/en
Normal file
@@ -0,0 +1,114 @@
|
||||
manage_window.title=Applications Manager
|
||||
manage_window.columns.latest_version=Latest Version
|
||||
manage_window.columns.update=Upgrade ?
|
||||
manage_window.apps_table.row.actions.info=Information
|
||||
manage_window.apps_table.row.actions.history=History
|
||||
manage_window.apps_table.row.actions.uninstall.popup.title=Uninstall
|
||||
manage_window.apps_table.row.actions.uninstall.popup.body=Remove {} from your computer ?
|
||||
manage_window.apps_table.row.actions.install.popup.title=Installation
|
||||
manage_window.apps_table.row.actions.install.popup.body=Install {} on your computer ?
|
||||
manage_window.apps_table.row.actions.downgrade=Downgrade
|
||||
manage_window.apps_table.row.actions.downgrade.popup.body=Do you really want to downgrade {} ?
|
||||
manage_window.apps_table.row.actions.install=Install
|
||||
manage_window.apps_table.row.actions.refresh=Refresh
|
||||
manage_window.apps_table.upgrade_toggle.tooltip=There is an update for this application. Click here to check or uncheck the update
|
||||
manage_window.checkbox.only_apps=Apps
|
||||
window_manage.input_search.placeholder=Search
|
||||
window_manage.input_search.tooltip=Type and press ENTER to search for applications
|
||||
manage_window.label.updates=Updates
|
||||
manage_window.status.refreshing=Refreshing
|
||||
manage_window.status.upgrading=Upgrading
|
||||
manage_window.status.uninstalling=Uninstalling
|
||||
manage_window.status.downgrading=Downgrading
|
||||
manage_window.status.info=Retrieving information
|
||||
manage_window.status.history=Retrieving history
|
||||
manage_window.status.searching=Searching
|
||||
manage_window.status.installing=Installing
|
||||
manage_window.status.refreshing_app=Refreshing
|
||||
manage_window.bt.refresh.tooltip=Reload the data about installed applications
|
||||
manage_window.bt.upgrade.tooltip=Upgrade all checked applications
|
||||
manage_window.checkbox.show_details=Show details
|
||||
popup.root.title=Requires root privileges
|
||||
popup.root.password=Password
|
||||
popup.root.bad_password.title=Error
|
||||
popup.root.bad_password.body=Wrong password
|
||||
popup.downgrade.impossible.title=Error
|
||||
popup.downgrade.impossible.body=Impossible to downgrade: the app is in its first version
|
||||
popup.history.title=History
|
||||
popup.history.selected.tooltip=Current version
|
||||
popup.button.yes=Yes
|
||||
popup.button.no=No
|
||||
popup.button.cancel=Cancel
|
||||
tray.action.manage=Manage applications
|
||||
tray.action.exit=Exit
|
||||
tray.action.about=About
|
||||
tray.action.refreshing=Refreshing
|
||||
notification.new_updates=Updates
|
||||
notification.update_selected.success=app(s) updated successfully
|
||||
notification.update_selected.failed=Update failed
|
||||
notification.install.failed=installation failed
|
||||
notification.uninstall.failed=uninstallation failed
|
||||
notification.downgrade.failed=Failed to downgrade
|
||||
flatpak.info.arch=arch
|
||||
flatpak.info.branch=branch
|
||||
flatpak.info.collection=collection
|
||||
flatpak.info.commit=commit
|
||||
flatpak.info.date=date
|
||||
flatpak.info.description=description
|
||||
flatpak.info.id=id
|
||||
flatpak.info.installation=installation
|
||||
flatpak.info.installed=installed
|
||||
flatpak.info.license=license
|
||||
flatpak.info.name=name
|
||||
flatpak.info.origin=origin
|
||||
flatpak.info.parent=parent
|
||||
flatpak.info.ref=ref
|
||||
flatpak.info.runtime=runtime
|
||||
flatpak.info.sdk=sdk
|
||||
flatpak.info.subject=subject
|
||||
flatpak.info.type=type
|
||||
flatpak.info.version=version
|
||||
snap.info.commands=commands
|
||||
snap.info.contact=contact
|
||||
snap.info.description=description
|
||||
snap.info.license=license
|
||||
snap.info.revision=revision
|
||||
snap.info.tracking=tracking
|
||||
snap.info.installed=installed
|
||||
snap.info.publisher=publisher
|
||||
about.info.desc=Non-official Flatpak / Snap application management graphical interface
|
||||
about.info.link=More information at
|
||||
about.info.license=Free license
|
||||
about.info.rate=If this tool is useful for you, give it a star on Github to keep it up
|
||||
yes=yes
|
||||
no=no
|
||||
version.updated=updated
|
||||
version.outdated=outdated
|
||||
name=name
|
||||
version=version
|
||||
latest_version=latest version
|
||||
description=description
|
||||
type=type
|
||||
installed=installed
|
||||
uninstalled=uninstalled
|
||||
not_installed=not installed
|
||||
downgraded=downgraded
|
||||
others=others
|
||||
internet.required=Internet connection is required
|
||||
license.unset=unset
|
||||
updates=updates
|
||||
version.installed=installed version
|
||||
version.latest=latest version
|
||||
version.installed_outdated=the installed version is outdated
|
||||
version.unknown=not informed
|
||||
app.name=application name
|
||||
warning=warning
|
||||
snap.notification.snapd_unavailable=snapd seems not to be enabled. snap packages will not be available.
|
||||
flatpak.notification.no_remotes=No Flatpak remotes set. It will not be possible to search for Flatpak apps.
|
||||
install=install
|
||||
uninstall=uninstall
|
||||
bt.app_upgrade=Upgrade
|
||||
bt.app_not_upgrade=Don't upgrade
|
||||
manage_window.upgrade_all.popup.title=Upgrade
|
||||
manage_window.upgrade_all.popup.body=Upgrade all selected applications ?
|
||||
manage_window.bt_about.tooltip=About
|
||||
116
bauh/resources/locale/es
Normal file
@@ -0,0 +1,116 @@
|
||||
manage_window.title=Administrador de Aplicativos
|
||||
manage_window.columns.latest_version=Ultima Versión
|
||||
manage_window.columns.update=Actualizar ?
|
||||
manage_window.columns.installed=Instalado
|
||||
manage_window.apps_table.row.actions.info=Información
|
||||
manage_window.apps_table.row.actions.history=Historia
|
||||
manage_window.apps_table.row.actions.uninstall=Desinstalar
|
||||
manage_window.apps_table.row.actions.uninstall.popup.title=Desinstalación
|
||||
manage_window.apps_table.row.actions.uninstall.popup.body=¿Eliminar {} de tu ordenador?
|
||||
manage_window.apps_table.row.actions.install.popup.title=Instalación
|
||||
manage_window.apps_table.row.actions.install.popup.body=¿Instalar {} en tu ordenador?
|
||||
manage_window.apps_table.row.actions.downgrade=Revertir versión
|
||||
manage_window.apps_table.row.actions.downgrade.popup.body=¿Realmente quieres revertir la versión actual de {}?
|
||||
manage_window.apps_table.row.actions.install=Instalar
|
||||
manage_window.apps_table.row.actions.refresh=Actualizar
|
||||
manage_window.apps_table.upgrade_toggle.tooltip=Haces clic aqui para marcar o desmarcar la actualización
|
||||
manage_window.checkbox.only_apps=Aplicativos
|
||||
window_manage.input_search.placeholder=Buscar
|
||||
window_manage.input_search.tooltip=Escriba y oprima ENTER para buscar aplicativos
|
||||
manage_window.label.updates=Actualizaciones
|
||||
manage_window.status.refreshing=Recargando
|
||||
manage_window.status.upgrading=Actualizando
|
||||
manage_window.status.uninstalling=Desinstalando
|
||||
manage_window.status.downgrading=Revirtiendo
|
||||
manage_window.status.info=Obteniendo información
|
||||
manage_window.status.history=Obteniendo la historia
|
||||
manage_window.status.searching=Buscando
|
||||
manage_window.status.installing=Instalando
|
||||
manage_window.status.refreshing_app=Actualizando
|
||||
manage_window.bt.refresh.tooltip=Recarga los datos sobre los aplicativos instalados
|
||||
manage_window.bt.upgrade.tooltip=Actualiza todos los aplicativos marcados
|
||||
manage_window.checkbox.show_details=Mostrar detalles
|
||||
popup.root.title=Requiere privilegios de root
|
||||
popup.root.password=Contraseña
|
||||
popup.root.bad_password.title=Error
|
||||
popup.root.bad_password.body=Contraseña incorrecta
|
||||
popup.downgrade.impossible.title=Error
|
||||
popup.downgrade.impossible.body=Imposible revertir la versión: el aplicativo está en su primera versión
|
||||
popup.history.title=Historia
|
||||
popup.history.selected.tooltip=Versión actual
|
||||
popup.button.yes=Sí
|
||||
popup.button.no=No
|
||||
popup.button.cancel=Cancelar
|
||||
tray.action.manage=Administrar aplicativos
|
||||
tray.action.exit=Salir
|
||||
tray.action.about=Sobre
|
||||
tray.action.refreshing=Recargando
|
||||
notification.new_updates=Actualizaciones
|
||||
notification.update_selected.success=aplicativo(s) actualizado(s) con éxito
|
||||
notification.update_selected.failed=La actualización falló
|
||||
notification.install.failed=la instalación ha fallado
|
||||
notification.uninstall.failed=la desinstalación ha fallado
|
||||
notification.downgrade.failed=Error al revertir la versión
|
||||
flatpak.info.arch=arquitectura
|
||||
flatpak.info.branch=rama
|
||||
flatpak.info.collection=colección
|
||||
flatpak.info.commit=commit
|
||||
flatpak.info.date=fecha
|
||||
flatpak.info.description=descripción
|
||||
flatpak.info.id=id
|
||||
flatpak.info.installation=instalación
|
||||
flatpak.info.installed=instalado
|
||||
flatpak.info.license=licencia
|
||||
flatpak.info.name=nombre
|
||||
flatpak.info.origin=origen
|
||||
flatpak.info.parent=padre
|
||||
flatpak.info.ref=ref
|
||||
flatpak.info.runtime=runtime
|
||||
flatpak.info.sdk=sdk
|
||||
flatpak.info.subject=tema
|
||||
flatpak.info.type=tipo
|
||||
flatpak.info.version=versión
|
||||
snap.info.commands=comandos
|
||||
snap.info.contact=contacto
|
||||
snap.info.description=descripción
|
||||
snap.info.license=licencia
|
||||
snap.info.revision=revisión
|
||||
snap.info.tracking=tracking
|
||||
snap.info.installed=instalado
|
||||
snap.info.publisher=publicador
|
||||
about.info.desc=Interfaz grafica no oficial para administración de aplicativos Flatpak / Snap
|
||||
about.info.link=Mas informaciones en
|
||||
about.info.license=Licencia gratuita
|
||||
about.info.rate=Si esta herramienta es útil para ti, dale una estrella en Github para mantenerla
|
||||
yes=sí
|
||||
no=no
|
||||
version.updated=actualizada
|
||||
version.outdated=desactualizada
|
||||
name=nombre
|
||||
version=versión
|
||||
latest_version=ultima versión
|
||||
description=descripción
|
||||
type=tipo
|
||||
installed=instalado
|
||||
uninstalled=desinstalado
|
||||
not_installed=no instalado
|
||||
downgraded=versión revertida
|
||||
others=otros
|
||||
internet.required=Se requiere conexión a internet
|
||||
license.unset=No está definida
|
||||
updates=actualizaciones
|
||||
version.installed=versión instalada
|
||||
version.latest=versión más reciente
|
||||
version.installed_outdated=la versión instalada está desactualizada
|
||||
version.unknown=versión no informada
|
||||
app.name=nombre del aplicativo
|
||||
warning=aviso
|
||||
snap.notification.snapd_unavailable=snapd no parece estar habilitado. los paquetes snap estarán indisponibles.
|
||||
flatpak.notification.no_remotes=No hay repositorios (remotes) Flatpak configurados. No será posible buscar aplicativos Flatpak.
|
||||
install=instalar
|
||||
uninstall=desinstalar
|
||||
bt.app_upgrade=Actualizar
|
||||
bt.app_not_upgrade=No actualizar
|
||||
manage_window.upgrade_all.popup.title=Actualizar
|
||||
manage_window.upgrade_all.popup.body=¿Actualizar todos los aplicativos seleccionados?
|
||||
manage_window.bt_about.tooltip=Sobre
|
||||
116
bauh/resources/locale/pt
Normal file
@@ -0,0 +1,116 @@
|
||||
manage_window.title=Gerenciador de Aplicativos
|
||||
manage_window.columns.latest_version=Última Versão
|
||||
manage_window.columns.update=Atualizar ?
|
||||
manage_window.columns.installed=Instalado
|
||||
manage_window.apps_table.row.actions.info=Informação
|
||||
manage_window.apps_table.row.actions.history=Histórico
|
||||
manage_window.apps_table.row.actions.uninstall=Desinstalar
|
||||
manage_window.apps_table.row.actions.uninstall.popup.title=Desinstalação
|
||||
manage_window.apps_table.row.actions.uninstall.popup.body=Remover {} do seu computador ?
|
||||
manage_window.apps_table.row.actions.install.popup.title=Instalação
|
||||
manage_window.apps_table.row.actions.install.popup.body=Instalar {} no seu computador ?
|
||||
manage_window.apps_table.row.actions.downgrade=Reverter versão
|
||||
manage_window.apps_table.row.actions.downgrade.popup.body=Você realmente quer reverter a versão atual de {} ?
|
||||
manage_window.apps_table.row.actions.install=Instalar
|
||||
manage_window.apps_table.row.actions.refresh=Atualizar
|
||||
manage_window.apps_table.upgrade_toggle.tooltip=Existe um atualização para esse aplicativo. Clique aqui para marcar ou desmarcar a atualização
|
||||
manage_window.checkbox.only_apps=Aplicativos
|
||||
window_manage.input_search.placeholder=Buscar
|
||||
window_manage.input_search.tooltip=Digite e pressione ENTER para buscar aplicativos
|
||||
manage_window.label.updates=Atualizações
|
||||
manage_window.status.refreshing=Recarregando
|
||||
manage_window.status.upgrading=Atualizando
|
||||
manage_window.status.uninstalling=Desinstalando
|
||||
manage_window.status.downgrading=Revertendo
|
||||
manage_window.status.info=Obtendo informação
|
||||
manage_window.status.history=Obtendo histórico
|
||||
manage_window.status.searching=Buscando
|
||||
manage_window.status.installing=Instalando
|
||||
manage_window.status.refreshing_app=Atualizando
|
||||
manage_window.bt.refresh.tooltip=Recarrega os dados sobre os aplicativos instalados
|
||||
manage_window.bt.upgrade.tooltip=Atualiza todos os aplicativos marcados
|
||||
manage_window.checkbox.show_details=Mostrar detalhes
|
||||
popup.root.title=Requer privilégios de root
|
||||
popup.root.password=Senha
|
||||
popup.root.bad_password.title=Erro
|
||||
popup.root.bad_password.body=Senha incorreta
|
||||
popup.downgrade.impossible.title=Erro
|
||||
popup.downgrade.impossible.body=Impossível reverter a versão: o aplicativo está na sua primeira versão
|
||||
popup.history.title=Histórico
|
||||
popup.history.selected.tooltip=Versão atual
|
||||
popup.button.yes=Sim
|
||||
popup.button.no=Não
|
||||
popup.button.cancel=Cancelar
|
||||
tray.action.manage=Gerenciar aplicativos
|
||||
tray.action.exit=Sair
|
||||
tray.action.about=Sobre
|
||||
tray.action.refreshing=Recarregando
|
||||
notification.new_updates=Atualizações
|
||||
notification.update_selected.success=aplicativo(s) atualizado(s) com sucesso
|
||||
notification.update_selected.failed=Erro ao atualizar
|
||||
notification.install.failed=instalação falhou
|
||||
notification.uninstall.failed=desinstalação falhou
|
||||
notification.downgrade.failed=Erro ao reverter versão
|
||||
flatpak.info.arch=arquitetura
|
||||
flatpak.info.branch=ramo
|
||||
flatpak.info.collection=coleção
|
||||
flatpak.info.commit=commit
|
||||
flatpak.info.date=data
|
||||
flatpak.info.description=descrição
|
||||
flatpak.info.id=id
|
||||
flatpak.info.installation=instalação
|
||||
flatpak.info.installed=instalado
|
||||
flatpak.info.license=licença
|
||||
flatpak.info.name=nome
|
||||
flatpak.info.origin=origem
|
||||
flatpak.info.parent=pai
|
||||
flatpak.info.ref=ref
|
||||
flatpak.info.runtime=runtime
|
||||
flatpak.info.sdk=sdk
|
||||
flatpak.info.subject=assunto
|
||||
flatpak.info.type=tipo
|
||||
flatpak.info.version=versão
|
||||
snap.info.commands=comandos
|
||||
snap.info.contact=contato
|
||||
snap.info.description=descrição
|
||||
snap.info.license=licença
|
||||
snap.info.revision=revisão
|
||||
snap.info.tracking=tracking
|
||||
snap.info.installed=instalado
|
||||
snap.info.publisher=publicador
|
||||
about.info.desc=Interface gráfica não oficial para gerenciamento de aplicativos Flatpak / Snap
|
||||
about.info.link=Mais informações em
|
||||
about.info.license=Licença gratuita
|
||||
about.info.rate=Se essa ferramenta é útil para você, dê uma estrela no Github para mantê-la de pé
|
||||
yes=sim
|
||||
no=não
|
||||
version.updated=atualizada
|
||||
version.outdated=desatualizada
|
||||
name=nome
|
||||
version=versão
|
||||
latest_version=última versão
|
||||
description=descrição
|
||||
type=tipo
|
||||
installed=instalado
|
||||
uninstalled=desinstalado
|
||||
not_installed=não instalado
|
||||
downgraded=versão revertida
|
||||
others=outros
|
||||
internet.required=É necessário estar conectado a Internet
|
||||
license.unset=Não definida
|
||||
updates=atualizações
|
||||
version.installed=versão instalada
|
||||
version.latest=versão mais recente
|
||||
version.installed_outdated=a versão instalada está desatualizada
|
||||
version.unknown=versão não informada
|
||||
app.name=nome do aplicativo
|
||||
warning=aviso
|
||||
snap.notification.snapd_unavailable=snapd não parece estar habilitado. os pacotes snap estarão indisponíveis.
|
||||
flatpak.notification.no_remotes=Não há repositórios (remotes) Flatpak configurados. Não será possível buscar aplicativos Flatpak.
|
||||
install=instalar
|
||||
uninstall=desinstalar
|
||||
bt.app_upgrade=Atualizar
|
||||
bt.app_not_upgrade=Não atualizar
|
||||
manage_window.upgrade_all.popup.title=Atualizar
|
||||
manage_window.upgrade_all.popup.body=Atualizar todos os aplicativos selecionados ?
|
||||
manage_window.bt_about.tooltip=Sobre
|
||||
0
bauh/util/__init__.py
Normal file
64
bauh/util/cache.py
Normal file
@@ -0,0 +1,64 @@
|
||||
from datetime import datetime, timedelta
|
||||
from threading import Lock
|
||||
|
||||
|
||||
class Cache:
|
||||
|
||||
def __init__(self, expiration_time: int):
|
||||
self.expiration_time = expiration_time
|
||||
self._cache = {}
|
||||
self.lock = Lock()
|
||||
|
||||
def is_enabled(self):
|
||||
return self.expiration_time < 0 or self.expiration_time > 0
|
||||
|
||||
def add(self, key: str, val: object):
|
||||
if key and self.is_enabled():
|
||||
self.lock.acquire()
|
||||
self._add(key, val)
|
||||
self.lock.release()
|
||||
|
||||
def _add(self, key: str, val: object):
|
||||
if key:
|
||||
self._cache[key] = {'val': val, 'expires_at': datetime.utcnow() + timedelta(seconds=self.expiration_time) if self.expiration_time > 0 else None}
|
||||
|
||||
def add_non_existing(self, key: str, val: object):
|
||||
|
||||
if key and self. is_enabled():
|
||||
self.lock.acquire()
|
||||
cur_val = self.get(key)
|
||||
|
||||
if cur_val is None:
|
||||
self._add(key, val)
|
||||
|
||||
self.lock.release()
|
||||
|
||||
def get(self, key: str):
|
||||
if key and self.is_enabled():
|
||||
val = self._cache.get(key)
|
||||
|
||||
if val:
|
||||
expiration = val.get('expires_at')
|
||||
|
||||
if expiration and expiration <= datetime.utcnow():
|
||||
self.lock.acquire()
|
||||
del self._cache[key]
|
||||
self.lock.release()
|
||||
return None
|
||||
|
||||
return val['val']
|
||||
|
||||
def delete(self, key):
|
||||
if key and self.is_enabled():
|
||||
if key in self._cache:
|
||||
self.lock.acquire()
|
||||
del self._cache[key]
|
||||
self.lock.release()
|
||||
|
||||
def keys(self):
|
||||
return set(self._cache.keys()) if self.is_enabled() else set()
|
||||
|
||||
def clean_expired(self):
|
||||
if self.is_enabled():
|
||||
for key in self.keys():
|
||||
self.get(key)
|
||||
23
bauh/util/memory.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import time
|
||||
from threading import Thread
|
||||
from typing import List
|
||||
|
||||
from bauh.util.cache import Cache
|
||||
|
||||
|
||||
class CacheCleaner(Thread):
|
||||
|
||||
def __init__(self, caches: List[Cache], check_interval: int = 15):
|
||||
super(CacheCleaner, self).__init__(daemon=True)
|
||||
self.caches = [c for c in caches if c.is_enabled()]
|
||||
self.check_interval = check_interval
|
||||
|
||||
def run(self):
|
||||
|
||||
if self.caches:
|
||||
while True:
|
||||
for cache in self.caches:
|
||||
cache.clean_expired()
|
||||
|
||||
time.sleep(self.check_interval)
|
||||
|
||||
47
bauh/util/util.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import locale
|
||||
|
||||
from bauh.core import resource
|
||||
import glob
|
||||
import re
|
||||
|
||||
HTML_RE = re.compile(r'<[^>]+>')
|
||||
|
||||
|
||||
def get_locale_keys(key: str = None):
|
||||
|
||||
locale_path = None
|
||||
|
||||
if key is None:
|
||||
current_locale = locale.getdefaultlocale()
|
||||
else:
|
||||
current_locale = [key.strip().lower()]
|
||||
|
||||
if current_locale:
|
||||
current_locale = current_locale[0]
|
||||
|
||||
locale_dir = resource.get_path('locale')
|
||||
|
||||
for locale_file in glob.glob(locale_dir + '/*'):
|
||||
name = locale_file.split('/')[-1]
|
||||
|
||||
if current_locale == name or current_locale.startswith(name + '_'):
|
||||
locale_path = locale_file
|
||||
break
|
||||
|
||||
if not locale_path:
|
||||
locale_path = resource.get_path('locale/en')
|
||||
|
||||
with open(locale_path, 'r') as f:
|
||||
locale_keys = f.readlines()
|
||||
|
||||
locale_obj = {}
|
||||
for line in locale_keys:
|
||||
if line:
|
||||
keyval = line.strip().split('=')
|
||||
locale_obj[keyval[0].strip()] = keyval[1].strip()
|
||||
|
||||
return locale_obj
|
||||
|
||||
|
||||
def strip_html(string: str):
|
||||
return HTML_RE.sub('', string)
|
||||
0
bauh/view/__init__.py
Executable file
0
bauh/view/qt/__init__.py
Executable file
71
bauh/view/qt/about.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtWidgets import QVBoxLayout, QDialog, QLabel
|
||||
|
||||
from bauh import __version__, __app_name__
|
||||
from bauh.core import resource
|
||||
|
||||
PROJECT_URL = 'https://github.com/vinifmor/' + __app_name__
|
||||
LICENSE_URL = 'https://raw.githubusercontent.com/vinifmor/{}/master/LICENSE'.format(__app_name__)
|
||||
|
||||
|
||||
class AboutDialog(QDialog):
|
||||
|
||||
def __init__(self, locale_keys: dict):
|
||||
super(AboutDialog, self).__init__()
|
||||
self.setWindowTitle(locale_keys['tray.action.about'])
|
||||
layout = QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
label_logo = QLabel(self)
|
||||
label_logo.setPixmap(QPixmap(resource.get_path('img/logo.svg')))
|
||||
label_logo.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(label_logo)
|
||||
|
||||
label_name = QLabel('{} ( {} {} )'.format(__app_name__, locale_keys['flatpak.info.version'].lower(), __version__))
|
||||
label_name.setStyleSheet('font-weight: bold;')
|
||||
label_name.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(label_name)
|
||||
|
||||
layout.addWidget(QLabel(''))
|
||||
|
||||
line_desc = QLabel(self)
|
||||
line_desc.setStyleSheet('font-size: 10px; font-weight: bold;')
|
||||
line_desc.setText(locale_keys['about.info.desc'])
|
||||
line_desc.setAlignment(Qt.AlignCenter)
|
||||
line_desc.setMinimumWidth(400)
|
||||
layout.addWidget(line_desc)
|
||||
|
||||
layout.addWidget(QLabel(''))
|
||||
|
||||
label_more_info = QLabel()
|
||||
label_more_info.setStyleSheet('font-size: 9px;')
|
||||
label_more_info.setText(locale_keys['about.info.link'] + ": <a href='{url}'>{url}</a>".format(url=PROJECT_URL))
|
||||
label_more_info.setOpenExternalLinks(True)
|
||||
label_more_info.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(label_more_info)
|
||||
|
||||
label_license = QLabel()
|
||||
label_license.setStyleSheet('font-size: 9px;')
|
||||
label_license.setText("<a href='{}'>{}</a>".format(LICENSE_URL, locale_keys['about.info.license']))
|
||||
label_license.setOpenExternalLinks(True)
|
||||
label_license.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(label_license)
|
||||
|
||||
layout.addWidget(QLabel(''))
|
||||
|
||||
label_rate = QLabel()
|
||||
label_rate.setStyleSheet('font-size: 9px; font-weight: bold;')
|
||||
label_rate.setText(locale_keys['about.info.rate'] + ' :)')
|
||||
label_rate.setOpenExternalLinks(True)
|
||||
label_rate.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(label_rate)
|
||||
|
||||
layout.addWidget(QLabel(''))
|
||||
|
||||
self.adjustSize()
|
||||
self.setFixedSize(self.size())
|
||||
|
||||
def closeEvent(self, event):
|
||||
event.ignore()
|
||||
self.hide()
|
||||
389
bauh/view/qt/apps_table.py
Normal file
@@ -0,0 +1,389 @@
|
||||
import os
|
||||
from threading import Lock
|
||||
from typing import List
|
||||
|
||||
from PyQt5.QtCore import Qt, QUrl
|
||||
from PyQt5.QtGui import QPixmap, QIcon, QCursor
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
|
||||
from PyQt5.QtWidgets import QTableWidget, QTableView, QMenu, QAction, QTableWidgetItem, QWidget, \
|
||||
QHeaderView, QLabel, QHBoxLayout, QPushButton, QToolButton, QToolBar
|
||||
|
||||
from bauh.core import resource
|
||||
from bauh.core.model import ApplicationStatus
|
||||
from bauh.util import util
|
||||
from bauh.util.cache import Cache
|
||||
from bauh.view.qt import dialog
|
||||
from bauh.view.qt.view_model import ApplicationView, ApplicationViewStatus
|
||||
|
||||
INSTALL_BT_STYLE = 'background: {back}; color: white; font-size: 10px; font-weight: bold'
|
||||
|
||||
|
||||
class IconButton(QWidget):
|
||||
|
||||
def __init__(self, icon_path: str, action, background: str = None, align: int = Qt.AlignCenter, tooltip: str = None):
|
||||
super(IconButton, self).__init__()
|
||||
self.bt = QToolButton()
|
||||
self.bt.setIcon(QIcon(icon_path))
|
||||
self.bt.clicked.connect(action)
|
||||
|
||||
if background:
|
||||
self.bt.setStyleSheet('QToolButton { color: white; background: ' + background + '}')
|
||||
|
||||
if tooltip:
|
||||
self.bt.setToolTip(tooltip)
|
||||
|
||||
layout = QHBoxLayout()
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setAlignment(align)
|
||||
layout.addWidget(self.bt)
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
class UpdateToggleButton(QWidget):
|
||||
|
||||
def __init__(self, app_view: ApplicationView, root: QWidget, locale_keys: dict, checked: bool = True):
|
||||
super(UpdateToggleButton, self).__init__()
|
||||
|
||||
self.app_view = app_view
|
||||
self.root = root
|
||||
|
||||
layout = QHBoxLayout()
|
||||
layout.setContentsMargins(2, 2, 2, 0)
|
||||
layout.setAlignment(Qt.AlignCenter)
|
||||
self.setLayout(layout)
|
||||
|
||||
self.bt = QToolButton()
|
||||
self.bt.setCheckable(True)
|
||||
self.bt.clicked.connect(self.change_state)
|
||||
|
||||
self.bt.setIcon(QIcon(resource.get_path('img/app_update.svg')))
|
||||
self.bt.setStyleSheet('QToolButton { background: #4EC306 } ' +
|
||||
'QToolButton:checked { background: gray }')
|
||||
layout.addWidget(self.bt)
|
||||
|
||||
self.setToolTip(locale_keys['manage_window.apps_table.upgrade_toggle.tooltip'])
|
||||
|
||||
if not checked:
|
||||
self.bt.click()
|
||||
|
||||
def change_state(self, not_checked: bool):
|
||||
self.app_view.update_checked = not not_checked
|
||||
self.root.change_update_state(change_filters=False)
|
||||
|
||||
|
||||
class AppsTable(QTableWidget):
|
||||
|
||||
def __init__(self, parent: QWidget, icon_cache: Cache, disk_cache: bool, download_icons: bool):
|
||||
super(AppsTable, self).__init__()
|
||||
self.setParent(parent)
|
||||
self.window = parent
|
||||
self.disk_cache = disk_cache
|
||||
self.download_icons = download_icons
|
||||
self.column_names = ['' for _ in range(7)]
|
||||
self.setColumnCount(len(self.column_names))
|
||||
self.setFocusPolicy(Qt.NoFocus)
|
||||
self.setShowGrid(False)
|
||||
self.verticalHeader().setVisible(False)
|
||||
self.horizontalHeader().setVisible(False)
|
||||
self.setSelectionBehavior(QTableView.SelectRows)
|
||||
self.setHorizontalHeaderLabels(self.column_names)
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
self.icon_flathub = QIcon(resource.get_path('img/flathub.svg'))
|
||||
self.icon_logo = QIcon(resource.get_path('img/logo.svg'))
|
||||
|
||||
self.network_man = QNetworkAccessManager()
|
||||
self.network_man.finished.connect(self._load_icon_and_cache)
|
||||
|
||||
self.icon_cache = icon_cache
|
||||
self.lock_async_data = Lock()
|
||||
|
||||
def has_any_settings(self, app_v: ApplicationView):
|
||||
return app_v.model.can_be_refreshed() or \
|
||||
app_v.model.has_history() or \
|
||||
app_v.model.can_be_downgraded()
|
||||
|
||||
def show_app_settings(self, app: ApplicationView):
|
||||
menu_row = QMenu()
|
||||
|
||||
if app.model.installed:
|
||||
if app.model.can_be_refreshed():
|
||||
action_history = QAction(self.window.locale_keys["manage_window.apps_table.row.actions.refresh"])
|
||||
action_history.setIcon(QIcon(resource.get_path('img/refresh.svg')))
|
||||
|
||||
def refresh():
|
||||
self.window.refresh(app)
|
||||
|
||||
action_history.triggered.connect(refresh)
|
||||
menu_row.addAction(action_history)
|
||||
|
||||
if app.model.has_history():
|
||||
action_history = QAction(self.window.locale_keys["manage_window.apps_table.row.actions.history"])
|
||||
action_history.setIcon(QIcon(resource.get_path('img/history.svg')))
|
||||
|
||||
def show_history():
|
||||
self.window.get_app_history(app)
|
||||
|
||||
action_history.triggered.connect(show_history)
|
||||
menu_row.addAction(action_history)
|
||||
|
||||
if app.model.can_be_downgraded():
|
||||
action_downgrade = QAction(self.window.locale_keys["manage_window.apps_table.row.actions.downgrade"])
|
||||
|
||||
def downgrade():
|
||||
if dialog.ask_confirmation(
|
||||
title=self.window.locale_keys['manage_window.apps_table.row.actions.downgrade'],
|
||||
body=self.window.locale_keys['manage_window.apps_table.row.actions.downgrade.popup.body'].format(app.model.base_data.name),
|
||||
locale_keys=self.window.locale_keys):
|
||||
self.window.downgrade_app(app)
|
||||
|
||||
action_downgrade.triggered.connect(downgrade)
|
||||
action_downgrade.setIcon(QIcon(resource.get_path('img/downgrade.svg')))
|
||||
menu_row.addAction(action_downgrade)
|
||||
|
||||
menu_row.adjustSize()
|
||||
menu_row.popup(QCursor.pos())
|
||||
menu_row.exec_()
|
||||
|
||||
def fill_async_data(self):
|
||||
if self.window.apps:
|
||||
|
||||
for idx, app_v in enumerate(self.window.apps):
|
||||
|
||||
if app_v.visible and app_v.status == ApplicationViewStatus.LOADING and app_v.model.status == ApplicationStatus.READY:
|
||||
|
||||
if self.download_icons:
|
||||
self.network_man.get(QNetworkRequest(QUrl(app_v.model.base_data.icon_url)))
|
||||
|
||||
app_name = self.item(idx, 0).text()
|
||||
|
||||
if not app_name or app_name == '...':
|
||||
self.item(idx, 0).setText(app_v.model.base_data.name)
|
||||
|
||||
self._set_col_version(idx, app_v)
|
||||
self._set_col_description(idx, app_v)
|
||||
app_v.status = ApplicationViewStatus.READY
|
||||
|
||||
self.window.resize_and_center()
|
||||
|
||||
def get_selected_app(self) -> ApplicationView:
|
||||
return self.window.apps[self.currentRow()]
|
||||
|
||||
def get_selected_app_icon(self) -> QIcon:
|
||||
return self.item(self.currentRow(), 0).icon()
|
||||
|
||||
def _uninstall_app(self, app_v: ApplicationView):
|
||||
if dialog.ask_confirmation(title=self.window.locale_keys['manage_window.apps_table.row.actions.uninstall.popup.title'],
|
||||
body=self.window.locale_keys['manage_window.apps_table.row.actions.uninstall.popup.body'].format(app_v),
|
||||
locale_keys=self.window.locale_keys):
|
||||
self.window.uninstall_app(app_v)
|
||||
|
||||
def _downgrade_app(self):
|
||||
selected_app = self.get_selected_app()
|
||||
|
||||
if dialog.ask_confirmation(title=self.window.locale_keys['manage_window.apps_table.row.actions.downgrade'],
|
||||
body=self.window.locale_keys['manage_window.apps_table.row.actions.downgrade.popup.body'].format(selected_app.model.base_data.name),
|
||||
locale_keys=self.window.locale_keys):
|
||||
self.window.downgrade_app(selected_app)
|
||||
|
||||
def _refresh_app(self):
|
||||
self.window.refresh(self.get_selected_app())
|
||||
|
||||
def _get_app_info(self):
|
||||
self.window.get_app_info(self.get_selected_app())
|
||||
|
||||
def _get_app_history(self):
|
||||
self.window.get_app_history(self.get_selected_app())
|
||||
|
||||
def _install_app(self, app_v: ApplicationView):
|
||||
|
||||
if dialog.ask_confirmation(
|
||||
title=self.window.locale_keys['manage_window.apps_table.row.actions.install.popup.title'],
|
||||
body=self.window.locale_keys['manage_window.apps_table.row.actions.install.popup.body'].format(app_v),
|
||||
locale_keys=self.window.locale_keys):
|
||||
|
||||
self.window.install_app(app_v)
|
||||
|
||||
def _load_icon_and_cache(self, http_response):
|
||||
icon_url = http_response.url().toString()
|
||||
|
||||
icon_data = self.icon_cache.get(icon_url)
|
||||
icon_was_cached = True
|
||||
|
||||
if not icon_data:
|
||||
icon_bytes = http_response.readAll()
|
||||
|
||||
if not icon_bytes:
|
||||
return
|
||||
|
||||
icon_was_cached = False
|
||||
pixmap = QPixmap()
|
||||
|
||||
pixmap.loadFromData(icon_bytes)
|
||||
icon = QIcon(pixmap)
|
||||
icon_data = {'icon': icon, 'bytes': icon_bytes}
|
||||
self.icon_cache.add(icon_url, icon_data)
|
||||
|
||||
for idx, app in enumerate(self.window.apps):
|
||||
if app.model.base_data.icon_url == icon_url:
|
||||
col_name = self.item(idx, 0)
|
||||
col_name.setIcon(icon_data['icon'])
|
||||
|
||||
if self.disk_cache and app.model.supports_disk_cache():
|
||||
if not icon_was_cached or not os.path.exists(app.model.get_disk_icon_path()):
|
||||
self.window.manager.cache_to_disk(app=app.model, icon_bytes=icon_data['bytes'], only_icon=True)
|
||||
|
||||
def update_apps(self, app_views: List[ApplicationView], update_check_enabled: bool = True):
|
||||
self.setRowCount(len(app_views) if app_views else 0)
|
||||
self.setEnabled(True)
|
||||
|
||||
if app_views:
|
||||
for idx, app_v in enumerate(app_views):
|
||||
self._set_col_name(idx, app_v)
|
||||
self._set_col_version(idx, app_v)
|
||||
self._set_col_description(idx, app_v)
|
||||
self._set_col_type(idx, app_v)
|
||||
self._set_col_installed(idx, app_v)
|
||||
|
||||
self._set_col_settings(idx, app_v)
|
||||
|
||||
col_update = None
|
||||
|
||||
if update_check_enabled and app_v.model.update:
|
||||
col_update = UpdateToggleButton(app_v, self.window, self.window.locale_keys, app_v.model.update)
|
||||
|
||||
self.setCellWidget(idx, 6, col_update)
|
||||
|
||||
def _gen_row_button(self, text: str, style: str, callback) -> QWidget:
|
||||
col = QWidget()
|
||||
col_bt = QPushButton()
|
||||
col_bt.setText(text)
|
||||
col_bt.setStyleSheet('QPushButton { ' + style + '}')
|
||||
col_bt.clicked.connect(callback)
|
||||
|
||||
layout = QHBoxLayout()
|
||||
layout.setContentsMargins(2, 2, 2, 0)
|
||||
layout.setAlignment(Qt.AlignCenter)
|
||||
layout.addWidget(col_bt)
|
||||
col.setLayout(layout)
|
||||
|
||||
return col
|
||||
|
||||
def _set_col_installed(self, idx: int, app_v: ApplicationView):
|
||||
|
||||
if app_v.model.installed:
|
||||
if app_v.model.can_be_uninstalled():
|
||||
def uninstall():
|
||||
self._uninstall_app(app_v)
|
||||
|
||||
col = self._gen_row_button(self.window.locale_keys['uninstall'].capitalize(), INSTALL_BT_STYLE.format(back='#cc0000'), uninstall)
|
||||
else:
|
||||
col = QLabel()
|
||||
col.setPixmap((QPixmap(resource.get_path('img/checked.svg'))))
|
||||
col.setAlignment(Qt.AlignCenter)
|
||||
col.setToolTip(self.window.locale_keys['installed'])
|
||||
elif app_v.model.can_be_installed():
|
||||
def install():
|
||||
self._install_app(app_v)
|
||||
col = self._gen_row_button(self.window.locale_keys['install'].capitalize(), INSTALL_BT_STYLE.format(back='#088A08'), install)
|
||||
else:
|
||||
col = None
|
||||
|
||||
self.setCellWidget(idx, 4, col)
|
||||
|
||||
def _set_col_type(self, idx: int, app_v: ApplicationView):
|
||||
col_type = QLabel()
|
||||
pixmap = QPixmap(app_v.model.get_default_icon_path())
|
||||
col_type.setPixmap(pixmap.scaled(16, 16, Qt.KeepAspectRatio, Qt.SmoothTransformation))
|
||||
col_type.setAlignment(Qt.AlignCenter)
|
||||
col_type.setToolTip('{}: {}'.format(self.window.locale_keys['type'], app_v.model.get_type()))
|
||||
self.setCellWidget(idx, 3, col_type)
|
||||
|
||||
def _set_col_version(self, idx: int, app_v: ApplicationView):
|
||||
label_version = QLabel(app_v.model.base_data.version if app_v.model.base_data.version else '?')
|
||||
label_version.setAlignment(Qt.AlignCenter)
|
||||
|
||||
col_version = QWidget()
|
||||
col_version.setLayout(QHBoxLayout())
|
||||
col_version.layout().addWidget(label_version)
|
||||
|
||||
if app_v.model.base_data.version:
|
||||
tooltip = self.window.locale_keys['version.installed'] if app_v.model.installed else self.window.locale_keys['version']
|
||||
else:
|
||||
tooltip = self.window.locale_keys['version.unknown']
|
||||
|
||||
if app_v.model.update:
|
||||
label_version.setStyleSheet("color: #4EC306; font-weight: bold")
|
||||
tooltip = self.window.locale_keys['version.installed_outdated']
|
||||
|
||||
if app_v.model.installed and app_v.model.base_data.version and app_v.model.base_data.latest_version and app_v.model.base_data.version < app_v.model.base_data.latest_version:
|
||||
tooltip = '{}. {}: {}'.format(tooltip, self.window.locale_keys['version.latest'], app_v.model.base_data.latest_version)
|
||||
label_version.setText(label_version.text() + ' > {}'.format(app_v.model.base_data.latest_version))
|
||||
|
||||
col_version.setToolTip(tooltip)
|
||||
self.setCellWidget(idx, 1, col_version)
|
||||
|
||||
def _set_col_name(self, idx: int, app_v: ApplicationView):
|
||||
col = QTableWidgetItem()
|
||||
col.setText(app_v.model.base_data.name if app_v.model.base_data.name else '...')
|
||||
col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
col.setToolTip(self.window.locale_keys['app.name'].lower())
|
||||
|
||||
if self.disk_cache and app_v.model.supports_disk_cache() and os.path.exists(app_v.model.get_disk_icon_path()):
|
||||
with open(app_v.model.get_disk_icon_path(), 'rb') as f:
|
||||
icon_bytes = f.read()
|
||||
pixmap = QPixmap()
|
||||
pixmap.loadFromData(icon_bytes)
|
||||
icon = QIcon(pixmap)
|
||||
self.icon_cache.add_non_existing(app_v.model.base_data.icon_url, {'icon': icon, 'bytes': icon_bytes})
|
||||
|
||||
elif not app_v.model.base_data.icon_url:
|
||||
icon = QIcon(app_v.model.get_default_icon_path())
|
||||
else:
|
||||
icon_data = self.icon_cache.get(app_v.model.base_data.icon_url)
|
||||
icon = icon_data['icon'] if icon_data else QIcon(app_v.model.get_default_icon_path())
|
||||
|
||||
col.setIcon(icon)
|
||||
self.setItem(idx, 0, col)
|
||||
|
||||
def _set_col_description(self, idx: int, app_v: ApplicationView):
|
||||
col = QTableWidgetItem()
|
||||
col.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
|
||||
if app_v.model.base_data.description is not None or app_v.model.is_library() or app_v.model.status == ApplicationStatus.READY:
|
||||
desc = app_v.model.base_data.description
|
||||
else:
|
||||
desc = '...'
|
||||
|
||||
if desc and desc != '...':
|
||||
desc = util.strip_html(desc[0:25]) + '...'
|
||||
|
||||
col.setText(desc)
|
||||
|
||||
if app_v.model.base_data.description:
|
||||
col.setToolTip(app_v.model.base_data.description)
|
||||
|
||||
self.setItem(idx, 2, col)
|
||||
|
||||
def _set_col_settings(self, idx: int, app_v: ApplicationView):
|
||||
tb = QToolBar()
|
||||
|
||||
if app_v.model.has_info():
|
||||
|
||||
def get_info():
|
||||
self.window.get_app_info(app_v)
|
||||
|
||||
tb.addWidget(IconButton(icon_path=resource.get_path('img/app_info.svg'), action=get_info, background='#2E68D3'))
|
||||
|
||||
def handle_click():
|
||||
self.show_app_settings(app_v)
|
||||
|
||||
if self.has_any_settings(app_v):
|
||||
bt = IconButton(icon_path=resource.get_path('img/app_settings.svg'), action=handle_click, background='#12ABAB')
|
||||
tb.addWidget(bt)
|
||||
|
||||
self.setCellWidget(idx, 5, tb)
|
||||
|
||||
def change_headers_policy(self, policy: QHeaderView = QHeaderView.ResizeToContents):
|
||||
header_horizontal = self.horizontalHeader()
|
||||
for i in range(self.columnCount()):
|
||||
header_horizontal.setSectionResizeMode(i, policy)
|
||||
46
bauh/view/qt/dialog.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from bauh.core import resource
|
||||
|
||||
|
||||
def show_error(title: str, body: str, icon: QIcon = QIcon(resource.get_path('img/logo.svg'))):
|
||||
error_msg = QMessageBox()
|
||||
error_msg.setIcon(QMessageBox.Critical)
|
||||
error_msg.setWindowTitle(title)
|
||||
error_msg.setText(body)
|
||||
|
||||
if icon:
|
||||
error_msg.setWindowIcon(icon)
|
||||
|
||||
error_msg.exec_()
|
||||
|
||||
|
||||
def show_warning(title: str, body: str, icon: QIcon = QIcon(resource.get_path('img/logo.svg'))):
|
||||
msg = QMessageBox()
|
||||
msg.setIcon(QMessageBox.Warning)
|
||||
msg.setWindowTitle(title)
|
||||
msg.setText(body)
|
||||
|
||||
if icon:
|
||||
msg.setWindowIcon(icon)
|
||||
|
||||
msg.exec_()
|
||||
|
||||
|
||||
def ask_confirmation(title: str, body: str, locale_keys: dict, icon: QIcon = QIcon(resource.get_path('img/logo.svg'))):
|
||||
dialog_confirmation = QMessageBox()
|
||||
dialog_confirmation.setIcon(QMessageBox.Question)
|
||||
dialog_confirmation.setWindowTitle(title)
|
||||
dialog_confirmation.setText(body)
|
||||
dialog_confirmation.setStyleSheet('QLabel { margin-right: 25px; }')
|
||||
|
||||
bt_yes = dialog_confirmation.addButton(locale_keys['popup.button.yes'], QMessageBox.YesRole)
|
||||
dialog_confirmation.addButton(locale_keys['popup.button.no'], QMessageBox.NoRole)
|
||||
|
||||
if icon:
|
||||
dialog_confirmation.setWindowIcon(icon)
|
||||
|
||||
dialog_confirmation.exec_()
|
||||
|
||||
return dialog_confirmation.clickedButton() == bt_yes
|
||||
60
bauh/view/qt/history.py
Normal file
@@ -0,0 +1,60 @@
|
||||
import operator
|
||||
from functools import reduce
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QColor
|
||||
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QTableWidget, QTableWidgetItem, QHeaderView
|
||||
|
||||
from bauh.util.cache import Cache
|
||||
|
||||
|
||||
class HistoryDialog(QDialog):
|
||||
|
||||
def __init__(self, app: dict, icon_cache: Cache, locale_keys: dict):
|
||||
super(HistoryDialog, self).__init__()
|
||||
|
||||
self.setWindowTitle('{} - {} '.format(locale_keys['popup.history.title'], app['model'].base_data.name))
|
||||
|
||||
layout = QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
table_history = QTableWidget()
|
||||
table_history.setFocusPolicy(Qt.NoFocus)
|
||||
table_history.setShowGrid(False)
|
||||
table_history.verticalHeader().setVisible(False)
|
||||
table_history.setAlternatingRowColors(True)
|
||||
|
||||
table_history.setColumnCount(len(app['history'][0]))
|
||||
table_history.setRowCount(len(app['history']))
|
||||
table_history.setHorizontalHeaderLabels([locale_keys['flatpak.info.' + key].capitalize() for key in sorted(app['history'][0].keys())])
|
||||
|
||||
for row, commit in enumerate(app['history']):
|
||||
|
||||
current_app_commit = app['model'].commit == commit['commit']
|
||||
|
||||
for col, key in enumerate(sorted(commit.keys())):
|
||||
item = QTableWidgetItem()
|
||||
item.setText(commit[key])
|
||||
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
|
||||
if current_app_commit:
|
||||
item.setBackground(QColor('#ffbf00' if row != 0 else '#32CD32'))
|
||||
tip = '{}. {}.'.format(locale_keys['popup.history.selected.tooltip'], locale_keys['version.{}'.format('updated'if row == 0 else 'outdated')].capitalize())
|
||||
|
||||
item.setToolTip(tip)
|
||||
|
||||
table_history.setItem(row, col, item)
|
||||
|
||||
layout.addWidget(table_history)
|
||||
|
||||
header_horizontal = table_history.horizontalHeader()
|
||||
for i in range(0, table_history.columnCount()):
|
||||
header_horizontal.setSectionResizeMode(i, QHeaderView.Stretch)
|
||||
|
||||
new_width = reduce(operator.add, [table_history.columnWidth(i) for i in range(table_history.columnCount())])
|
||||
self.resize(new_width, table_history.height())
|
||||
|
||||
icon_data = icon_cache.get(app['model'].base_data.icon_url)
|
||||
|
||||
if icon_data and icon_data.get('icon'):
|
||||
self.setWindowIcon(icon_data.get('icon'))
|
||||
55
bauh/view/qt/info.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from PyQt5.QtCore import QSize
|
||||
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QFormLayout, QGroupBox, \
|
||||
QLineEdit, QLabel
|
||||
|
||||
from bauh.util import util
|
||||
from bauh.util.cache import Cache
|
||||
|
||||
IGNORED_ATTRS = {'name', '__app__'}
|
||||
|
||||
|
||||
class InfoDialog(QDialog):
|
||||
|
||||
def __init__(self, app: dict, icon_cache: Cache, locale_keys: dict, screen_size: QSize()):
|
||||
super(InfoDialog, self).__init__()
|
||||
self.setWindowTitle(app['name'])
|
||||
self.screen_size = screen_size
|
||||
layout = QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
gbox_info = QGroupBox()
|
||||
gbox_info.setMaximumHeight(self.screen_size.height() - self.screen_size.height() * 0.1)
|
||||
gbox_info_layout = QFormLayout()
|
||||
gbox_info.setLayout(gbox_info_layout)
|
||||
|
||||
layout.addWidget(gbox_info)
|
||||
|
||||
icon_data = icon_cache.get(app['__app__'].model.base_data.icon_url)
|
||||
|
||||
if icon_data and icon_data.get('icon'):
|
||||
self.setWindowIcon(icon_data.get('icon'))
|
||||
|
||||
for attr in sorted(app.keys()):
|
||||
if attr not in IGNORED_ATTRS and app[attr]:
|
||||
val = app[attr]
|
||||
text = QLineEdit()
|
||||
text.setToolTip(val)
|
||||
|
||||
if attr == 'license' and val.strip() == 'unset':
|
||||
val = locale_keys['license.unset']
|
||||
|
||||
if attr == 'description':
|
||||
val = util.strip_html(val)
|
||||
val = val[0:40] + '...'
|
||||
|
||||
text.setText(val)
|
||||
text.setCursorPosition(0)
|
||||
text.setStyleSheet("width: 400px")
|
||||
text.setReadOnly(True)
|
||||
|
||||
label = QLabel("{}: ".format(locale_keys.get(app['__app__'].model.get_type() + '.info.' + attr, attr)).capitalize())
|
||||
label.setStyleSheet("font-weight: bold")
|
||||
|
||||
gbox_info_layout.addRow(label, text)
|
||||
|
||||
self.adjustSize()
|
||||
45
bauh/view/qt/root.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import io
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from PyQt5.QtWidgets import QInputDialog, QLineEdit
|
||||
|
||||
from bauh.view.qt.dialog import show_error
|
||||
|
||||
|
||||
def is_root():
|
||||
return os.getuid() == 0
|
||||
|
||||
|
||||
def ask_root_password(locale_keys: dict):
|
||||
|
||||
dialog_pwd = QInputDialog()
|
||||
dialog_pwd.setInputMode(QInputDialog.TextInput)
|
||||
dialog_pwd.setTextEchoMode(QLineEdit.Password)
|
||||
dialog_pwd.setWindowTitle(locale_keys['popup.root.title'])
|
||||
dialog_pwd.setLabelText(locale_keys['popup.root.password'] + ':')
|
||||
dialog_pwd.setCancelButtonText(locale_keys['popup.button.cancel'])
|
||||
dialog_pwd.resize(400, 200)
|
||||
|
||||
ok = dialog_pwd.exec_()
|
||||
|
||||
if ok:
|
||||
if not validate_password(dialog_pwd.textValue()):
|
||||
show_error(title=locale_keys['popup.root.bad_password.title'],
|
||||
body=locale_keys['popup.root.bad_password.body'])
|
||||
ok = False
|
||||
|
||||
return dialog_pwd.textValue(), ok
|
||||
|
||||
|
||||
def validate_password(password: str) -> bool:
|
||||
proc = subprocess.Popen('sudo -k && echo {} | sudo -S whoami'.format(password),
|
||||
shell=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
bufsize=-1)
|
||||
stream = os._wrap_close(io.TextIOWrapper(proc.stdout), proc)
|
||||
res = stream.read()
|
||||
stream.close()
|
||||
|
||||
return bool(res.strip())
|
||||
128
bauh/view/qt/systray.py
Executable file
@@ -0,0 +1,128 @@
|
||||
import time
|
||||
from threading import Lock, Thread
|
||||
from typing import List
|
||||
|
||||
from PyQt5.QtCore import QThread, pyqtSignal, QCoreApplication, Qt
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QSystemTrayIcon, QMenu
|
||||
|
||||
from bauh import __app_name__
|
||||
from bauh.core import resource, system
|
||||
from bauh.core.controller import ApplicationManager
|
||||
from bauh.core.model import ApplicationUpdate
|
||||
from bauh.view.qt.about import AboutDialog
|
||||
from bauh.view.qt.window import ManageWindow
|
||||
|
||||
|
||||
class UpdateCheck(QThread):
|
||||
|
||||
signal = pyqtSignal(list)
|
||||
|
||||
def __init__(self, manager: ApplicationManager, check_interval: int, parent=None):
|
||||
super(UpdateCheck, self).__init__(parent)
|
||||
self.check_interval = check_interval
|
||||
self.manager = manager
|
||||
|
||||
def run(self):
|
||||
|
||||
while True:
|
||||
updates = self.manager.list_updates()
|
||||
self.signal.emit(updates)
|
||||
time.sleep(self.check_interval)
|
||||
|
||||
|
||||
class TrayIcon(QSystemTrayIcon):
|
||||
|
||||
def __init__(self, locale_keys: dict, manager: ApplicationManager, manage_window: ManageWindow, check_interval: int = 60, update_notification: bool = True):
|
||||
super(TrayIcon, self).__init__()
|
||||
self.locale_keys = locale_keys
|
||||
self.manager = manager
|
||||
|
||||
self.icon_default = QIcon(resource.get_path('img/logo.png'))
|
||||
self.icon_update = QIcon(resource.get_path('img/logo_update.png'))
|
||||
self.setIcon(self.icon_default)
|
||||
|
||||
self.menu = QMenu()
|
||||
|
||||
self.action_manage = self.menu.addAction(self.locale_keys['tray.action.manage'])
|
||||
self.action_manage.triggered.connect(self.show_manage_window)
|
||||
|
||||
self.action_about = self.menu.addAction(self.locale_keys['tray.action.about'])
|
||||
self.action_about.triggered.connect(self.show_about)
|
||||
|
||||
self.action_exit = self.menu.addAction(self.locale_keys['tray.action.exit'])
|
||||
self.action_exit.triggered.connect(lambda: QCoreApplication.exit())
|
||||
|
||||
self.setContextMenu(self.menu)
|
||||
|
||||
self.manage_window = None
|
||||
self.dialog_about = None
|
||||
self.check_thread = UpdateCheck(check_interval=check_interval, manager=self.manager)
|
||||
self.check_thread.signal.connect(self.notify_updates)
|
||||
self.check_thread.start()
|
||||
|
||||
self.last_updates = set()
|
||||
self.update_notification = update_notification
|
||||
self.lock_notify = Lock()
|
||||
|
||||
self.activated.connect(self.handle_click)
|
||||
self.set_default_tooltip()
|
||||
|
||||
self.manage_window = manage_window
|
||||
|
||||
def set_default_tooltip(self):
|
||||
self.setToolTip('{} ({})'.format(self.locale_keys['manage_window.title'], __app_name__).lower())
|
||||
|
||||
def handle_click(self, reason):
|
||||
if reason == self.Trigger:
|
||||
self.show_manage_window()
|
||||
|
||||
def verify_updates(self, notify_user: bool = True):
|
||||
Thread(target=self._verify_updates, args=(notify_user,)).start()
|
||||
|
||||
def _verify_updates(self, notify_user: bool):
|
||||
self.notify_updates(self.manager.list_updates(), notify_user=notify_user)
|
||||
|
||||
def notify_updates(self, updates: List[ApplicationUpdate], notify_user: bool = True):
|
||||
|
||||
self.lock_notify.acquire()
|
||||
|
||||
try:
|
||||
if len(updates) > 0:
|
||||
update_keys = {'{}:{}:{}'.format(up.type, up.id, up.version) for up in updates}
|
||||
|
||||
new_icon = self.icon_update
|
||||
|
||||
if update_keys.difference(self.last_updates):
|
||||
self.last_updates = update_keys
|
||||
msg = '{}: {}'.format(self.locale_keys['notification.new_updates'], len(updates))
|
||||
self.setToolTip(msg)
|
||||
|
||||
if self.update_notification and notify_user:
|
||||
system.notify_user(msg)
|
||||
|
||||
else:
|
||||
self.last_updates.clear()
|
||||
new_icon = self.icon_default
|
||||
self.set_default_tooltip()
|
||||
|
||||
if self.icon().cacheKey() != new_icon.cacheKey(): # changes the icon if needed
|
||||
self.setIcon(new_icon)
|
||||
|
||||
finally:
|
||||
self.lock_notify.release()
|
||||
|
||||
def show_manage_window(self):
|
||||
if self.manage_window.isMinimized():
|
||||
self.manage_window.setWindowState(Qt.WindowNoState)
|
||||
elif not self.manage_window.isVisible():
|
||||
self.manage_window.refresh_apps()
|
||||
self.manage_window.show()
|
||||
|
||||
def show_about(self):
|
||||
|
||||
if self.dialog_about is None:
|
||||
self.dialog_about = AboutDialog(self.locale_keys)
|
||||
|
||||
if self.dialog_about.isHidden():
|
||||
self.dialog_about.show()
|
||||
364
bauh/view/qt/thread.py
Normal file
@@ -0,0 +1,364 @@
|
||||
import subprocess
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List
|
||||
|
||||
import requests
|
||||
from PyQt5.QtCore import QThread, pyqtSignal
|
||||
|
||||
from bauh.core.controller import ApplicationManager
|
||||
from bauh.core.exception import NoInternetException
|
||||
from bauh.core.model import ApplicationStatus
|
||||
from bauh.core.system import BauhProcess
|
||||
from bauh.util.cache import Cache
|
||||
from bauh.view.qt import dialog
|
||||
from bauh.view.qt.view_model import ApplicationView
|
||||
|
||||
|
||||
class AsyncAction(QThread):
|
||||
|
||||
def notify_subproc_outputs(self, proc: BauhProcess, signal) -> bool:
|
||||
"""
|
||||
:param subproc:
|
||||
:param signal:
|
||||
:param success:
|
||||
:return: if the subprocess succeeded
|
||||
"""
|
||||
signal.emit(' '.join(proc.subproc.args) + '\n')
|
||||
|
||||
success, already_succeeded = True, False
|
||||
|
||||
for output in proc.subproc.stdout:
|
||||
line = output.decode().strip()
|
||||
if line:
|
||||
signal.emit(line)
|
||||
|
||||
if proc.success_pgrase and proc.success_pgrase in line:
|
||||
already_succeeded = True
|
||||
|
||||
if already_succeeded:
|
||||
return True
|
||||
|
||||
for output in proc.subproc.stderr:
|
||||
line = output.decode().strip()
|
||||
if line:
|
||||
if proc.wrong_error_phrase and proc.wrong_error_phrase in line:
|
||||
continue
|
||||
else:
|
||||
success = False
|
||||
signal.emit(line)
|
||||
|
||||
return success
|
||||
|
||||
|
||||
class UpdateSelectedApps(AsyncAction):
|
||||
|
||||
signal_finished = pyqtSignal(bool, int)
|
||||
signal_status = pyqtSignal(str)
|
||||
signal_output = pyqtSignal(str)
|
||||
|
||||
def __init__(self, manager: ApplicationManager, apps_to_update: List[ApplicationView] = None):
|
||||
super(UpdateSelectedApps, self).__init__()
|
||||
self.apps_to_update = apps_to_update
|
||||
self.manager = manager
|
||||
|
||||
def run(self):
|
||||
|
||||
success = False
|
||||
|
||||
for app in self.apps_to_update:
|
||||
self.signal_status.emit(app.model.base_data.name)
|
||||
process = self.manager.update_and_stream(app.model)
|
||||
success = self.notify_subproc_outputs(process, self.signal_output)
|
||||
|
||||
if not success:
|
||||
break
|
||||
else:
|
||||
self.signal_output.emit('\n')
|
||||
|
||||
self.signal_finished.emit(success, len(self.apps_to_update))
|
||||
self.apps_to_update = None
|
||||
|
||||
|
||||
class RefreshApps(QThread):
|
||||
|
||||
signal = pyqtSignal(list)
|
||||
|
||||
def __init__(self, manager: ApplicationManager):
|
||||
super(RefreshApps, self).__init__()
|
||||
self.manager = manager
|
||||
|
||||
def run(self):
|
||||
self.signal.emit(self.manager.read_installed())
|
||||
|
||||
|
||||
class UninstallApp(AsyncAction):
|
||||
signal_finished = pyqtSignal(object)
|
||||
signal_output = pyqtSignal(str)
|
||||
|
||||
def __init__(self, manager: ApplicationManager, icon_cache: Cache, app: ApplicationView = None):
|
||||
super(UninstallApp, self).__init__()
|
||||
self.app = app
|
||||
self.manager = manager
|
||||
self.icon_cache = icon_cache
|
||||
self.root_password = None
|
||||
|
||||
def run(self):
|
||||
if self.app:
|
||||
process = self.manager.uninstall_and_stream(self.app.model, self.root_password)
|
||||
success = self.notify_subproc_outputs(process, self.signal_output)
|
||||
|
||||
if success:
|
||||
self.icon_cache.delete(self.app.model.base_data.icon_url)
|
||||
self.manager.clean_cache_for(self.app.model)
|
||||
|
||||
self.signal_finished.emit(self.app if success else None)
|
||||
self.app = None
|
||||
self.root_password = None
|
||||
|
||||
|
||||
class DowngradeApp(AsyncAction):
|
||||
signal_finished = pyqtSignal(bool)
|
||||
signal_output = pyqtSignal(str)
|
||||
|
||||
def __init__(self, manager: ApplicationManager, locale_keys: dict, app: ApplicationView = None):
|
||||
super(DowngradeApp, self).__init__()
|
||||
self.manager = manager
|
||||
self.app = app
|
||||
self.locale_keys = locale_keys
|
||||
self.root_password = None
|
||||
|
||||
def run(self):
|
||||
if self.app:
|
||||
|
||||
success = False
|
||||
try:
|
||||
process = self.manager.downgrade_app(self.app.model, self.root_password)
|
||||
|
||||
if process is None:
|
||||
dialog.show_error(title=self.locale_keys['popup.downgrade.impossible.title'],
|
||||
body=self.locale_keys['popup.downgrade.impossible.body'])
|
||||
else:
|
||||
success = self.notify_subproc_outputs(process, self.signal_output)
|
||||
except (requests.exceptions.ConnectionError, NoInternetException):
|
||||
success = False
|
||||
self.signal_output.emit(self.locale_keys['internet.required'])
|
||||
finally:
|
||||
self.app = None
|
||||
self.root_password = None
|
||||
self.signal_finished.emit(success)
|
||||
|
||||
|
||||
class GetAppInfo(QThread):
|
||||
signal_finished = pyqtSignal(dict)
|
||||
|
||||
def __init__(self, manager: ApplicationManager, app: ApplicationView = None):
|
||||
super(GetAppInfo, self).__init__()
|
||||
self.app = app
|
||||
self.manager = manager
|
||||
|
||||
def run(self):
|
||||
if self.app:
|
||||
info = {'__app__': self.app}
|
||||
info.update(self.manager.get_info(self.app.model))
|
||||
self.signal_finished.emit(info)
|
||||
self.app = None
|
||||
|
||||
|
||||
class GetAppHistory(QThread):
|
||||
signal_finished = pyqtSignal(dict)
|
||||
|
||||
def __init__(self, manager: ApplicationManager, locale_keys: dict, app: ApplicationView = None):
|
||||
super(GetAppHistory, self).__init__()
|
||||
self.app = app
|
||||
self.manager = manager
|
||||
self.locale_keys = locale_keys
|
||||
|
||||
def run(self):
|
||||
if self.app:
|
||||
try:
|
||||
res = {'model': self.app.model, 'history': self.manager.get_history(self.app.model)}
|
||||
self.signal_finished.emit(res)
|
||||
except (requests.exceptions.ConnectionError, NoInternetException):
|
||||
self.signal_finished.emit({'error': self.locale_keys['internet.required']})
|
||||
finally:
|
||||
self.app = None
|
||||
|
||||
|
||||
class SearchApps(QThread):
|
||||
signal_finished = pyqtSignal(list)
|
||||
|
||||
def __init__(self, manager: ApplicationManager):
|
||||
super(SearchApps, self).__init__()
|
||||
self.word = None
|
||||
self.manager = manager
|
||||
|
||||
def run(self):
|
||||
apps_found = []
|
||||
|
||||
if self.word:
|
||||
res = self.manager.search(self.word)
|
||||
apps_found.extend(res['installed'])
|
||||
apps_found.extend(res['new'])
|
||||
|
||||
self.signal_finished.emit(apps_found)
|
||||
self.word = None
|
||||
|
||||
|
||||
class InstallApp(AsyncAction):
|
||||
|
||||
signal_finished = pyqtSignal(object)
|
||||
signal_output = pyqtSignal(str)
|
||||
|
||||
def __init__(self, manager: ApplicationManager, disk_cache: bool, icon_cache: Cache, locale_keys: dict, app: ApplicationView = None):
|
||||
super(InstallApp, self).__init__()
|
||||
self.app = app
|
||||
self.manager = manager
|
||||
self.icon_cache = icon_cache
|
||||
self.disk_cache = disk_cache
|
||||
self.locale_keys = locale_keys
|
||||
self.root_password = None
|
||||
|
||||
def run(self):
|
||||
|
||||
if self.app:
|
||||
success = False
|
||||
|
||||
try:
|
||||
process = self.manager.install_and_stream(self.app.model, self.root_password)
|
||||
success = self.notify_subproc_outputs(process, self.signal_output)
|
||||
|
||||
if success and self.disk_cache:
|
||||
self.app.model.installed = True
|
||||
|
||||
if self.app.model.supports_disk_cache():
|
||||
icon_data = self.icon_cache.get(self.app.model.base_data.icon_url)
|
||||
self.manager.cache_to_disk(app=self.app.model,
|
||||
icon_bytes=icon_data.get('bytes') if icon_data else None,
|
||||
only_icon=False)
|
||||
except (requests.exceptions.ConnectionError, NoInternetException):
|
||||
success = False
|
||||
self.signal_output.emit(self.locale_keys['internet.required'])
|
||||
finally:
|
||||
self.signal_finished.emit(self.app if success else None)
|
||||
self.app = None
|
||||
|
||||
|
||||
class AnimateProgress(QThread):
|
||||
|
||||
signal_change = pyqtSignal(int)
|
||||
|
||||
def __init__(self):
|
||||
super(AnimateProgress, self).__init__()
|
||||
self.progress_value = 0
|
||||
self.increment = 5
|
||||
self.stop = False
|
||||
|
||||
def run(self):
|
||||
|
||||
current_increment = self.increment
|
||||
|
||||
while not self.stop:
|
||||
self.signal_change.emit(self.progress_value)
|
||||
|
||||
if self.progress_value == 100:
|
||||
current_increment = -current_increment
|
||||
if self.progress_value == 0:
|
||||
current_increment = self.increment
|
||||
|
||||
self.progress_value += current_increment
|
||||
|
||||
time.sleep(0.05)
|
||||
|
||||
self.progress_value = 0
|
||||
|
||||
|
||||
class VerifyModels(QThread):
|
||||
|
||||
signal_updates = pyqtSignal()
|
||||
|
||||
def __init__(self, apps: List[ApplicationView] = None):
|
||||
super(VerifyModels, self).__init__()
|
||||
self.apps = apps
|
||||
|
||||
def run(self):
|
||||
|
||||
if self.apps:
|
||||
|
||||
stop_at = datetime.utcnow() + timedelta(seconds=30)
|
||||
last_ready = 0
|
||||
|
||||
while True:
|
||||
current_ready = 0
|
||||
|
||||
for app in self.apps:
|
||||
current_ready += 1 if app.model.status == ApplicationStatus.READY else 0
|
||||
|
||||
if current_ready > last_ready:
|
||||
last_ready = current_ready
|
||||
self.signal_updates.emit()
|
||||
|
||||
if current_ready == len(self.apps):
|
||||
self.signal_updates.emit()
|
||||
break
|
||||
|
||||
if stop_at <= datetime.utcnow():
|
||||
break
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
self.apps = None
|
||||
|
||||
|
||||
class RefreshApp(AsyncAction):
|
||||
|
||||
signal_finished = pyqtSignal(bool)
|
||||
signal_output = pyqtSignal(str)
|
||||
|
||||
def __init__(self, manager: ApplicationManager, app: ApplicationView = None):
|
||||
super(RefreshApp, self).__init__()
|
||||
self.app = app
|
||||
self.manager = manager
|
||||
self.root_password = None
|
||||
|
||||
def run(self):
|
||||
|
||||
if self.app:
|
||||
success = False
|
||||
|
||||
try:
|
||||
process = self.manager.refresh(self.app.model, self.root_password)
|
||||
success = self.notify_subproc_outputs(process, self.signal_output)
|
||||
except (requests.exceptions.ConnectionError, NoInternetException):
|
||||
success = False
|
||||
self.signal_output.emit(self.locale_keys['internet.required'])
|
||||
finally:
|
||||
self.app = None
|
||||
self.signal_finished.emit(success)
|
||||
|
||||
|
||||
class FindSuggestions(AsyncAction):
|
||||
|
||||
signal_finished = pyqtSignal(list)
|
||||
|
||||
def __init__(self, man: ApplicationManager):
|
||||
super(FindSuggestions, self).__init__()
|
||||
self.man = man
|
||||
|
||||
def run(self):
|
||||
self.signal_finished.emit(self.man.list_suggestions(limit=-1))
|
||||
|
||||
|
||||
class ListWarnings(QThread):
|
||||
|
||||
signal_warnings = pyqtSignal(list)
|
||||
|
||||
def __init__(self, man: ApplicationManager, locale_keys: dict):
|
||||
super(QThread, self).__init__()
|
||||
self.locale_keys = locale_keys
|
||||
self.man = man
|
||||
|
||||
def run(self):
|
||||
warnings = self.man.list_warnings()
|
||||
if warnings:
|
||||
self.signal_warnings.emit(warnings)
|
||||
20
bauh/view/qt/view_model.py
Normal file
@@ -0,0 +1,20 @@
|
||||
from enum import Enum
|
||||
|
||||
from bauh.core.model import Application
|
||||
|
||||
|
||||
class ApplicationViewStatus(Enum):
|
||||
LOADING = 0
|
||||
READY = 1
|
||||
|
||||
|
||||
class ApplicationView:
|
||||
|
||||
def __init__(self, model: Application, visible: bool = True):
|
||||
self.model = model
|
||||
self.update_checked = model.update
|
||||
self.visible = visible
|
||||
self.status = ApplicationViewStatus.LOADING
|
||||
|
||||
def __repr__(self):
|
||||
return '{} ( {} )'.format(self.model.base_data.name, self.model.get_type())
|
||||
707
bauh/view/qt/window.py
Executable file
@@ -0,0 +1,707 @@
|
||||
import operator
|
||||
from functools import reduce
|
||||
from typing import List, Set
|
||||
|
||||
from PyQt5.QtCore import QEvent, Qt, QSize
|
||||
from PyQt5.QtGui import QIcon, QWindowStateChangeEvent, QPixmap
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QCheckBox, QHeaderView, QToolButton, QToolBar, \
|
||||
QSizePolicy, QLabel, QPlainTextEdit, QLineEdit, QProgressBar, QHBoxLayout
|
||||
|
||||
from bauh.core import resource, system
|
||||
from bauh.core.controller import ApplicationManager
|
||||
from bauh.core.model import Application
|
||||
from bauh.util.cache import Cache
|
||||
from bauh.view.qt import dialog
|
||||
from bauh.view.qt.about import AboutDialog
|
||||
from bauh.view.qt.apps_table import AppsTable
|
||||
from bauh.view.qt.history import HistoryDialog
|
||||
from bauh.view.qt.info import InfoDialog
|
||||
from bauh.view.qt.root import is_root, ask_root_password
|
||||
from bauh.view.qt.thread import UpdateSelectedApps, RefreshApps, UninstallApp, DowngradeApp, GetAppInfo, \
|
||||
GetAppHistory, SearchApps, InstallApp, AnimateProgress, VerifyModels, RefreshApp, FindSuggestions, ListWarnings
|
||||
from bauh.view.qt.view_model import ApplicationView
|
||||
|
||||
DARK_ORANGE = '#FF4500'
|
||||
|
||||
|
||||
class ManageWindow(QWidget):
|
||||
__BASE_HEIGHT__ = 400
|
||||
|
||||
def __init__(self, locale_keys: dict, icon_cache: Cache, manager: ApplicationManager, disk_cache: bool, download_icons: bool, screen_size, suggestions: bool, tray_icon=None):
|
||||
super(ManageWindow, self).__init__()
|
||||
self.locale_keys = locale_keys
|
||||
self.manager = manager
|
||||
self.tray_icon = tray_icon
|
||||
self.working = False # restrict the number of threaded actions
|
||||
self.apps = []
|
||||
self.label_flatpak = None
|
||||
self.icon_cache = icon_cache
|
||||
self.disk_cache = disk_cache
|
||||
self.download_icons = download_icons
|
||||
self.screen_size = screen_size
|
||||
|
||||
self.icon_flathub = QIcon(resource.get_path('img/logo.svg'))
|
||||
self.resize(ManageWindow.__BASE_HEIGHT__, ManageWindow.__BASE_HEIGHT__)
|
||||
self.setWindowIcon(self.icon_flathub)
|
||||
|
||||
self.layout = QVBoxLayout()
|
||||
self.setLayout(self.layout)
|
||||
|
||||
self.toolbar_top = QToolBar()
|
||||
self.toolbar_top.addWidget(self._new_spacer())
|
||||
|
||||
self.label_status = QLabel()
|
||||
self.label_status.setText('')
|
||||
self.label_status.setStyleSheet("font-weight: bold")
|
||||
self.toolbar_top.addWidget(self.label_status)
|
||||
|
||||
self.toolbar_search = QToolBar()
|
||||
self.toolbar_search.setStyleSheet("spacing: 0px;")
|
||||
self.toolbar_search.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
label_pre_search = QLabel()
|
||||
label_pre_search.setStyleSheet("background: white; border-top-left-radius: 5px; border-bottom-left-radius: 5px;")
|
||||
self.toolbar_search.addWidget(label_pre_search)
|
||||
|
||||
self.input_search = QLineEdit()
|
||||
self.input_search.setMaxLength(20)
|
||||
self.input_search.setFrame(False)
|
||||
self.input_search.setPlaceholderText(self.locale_keys['window_manage.input_search.placeholder'] + "...")
|
||||
self.input_search.setToolTip(self.locale_keys['window_manage.input_search.tooltip'])
|
||||
self.input_search.setStyleSheet("QLineEdit { background-color: white; color: gray; spacing: 0;}")
|
||||
self.input_search.returnPressed.connect(self.search)
|
||||
self.toolbar_search.addWidget(self.input_search)
|
||||
|
||||
label_pos_search = QLabel()
|
||||
label_pos_search.setPixmap(QPixmap(resource.get_path('img/search.svg')))
|
||||
label_pos_search.setStyleSheet("background: white; padding-right: 10px; border-top-right-radius: 5px; border-bottom-right-radius: 5px;")
|
||||
self.toolbar_search.addWidget(label_pos_search)
|
||||
|
||||
self.ref_toolbar_search = self.toolbar_top.addWidget(self.toolbar_search)
|
||||
self.toolbar_top.addWidget(self._new_spacer())
|
||||
self.layout.addWidget(self.toolbar_top)
|
||||
|
||||
toolbar = QToolBar()
|
||||
|
||||
self.checkbox_updates = QCheckBox()
|
||||
self.checkbox_updates.setText(self.locale_keys['updates'].capitalize())
|
||||
self.checkbox_updates.stateChanged.connect(self._handle_updates_filter)
|
||||
self.ref_checkbox_updates = toolbar.addWidget(self.checkbox_updates)
|
||||
|
||||
self.checkbox_only_apps = QCheckBox()
|
||||
self.checkbox_only_apps.setText(self.locale_keys['manage_window.checkbox.only_apps'])
|
||||
self.checkbox_only_apps.setChecked(True)
|
||||
self.checkbox_only_apps.stateChanged.connect(self._handle_filter_only_apps)
|
||||
self.ref_checkbox_only_apps = toolbar.addWidget(self.checkbox_only_apps)
|
||||
|
||||
self.extra_filters = QWidget()
|
||||
self.extra_filters.setLayout(QHBoxLayout())
|
||||
toolbar.addWidget(self.extra_filters)
|
||||
|
||||
toolbar.addWidget(self._new_spacer())
|
||||
|
||||
self.bt_refresh = QToolButton()
|
||||
self.bt_refresh.setToolTip(locale_keys['manage_window.bt.refresh.tooltip'])
|
||||
self.bt_refresh.setIcon(QIcon(resource.get_path('img/refresh.svg')))
|
||||
self.bt_refresh.clicked.connect(lambda: self.refresh_apps(keep_console=False))
|
||||
toolbar.addWidget(self.bt_refresh)
|
||||
|
||||
self.bt_upgrade = QToolButton()
|
||||
self.bt_upgrade.setToolTip(locale_keys['manage_window.bt.upgrade.tooltip'])
|
||||
self.bt_upgrade.setIcon(QIcon(resource.get_path('img/update_green.svg')))
|
||||
self.bt_upgrade.setEnabled(False)
|
||||
self.bt_upgrade.clicked.connect(self.update_selected)
|
||||
self.ref_bt_upgrade = toolbar.addWidget(self.bt_upgrade)
|
||||
|
||||
self.layout.addWidget(toolbar)
|
||||
|
||||
self.table_apps = AppsTable(self, self.icon_cache, disk_cache=self.disk_cache, download_icons=self.download_icons)
|
||||
self.table_apps.change_headers_policy()
|
||||
|
||||
self.layout.addWidget(self.table_apps)
|
||||
|
||||
toolbar_console = QToolBar()
|
||||
|
||||
self.checkbox_console = QCheckBox()
|
||||
self.checkbox_console.setText(self.locale_keys['manage_window.checkbox.show_details'])
|
||||
self.checkbox_console.stateChanged.connect(self._handle_console)
|
||||
self.checkbox_console.setVisible(False)
|
||||
self.ref_checkbox_console = toolbar_console.addWidget(self.checkbox_console)
|
||||
|
||||
toolbar_console.addWidget(self._new_spacer())
|
||||
self.layout.addWidget(toolbar_console)
|
||||
|
||||
self.textarea_output = QPlainTextEdit(self)
|
||||
self.textarea_output.resize(self.table_apps.size())
|
||||
self.textarea_output.setStyleSheet("background: black; color: white;")
|
||||
self.layout.addWidget(self.textarea_output)
|
||||
self.textarea_output.setVisible(False)
|
||||
self.textarea_output.setReadOnly(True)
|
||||
|
||||
self.thread_update = UpdateSelectedApps(self.manager)
|
||||
self.thread_update.signal_output.connect(self._update_action_output)
|
||||
self.thread_update.signal_finished.connect(self._finish_update_selected)
|
||||
self.thread_update.signal_status.connect(self._change_updating_app_status)
|
||||
|
||||
self.thread_refresh = RefreshApps(self.manager)
|
||||
self.thread_refresh.signal.connect(self._finish_refresh_apps)
|
||||
|
||||
self.thread_uninstall = UninstallApp(self.manager, self.icon_cache)
|
||||
self.thread_uninstall.signal_output.connect(self._update_action_output)
|
||||
self.thread_uninstall.signal_finished.connect(self._finish_uninstall)
|
||||
|
||||
self.thread_downgrade = DowngradeApp(self.manager, self.locale_keys)
|
||||
self.thread_downgrade.signal_output.connect(self._update_action_output)
|
||||
self.thread_downgrade.signal_finished.connect(self._finish_downgrade)
|
||||
|
||||
self.thread_get_info = GetAppInfo(self.manager)
|
||||
self.thread_get_info.signal_finished.connect(self._finish_get_info)
|
||||
|
||||
self.thread_get_history = GetAppHistory(self.manager, self.locale_keys)
|
||||
self.thread_get_history.signal_finished.connect(self._finish_get_history)
|
||||
|
||||
self.thread_search = SearchApps(self.manager)
|
||||
self.thread_search.signal_finished.connect(self._finish_search)
|
||||
|
||||
self.thread_install = InstallApp(manager=self.manager, disk_cache=self.disk_cache, icon_cache=self.icon_cache, locale_keys=self.locale_keys)
|
||||
self.thread_install.signal_output.connect(self._update_action_output)
|
||||
self.thread_install.signal_finished.connect(self._finish_install)
|
||||
|
||||
self.thread_animate_progress = AnimateProgress()
|
||||
self.thread_animate_progress.signal_change.connect(self._update_progress)
|
||||
|
||||
self.thread_verify_models = VerifyModels()
|
||||
self.thread_verify_models.signal_updates.connect(self._notify_model_data_change)
|
||||
|
||||
self.thread_refresh_app = RefreshApp(manager=self.manager)
|
||||
self.thread_refresh_app.signal_finished.connect(self._finish_refresh)
|
||||
self.thread_refresh_app.signal_output.connect(self._update_action_output)
|
||||
|
||||
self.thread_suggestions = FindSuggestions(man=self.manager)
|
||||
self.thread_suggestions.signal_finished.connect(self._finish_search)
|
||||
|
||||
self.toolbar_bottom = QToolBar()
|
||||
self.toolbar_bottom.setIconSize(QSize(16, 16))
|
||||
|
||||
self.label_updates = QLabel()
|
||||
self.ref_label_updates = self.toolbar_bottom.addWidget(self.label_updates)
|
||||
|
||||
self.toolbar_bottom.addWidget(self._new_spacer())
|
||||
|
||||
self.progress_bar = QProgressBar()
|
||||
self.progress_bar.setTextVisible(False)
|
||||
self.ref_progress_bar = self.toolbar_bottom.addWidget(self.progress_bar)
|
||||
|
||||
self.toolbar_bottom.addWidget(self._new_spacer())
|
||||
|
||||
bt_about = QToolButton()
|
||||
bt_about.setStyleSheet('QToolButton { border: 0px; }')
|
||||
bt_about.setIcon(QIcon(resource.get_path('img/about.svg')))
|
||||
bt_about.clicked.connect(self._show_about)
|
||||
bt_about.setToolTip(self.locale_keys['manage_window.bt_about.tooltip'])
|
||||
self.ref_bt_about = self.toolbar_bottom.addWidget(bt_about)
|
||||
|
||||
self.layout.addWidget(self.toolbar_bottom)
|
||||
|
||||
self.centralize()
|
||||
|
||||
self.filter_only_apps = True
|
||||
self.filter_types = set()
|
||||
self.filter_updates = False
|
||||
self._maximized = False
|
||||
|
||||
self.dialog_about = None
|
||||
self.first_refresh = suggestions
|
||||
|
||||
self.thread_warnings = ListWarnings(man=manager, locale_keys=locale_keys)
|
||||
self.thread_warnings.signal_warnings.connect(self._show_warnings)
|
||||
|
||||
def _show_warnings(self, warnings: List[str]):
|
||||
if warnings:
|
||||
for warning in warnings:
|
||||
dialog.show_warning(title=self.locale_keys['warning'].capitalize(), body=warning)
|
||||
|
||||
def show(self):
|
||||
super(ManageWindow, self).show()
|
||||
if not self.thread_warnings.isFinished():
|
||||
self.thread_warnings.start()
|
||||
|
||||
def _show_about(self):
|
||||
if self.dialog_about is None:
|
||||
self.dialog_about = AboutDialog(self.locale_keys)
|
||||
|
||||
self.dialog_about.show()
|
||||
|
||||
def _handle_updates_filter(self, status: int):
|
||||
self.filter_updates = status == 2
|
||||
self.apply_filters()
|
||||
|
||||
def _handle_filter_only_apps(self, status: int):
|
||||
self.filter_only_apps = status == 2
|
||||
self.apply_filters()
|
||||
|
||||
def _handle_type_filter(self, status: int, app_type: str):
|
||||
|
||||
if status == 2:
|
||||
self.filter_types.add(app_type)
|
||||
elif app_type in self.filter_types:
|
||||
self.filter_types.remove(app_type)
|
||||
|
||||
self.apply_filters()
|
||||
|
||||
def _notify_model_data_change(self):
|
||||
self.table_apps.fill_async_data()
|
||||
|
||||
def _new_spacer(self):
|
||||
spacer = QWidget()
|
||||
spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
return spacer
|
||||
|
||||
def changeEvent(self, e: QEvent):
|
||||
if isinstance(e, QWindowStateChangeEvent):
|
||||
self._maximized = self.isMaximized()
|
||||
policy = QHeaderView.Stretch if self._maximized else QHeaderView.ResizeToContents
|
||||
self.table_apps.change_headers_policy(policy)
|
||||
|
||||
def closeEvent(self, event):
|
||||
|
||||
if self.tray_icon:
|
||||
event.ignore()
|
||||
self.hide()
|
||||
self._handle_console_option(False)
|
||||
|
||||
def _handle_console(self, checked: bool):
|
||||
|
||||
if checked:
|
||||
self.textarea_output.show()
|
||||
else:
|
||||
self.textarea_output.hide()
|
||||
|
||||
def _handle_console_option(self, enable: bool):
|
||||
|
||||
if enable:
|
||||
self.textarea_output.clear()
|
||||
|
||||
self.ref_checkbox_console.setVisible(enable)
|
||||
self.checkbox_console.setChecked(False)
|
||||
self.textarea_output.hide()
|
||||
|
||||
def refresh_apps(self, keep_console: bool = True):
|
||||
self.filter_types.clear()
|
||||
self.input_search.clear()
|
||||
|
||||
if not keep_console:
|
||||
self._handle_console_option(False)
|
||||
|
||||
self.ref_checkbox_updates.setVisible(False)
|
||||
self.ref_checkbox_only_apps.setVisible(False)
|
||||
self._begin_action(self.locale_keys['manage_window.status.refreshing'], clear_filters=True)
|
||||
self.thread_refresh.start()
|
||||
|
||||
def _finish_refresh_apps(self, apps: List[Application]):
|
||||
self.finish_action()
|
||||
self.ref_checkbox_only_apps.setVisible(True)
|
||||
self.ref_bt_upgrade.setVisible(True)
|
||||
self.update_apps(apps)
|
||||
self.first_refresh = False
|
||||
|
||||
def uninstall_app(self, app: ApplicationView):
|
||||
pwd = None
|
||||
requires_root = self.manager.requires_root('uninstall', app.model)
|
||||
|
||||
if not is_root() and requires_root:
|
||||
pwd, ok = ask_root_password(self.locale_keys)
|
||||
|
||||
if not ok:
|
||||
return
|
||||
|
||||
self._handle_console_option(True)
|
||||
self._begin_action('{} {}'.format(self.locale_keys['manage_window.status.uninstalling'], app.model.base_data.name))
|
||||
|
||||
self.thread_uninstall.app = app
|
||||
self.thread_uninstall.root_password = pwd
|
||||
self.thread_uninstall.start()
|
||||
|
||||
def refresh(self, app: ApplicationView):
|
||||
pwd = None
|
||||
requires_root = self.manager.requires_root('refresh', app.model)
|
||||
|
||||
if not is_root() and requires_root:
|
||||
pwd, ok = ask_root_password(self.locale_keys)
|
||||
|
||||
if not ok:
|
||||
return
|
||||
|
||||
self._handle_console_option(True)
|
||||
self._begin_action('{} {}'.format(self.locale_keys['manage_window.status.refreshing_app'], app.model.base_data.name))
|
||||
|
||||
self.thread_refresh_app.app = app
|
||||
self.thread_refresh_app.root_password = pwd
|
||||
self.thread_refresh_app.start()
|
||||
|
||||
def _finish_uninstall(self, app: ApplicationView):
|
||||
self.finish_action()
|
||||
|
||||
if app:
|
||||
if self._can_notify_user():
|
||||
system.notify_user('{} ({}) {}'.format(app.model.base_data.name, app.model.get_type(), self.locale_keys['uninstalled']))
|
||||
|
||||
self.refresh_apps()
|
||||
else:
|
||||
if self._can_notify_user():
|
||||
system.notify_user('{}: {}'.format(app.model.base_data.name, self.locale_keys['notification.uninstall.failed']))
|
||||
|
||||
self.checkbox_console.setChecked(True)
|
||||
|
||||
def _can_notify_user(self):
|
||||
return self.isHidden() or self.isMinimized()
|
||||
|
||||
def _finish_downgrade(self, success: bool):
|
||||
self.finish_action()
|
||||
|
||||
if success:
|
||||
if self._can_notify_user():
|
||||
app = self.table_apps.get_selected_app()
|
||||
system.notify_user('{} ({}) {}'.format(app.model.base_data.name, app.model.get_type(), self.locale_keys['downgraded']))
|
||||
|
||||
self.refresh_apps()
|
||||
|
||||
if self.tray_icon:
|
||||
self.tray_icon.verify_updates(notify_user=False)
|
||||
else:
|
||||
if self._can_notify_user():
|
||||
system.notify_user(self.locale_keys['notification.downgrade.failed'])
|
||||
|
||||
self.checkbox_console.setChecked(True)
|
||||
|
||||
def _finish_refresh(self, success: bool):
|
||||
self.finish_action()
|
||||
|
||||
if success:
|
||||
self.refresh_apps()
|
||||
else:
|
||||
self.checkbox_console.setChecked(True)
|
||||
|
||||
def _change_updating_app_status(self, app_name: str):
|
||||
self.label_status.setText('{} {}...'.format(self.locale_keys['manage_window.status.upgrading'], app_name))
|
||||
|
||||
def apply_filters(self):
|
||||
if self.apps:
|
||||
visible_apps = len(self.apps)
|
||||
for idx, app_v in enumerate(self.apps):
|
||||
hidden = self.filter_only_apps and app_v.model.is_library()
|
||||
|
||||
if not hidden and self.filter_types is not None:
|
||||
hidden = app_v.model.get_type() not in self.filter_types
|
||||
|
||||
if not hidden and self.filter_updates:
|
||||
hidden = not app_v.model.update
|
||||
|
||||
self.table_apps.setRowHidden(idx, hidden)
|
||||
app_v.visible = not hidden
|
||||
visible_apps -= 1 if hidden else 0
|
||||
|
||||
self.change_update_state(change_filters=False)
|
||||
|
||||
if not self._maximized:
|
||||
self.table_apps.change_headers_policy(QHeaderView.Stretch)
|
||||
self.table_apps.change_headers_policy()
|
||||
self.resize_and_center(accept_lower_width=visible_apps > 0)
|
||||
|
||||
def change_update_state(self, change_filters: bool = True):
|
||||
enable_bt_update = False
|
||||
app_updates, library_updates, not_installed = 0, 0, 0
|
||||
|
||||
for app_v in self.apps:
|
||||
if app_v.model.update:
|
||||
if app_v.model.runtime:
|
||||
library_updates += 1
|
||||
else:
|
||||
app_updates += 1
|
||||
|
||||
if not app_v.model.installed:
|
||||
not_installed += 1
|
||||
|
||||
for app_v in self.apps:
|
||||
if not_installed == 0 and app_v.visible and app_v.update_checked:
|
||||
enable_bt_update = True
|
||||
break
|
||||
|
||||
self.bt_upgrade.setEnabled(enable_bt_update)
|
||||
|
||||
total_updates = app_updates + library_updates
|
||||
|
||||
if total_updates > 0:
|
||||
self.label_updates.setPixmap(QPixmap(resource.get_path('img/exclamation.svg')).scaled(16, 16, Qt.KeepAspectRatio, Qt.SmoothTransformation))
|
||||
self.label_updates.setToolTip('{}: {} ( {} {} | {} {} )'.format(self.locale_keys['manage_window.label.updates'],
|
||||
total_updates,
|
||||
app_updates,
|
||||
self.locale_keys['manage_window.checkbox.only_apps'].lower(),
|
||||
library_updates,
|
||||
self.locale_keys['others'].lower()))
|
||||
|
||||
if not_installed == 0:
|
||||
if not self.ref_checkbox_updates.isVisible():
|
||||
self.ref_checkbox_updates.setVisible(True)
|
||||
|
||||
if change_filters and not self.checkbox_updates.isChecked():
|
||||
self.checkbox_updates.setChecked(True)
|
||||
|
||||
if change_filters and library_updates > 0 and self.checkbox_only_apps.isChecked():
|
||||
self.checkbox_only_apps.setChecked(False)
|
||||
else:
|
||||
self.checkbox_updates.setChecked(False)
|
||||
self.ref_checkbox_updates.setVisible(False)
|
||||
self.label_updates.setPixmap(QPixmap())
|
||||
|
||||
def centralize(self):
|
||||
geo = self.frameGeometry()
|
||||
screen = QApplication.desktop().screenNumber(QApplication.desktop().cursor().pos())
|
||||
center_point = QApplication.desktop().screenGeometry(screen).center()
|
||||
geo.moveCenter(center_point)
|
||||
self.move(geo.topLeft())
|
||||
|
||||
def update_apps(self, apps: List[Application], update_check_enabled: bool = True):
|
||||
self.apps = []
|
||||
|
||||
napps = 0 # number of apps (not libraries)
|
||||
available_types = set()
|
||||
|
||||
if apps:
|
||||
for app in apps:
|
||||
app_model = ApplicationView(model=app, visible=(not app.is_library()) or not self.checkbox_only_apps.isChecked())
|
||||
available_types.add(app.get_type())
|
||||
napps += 1 if not app.is_library() else 0
|
||||
self.apps.append(app_model)
|
||||
|
||||
if napps == 0:
|
||||
|
||||
if self.first_refresh:
|
||||
self._begin_search('')
|
||||
self.thread_suggestions.start()
|
||||
return
|
||||
else:
|
||||
self.checkbox_only_apps.setChecked(False)
|
||||
self.checkbox_only_apps.setCheckable(False)
|
||||
else:
|
||||
self.checkbox_only_apps.setCheckable(True)
|
||||
self.checkbox_only_apps.setChecked(True)
|
||||
|
||||
self._update_type_filters(available_types)
|
||||
self.table_apps.update_apps(self.apps, update_check_enabled=update_check_enabled)
|
||||
self.apply_filters()
|
||||
self.change_update_state()
|
||||
self.resize_and_center()
|
||||
|
||||
self.thread_verify_models.apps = self.apps
|
||||
self.thread_verify_models.start()
|
||||
|
||||
def _update_type_filters(self, available_types: Set[str]):
|
||||
|
||||
self.filter_types = available_types
|
||||
|
||||
filters_layout = self.extra_filters.layout()
|
||||
for i in reversed(range(filters_layout.count())):
|
||||
filters_layout.itemAt(i).widget().setParent(None)
|
||||
|
||||
if available_types:
|
||||
for app_type in sorted(list(available_types)):
|
||||
checkbox_app_type = QCheckBox()
|
||||
checkbox_app_type.setChecked(True)
|
||||
checkbox_app_type.setText(app_type.capitalize())
|
||||
|
||||
def handle_click(status: int, filter_type: str = app_type):
|
||||
self._handle_type_filter(status, filter_type)
|
||||
|
||||
checkbox_app_type.stateChanged.connect(handle_click)
|
||||
filters_layout.addWidget(checkbox_app_type)
|
||||
|
||||
def resize_and_center(self, accept_lower_width: bool = True):
|
||||
new_width = reduce(operator.add, [self.table_apps.columnWidth(i) for i in range(len(self.table_apps.column_names))]) * 1.05
|
||||
|
||||
if accept_lower_width or new_width > self.width():
|
||||
self.resize(new_width, self.height())
|
||||
|
||||
self.centralize()
|
||||
|
||||
def update_selected(self):
|
||||
if self.apps:
|
||||
|
||||
to_update = [app_v for app_v in self.apps if app_v.visible and app_v.update_checked]
|
||||
|
||||
if to_update:
|
||||
if dialog.ask_confirmation(
|
||||
title=self.locale_keys['manage_window.upgrade_all.popup.title'],
|
||||
body=self.locale_keys['manage_window.upgrade_all.popup.body'],
|
||||
locale_keys=self.locale_keys):
|
||||
self._handle_console_option(True)
|
||||
|
||||
self._begin_action(self.locale_keys['manage_window.status.upgrading'])
|
||||
self.thread_update.apps_to_update = to_update
|
||||
self.thread_update.start()
|
||||
|
||||
def _finish_update_selected(self, success: bool, updated: int):
|
||||
self.finish_action()
|
||||
|
||||
if success:
|
||||
if self._can_notify_user():
|
||||
system.notify_user('{} {}'.format(updated, self.locale_keys['notification.update_selected.success']))
|
||||
|
||||
self.refresh_apps()
|
||||
|
||||
if self.tray_icon:
|
||||
self.tray_icon.verify_updates()
|
||||
else:
|
||||
if self._can_notify_user():
|
||||
system.notify_user(self.locale_keys['notification.update_selected.failed'])
|
||||
|
||||
self.bt_upgrade.setEnabled(True)
|
||||
self.checkbox_console.setChecked(True)
|
||||
|
||||
def _update_action_output(self, output: str):
|
||||
self.textarea_output.appendPlainText(output)
|
||||
|
||||
def _begin_action(self, action_label: str, keep_search: bool = False, clear_filters: bool = False):
|
||||
self.ref_bt_about.setVisible(False)
|
||||
self.ref_label_updates.setVisible(False)
|
||||
self.thread_animate_progress.stop = False
|
||||
self.thread_animate_progress.start()
|
||||
self.ref_progress_bar.setVisible(True)
|
||||
|
||||
self.label_status.setText(action_label + "...")
|
||||
self.bt_upgrade.setEnabled(False)
|
||||
self.bt_refresh.setEnabled(False)
|
||||
self.checkbox_only_apps.setEnabled(False)
|
||||
self.table_apps.setEnabled(False)
|
||||
self.checkbox_updates.setEnabled(False)
|
||||
|
||||
if keep_search:
|
||||
self.ref_toolbar_search.setVisible(True)
|
||||
else:
|
||||
self.ref_toolbar_search.setVisible(False)
|
||||
|
||||
if clear_filters:
|
||||
self._update_type_filters(set())
|
||||
else:
|
||||
self.extra_filters.setEnabled(False)
|
||||
|
||||
def finish_action(self):
|
||||
self.ref_bt_about.setVisible(True)
|
||||
self.ref_progress_bar.setVisible(False)
|
||||
self.ref_label_updates.setVisible(True)
|
||||
self.thread_animate_progress.stop = True
|
||||
self.progress_bar.setValue(0)
|
||||
self.bt_refresh.setEnabled(True)
|
||||
self.checkbox_only_apps.setEnabled(True)
|
||||
self.table_apps.setEnabled(True)
|
||||
self.input_search.setEnabled(True)
|
||||
self.label_status.setText('')
|
||||
self.ref_toolbar_search.setVisible(True)
|
||||
self.ref_toolbar_search.setEnabled(True)
|
||||
self.extra_filters.setEnabled(True)
|
||||
self.checkbox_updates.setEnabled(True)
|
||||
|
||||
def downgrade_app(self, app: ApplicationView):
|
||||
pwd = None
|
||||
requires_root = self.manager.requires_root('downgrade', self.table_apps.get_selected_app().model)
|
||||
|
||||
if not is_root() and requires_root:
|
||||
pwd, ok = ask_root_password(self.locale_keys)
|
||||
|
||||
if not ok:
|
||||
return
|
||||
|
||||
self._handle_console_option(True)
|
||||
self._begin_action('{} {}'.format(self.locale_keys['manage_window.status.downgrading'], app.model.base_data.name))
|
||||
|
||||
self.thread_downgrade.app = app
|
||||
self.thread_downgrade.root_password = pwd
|
||||
self.thread_downgrade.start()
|
||||
|
||||
def get_app_info(self, app: dict):
|
||||
self._handle_console_option(False)
|
||||
self._begin_action(self.locale_keys['manage_window.status.info'])
|
||||
|
||||
self.thread_get_info.app = app
|
||||
self.thread_get_info.start()
|
||||
|
||||
def get_app_history(self, app: dict):
|
||||
self._handle_console_option(False)
|
||||
self._begin_action(self.locale_keys['manage_window.status.history'])
|
||||
|
||||
self.thread_get_history.app = app
|
||||
self.thread_get_history.start()
|
||||
|
||||
def _finish_get_info(self, app_info: dict):
|
||||
self.finish_action()
|
||||
self.change_update_state(change_filters=False)
|
||||
dialog_info = InfoDialog(app=app_info, icon_cache=self.icon_cache, locale_keys=self.locale_keys, screen_size=self.screen_size)
|
||||
dialog_info.exec_()
|
||||
|
||||
def _finish_get_history(self, app: dict):
|
||||
self.finish_action()
|
||||
self.change_update_state(change_filters=False)
|
||||
|
||||
if app.get('error'):
|
||||
self._handle_console_option(True)
|
||||
self.textarea_output.appendPlainText(app['error'])
|
||||
self.checkbox_console.setChecked(True)
|
||||
else:
|
||||
dialog_history = HistoryDialog(app, self.icon_cache, self.locale_keys)
|
||||
dialog_history.exec_()
|
||||
|
||||
def _begin_search(self, word):
|
||||
self._handle_console_option(False)
|
||||
self.ref_checkbox_only_apps.setVisible(False)
|
||||
self.ref_checkbox_updates.setVisible(False)
|
||||
self.filter_updates = False
|
||||
self._begin_action('{}{}'.format(self.locale_keys['manage_window.status.searching'], '"{}"'.format(word) if word else ''), clear_filters=True)
|
||||
|
||||
def search(self):
|
||||
|
||||
word = self.input_search.text().strip()
|
||||
|
||||
if word:
|
||||
self._begin_search(word)
|
||||
self.thread_search.word = word
|
||||
self.thread_search.start()
|
||||
|
||||
def _finish_search(self, apps_found: List[Application]):
|
||||
self.finish_action()
|
||||
self.ref_bt_upgrade.setVisible(False)
|
||||
self.update_apps(apps_found, update_check_enabled=False)
|
||||
|
||||
def install_app(self, app: ApplicationView):
|
||||
pwd = None
|
||||
requires_root = self.manager.requires_root('install', app.model)
|
||||
|
||||
if not is_root() and requires_root:
|
||||
pwd, ok = ask_root_password(self.locale_keys)
|
||||
|
||||
if not ok:
|
||||
return
|
||||
|
||||
self._handle_console_option(True)
|
||||
self._begin_action('{} {}'.format(self.locale_keys['manage_window.status.installing'], app.model.base_data.name))
|
||||
|
||||
self.thread_install.app = app
|
||||
self.thread_install.root_password = pwd
|
||||
self.thread_install.start()
|
||||
|
||||
def _finish_install(self, app: ApplicationView):
|
||||
self.input_search.setText('')
|
||||
self.finish_action()
|
||||
|
||||
if app:
|
||||
if self._can_notify_user():
|
||||
system.notify_user('{} ({}) {}'.format(app.model.base_data.name, app.model.get_type(), self.locale_keys['installed']))
|
||||
|
||||
self.refresh_apps()
|
||||
else:
|
||||
if self._can_notify_user():
|
||||
system.notify_user('{}: {}'.format(app.model.base_data.name, self.locale_keys['notification.install.failed']))
|
||||
|
||||
self.checkbox_console.setChecked(True)
|
||||
|
||||
def _update_progress(self, value: int):
|
||||
self.progress_bar.setValue(value)
|
||||