0.3.0
### Features
- Applications search
- Now when you right-click a selected application you can:
- retrieve its information
- retrieve its commit history
- downgrade
- install and uninstall it
- "About" window available when right-clicking the tray icon.
### Improvements
- Performance and memory usage
- Adding tooltips to toolbar buttons
- "Update ?" column renamed to "Upgrade ?"
- Management panel title renamed
- Showing runtime apps when no app is available
- Allowing to specify a custom app translation with the environment variable **FPAKMAN_LOCALE**
- Adding expiration time for cached app data. Default to 1 hour. The environment variable **FPAKMAN_CACHE_EXPIRATION** can change this value.
- Now the application accepts arguments related to environment variables as well. Check 'README.md'.
- Minor GUI improvements
- Notifying only new updates
- New icon
- Progress bar
24
CHANGELOG.md
@@ -4,6 +4,30 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [0.3.0] - 2019-07-02
|
||||
### Features
|
||||
- Applications search
|
||||
- Now when you right-click a selected application you can:
|
||||
- retrieve its information
|
||||
- retrieve its commit history
|
||||
- downgrade
|
||||
- install and uninstall it
|
||||
- "About" window available when right-clicking the tray icon.
|
||||
|
||||
### Improvements
|
||||
- Performance and memory usage
|
||||
- Adding tooltips to toolbar buttons
|
||||
- "Update ?" column renamed to "Upgrade ?"
|
||||
- Management panel title renamed
|
||||
- Showing runtime apps when no app is available
|
||||
- Allowing to specify a custom app translation with the environment variable **FPAKMAN_LOCALE**
|
||||
- Adding expiration time for cached app data. Default to 1 hour. The environment variable **FPAKMAN_CACHE_EXPIRATION** can change this value.
|
||||
- Now the application accepts arguments related to environment variables as well. Check 'README.md'.
|
||||
- Minor GUI improvements
|
||||
- Notifying only new updates
|
||||
- New icon
|
||||
- Progress bar
|
||||
|
||||
## [0.2.1] - 2019-06-24
|
||||
### Features
|
||||
- Showing the number of apps and runtime updates available
|
||||
|
||||
25
README.md
@@ -1,12 +1,13 @@
|
||||
## fpakman
|
||||
Graphical user interface for Flatpak application management. It is a tray icon to let the user known when new updates are available.
|
||||
It has also a management window allowing the user to see all installed applications and update them.
|
||||
|
||||
Non-official graphical user interface for Flatpak application management. It is a tray icon that let the user known when new updates are available and
|
||||
an application management panel where you can search, update, install and uninstall applications.
|
||||
|
||||
### Developed with:
|
||||
- Python3 and QT 5.
|
||||
- Python3 and Qt5.
|
||||
|
||||
### Requirements
|
||||
- libappindicator3 (for GTK3 desktop environments)
|
||||
- libappindicator3 ( for GTK3 desktop environments )
|
||||
#### Debian-based distros
|
||||
- python3-venv
|
||||
#### Arch-based distros
|
||||
@@ -23,7 +24,8 @@ sudo pip3 install fpakman
|
||||
```
|
||||
|
||||
**AUR**
|
||||
As **fpakman** package. There is also a staging version (**fpakman-staging**) but is intended for testing and it may not work properly.
|
||||
|
||||
As **fpakman** package. There is also a staging version (**fpakman-staging**) but is intended for testing and may not work properly.
|
||||
|
||||
|
||||
### Manual installation:
|
||||
@@ -35,14 +37,17 @@ env/bin/fpakman
|
||||
```
|
||||
|
||||
### Autostart
|
||||
In order to autostart the application, use your Desktop Environment settings to register it as startup script ("fpakman").
|
||||
(P.S: the installation script currently does not do that)
|
||||
In order to autostart the application, use your Desktop Environment settings to register it as a startup application / script ("fpakman").
|
||||
|
||||
|
||||
### Settings
|
||||
You can change some application settings via environment variables:
|
||||
You can change some application settings via environment variables or arguments (type ```fpakman --help``` to get more information).
|
||||
- **FPAKMAN_UPDATE_NOTIFICATION**: enable or disable system updates notifications. Use **0** (disable) or **1** (enable, default).
|
||||
- **FPAKMAN_CHECK_INTERVAL**: define the updates check interval in seconds. Default: 60.
|
||||
- **FPAKMAN_LOCALE**: define a custom app translation for a given locale key (e.g: 'pt', 'en', 'es', ...). Default: system locale.
|
||||
- **FPAKMAN_CACHE_EXPIRATION**: define a custom expiration time in SECONDS for application data retrieved from the origin API. Default: 3600 (1 hour).
|
||||
|
||||
|
||||
### Roadmap
|
||||
- Search and install applications
|
||||
- Uninstall applications
|
||||
- Support for other packaging technologies
|
||||
- Memory and performance improvements
|
||||
|
||||
24
aur/desktop_entry.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# Generates a .desktop file based on the current python version. Used for AUR installation
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
desktop_file = """
|
||||
[Desktop Entry]
|
||||
Type = Application
|
||||
Name = fpakman
|
||||
Categories = System;
|
||||
Comment = Manage your Flatpak applications
|
||||
Exec = /usr/bin/fpakman
|
||||
Icon = /usr/lib/python{version}/site-packages/fpakman/resources/img/logo.svg
|
||||
""".format(version="{}.{}".format(sys.version_info.major, sys.version_info.minor))
|
||||
|
||||
with open('fpakman.desktop', 'w+') as f:
|
||||
f.write(desktop_file)
|
||||
|
||||
|
||||
# cleaning the old fpakman.desktop entry model -> the following lines will be removed for the next releases
|
||||
desktop_file_path = '{}/.local/share/applications/fpakman.desktop'.format(str(Path.home()))
|
||||
|
||||
if os.path.exists(desktop_file_path):
|
||||
os.remove(desktop_file_path)
|
||||
@@ -1,24 +0,0 @@
|
||||
# used for AUR installation
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
desktop_file = """
|
||||
[Desktop Entry]
|
||||
Type = Application
|
||||
Name = fpakman
|
||||
Categories = System;
|
||||
Comment = Manage your Flatpak applications
|
||||
Exec = /usr/bin/fpakman
|
||||
Icon = /usr/lib/python{version}/site-packages/fpakman/resources/img/flathub_45.svg
|
||||
""".format(version="{}.{}".format(sys.version_info.major, sys.version_info.minor))
|
||||
|
||||
apps_path = '{}/.local/share/applications'.format(str(Path.home()))
|
||||
|
||||
if not os.path.exists(apps_path):
|
||||
os.mkdir(apps_path)
|
||||
|
||||
file_path = '{}/fpakman.desktop'.format(apps_path)
|
||||
|
||||
with open(file_path, 'w+') as f:
|
||||
f.write(desktop_file)
|
||||
@@ -1 +1 @@
|
||||
__version__ = '0.2.1'
|
||||
__version__ = '0.3.0'
|
||||
|
||||
@@ -4,29 +4,59 @@ import sys
|
||||
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
from colorama import Fore
|
||||
|
||||
from fpakman import __version__
|
||||
from fpakman.core import resource
|
||||
from fpakman.core import util
|
||||
from fpakman.core.controller import FlatpakController
|
||||
from fpakman.core.model import FlatpakManager
|
||||
from fpakman.core.controller import FlatpakManager
|
||||
from fpakman.view.qt.systray import TrayIcon
|
||||
|
||||
parser = argparse.ArgumentParser(prog='fpakman', description="GUI for Flatpak applications management")
|
||||
app_name = 'fpakman'
|
||||
|
||||
|
||||
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('FPAKMAN_CACHE_EXPIRATION', 60 * 60)), type=int, help='cached application expiration time in SECONDS. Default: %(default)s')
|
||||
parser.add_argument('-l', '--locale', action="store", default=os.getenv('FPAKMAN_LOCALE', 'en'), help='Translation key. Default: %(default)s')
|
||||
parser.add_argument('-i', '--check-interval', action="store", default=int(os.getenv('FPAKMAN_CHECK_INTERVAL', 60)), type=int, help='Updates check interval in SECONDS. Default: %(default)s')
|
||||
parser.add_argument('-n', '--update-notification', action="store", default=os.getenv('FPAKMAN_UPDATE_NOTIFICATION', 1), type=int, help='Enable/disable system notifications for new updates. Default: %(default)s')
|
||||
args = parser.parse_args()
|
||||
|
||||
locale_keys = util.get_locale_keys()
|
||||
if args.cache_exp <= 0:
|
||||
log_msg("'cache-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 args.update_notification == 0:
|
||||
log_msg('updates notifications are disabled', Fore.YELLOW)
|
||||
|
||||
locale_keys = util.get_locale_keys(args.locale)
|
||||
|
||||
app = QApplication(sys.argv)
|
||||
app.setWindowIcon(QIcon(resource.get_path('img/flathub_45.svg')))
|
||||
manager = FlatpakManager()
|
||||
manager.load_database_async()
|
||||
controller = FlatpakController(manager)
|
||||
app.setWindowIcon(QIcon(resource.get_path('img/logo.svg')))
|
||||
|
||||
manager = FlatpakManager(cache_expire=args.cache_exp)
|
||||
|
||||
trayIcon = TrayIcon(locale_keys=locale_keys,
|
||||
controller=controller,
|
||||
check_interval=int(os.getenv('FPAKMAN_CHECK_INTERVAL', 60)))
|
||||
manager=manager,
|
||||
check_interval=args.check_interval,
|
||||
update_notification=bool(args.update_notification))
|
||||
trayIcon.load_database()
|
||||
trayIcon.show()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
|
||||
@@ -1,22 +1,134 @@
|
||||
from datetime import datetime, timedelta
|
||||
from threading import Lock
|
||||
from typing import List
|
||||
|
||||
from fpakman.core.model import FlatpakManager
|
||||
import requests
|
||||
|
||||
from fpakman.core import flatpak
|
||||
|
||||
__FLATHUB_URL__ = 'https://flathub.org'
|
||||
__FLATHUB_API_URL__ = __FLATHUB_URL__ + '/api/v1'
|
||||
|
||||
|
||||
class FlatpakController:
|
||||
class FlatpakManager:
|
||||
|
||||
def __init__(self, model: FlatpakManager):
|
||||
self.model = model
|
||||
def __init__(self, cache_expire: int = 60 * 60):
|
||||
self.cache_apps = {}
|
||||
self.cache_expire = cache_expire
|
||||
self.http_session = requests.Session()
|
||||
self.lock_db_read = Lock()
|
||||
self.lock_read = Lock()
|
||||
|
||||
def refresh(self) -> List[dict]:
|
||||
return self.model.read_installed()
|
||||
def load_full_database(self):
|
||||
|
||||
def update(self, app_ref: str):
|
||||
return self.model.update_app(app_ref)
|
||||
self.lock_db_read.acquire()
|
||||
|
||||
def check_installed(self) -> bool:
|
||||
version = self.model.get_version()
|
||||
return False if version is None else version
|
||||
try:
|
||||
res = self.http_session.get(__FLATHUB_API_URL__ + '/apps', timeout=30)
|
||||
|
||||
def get_version(self) -> str:
|
||||
return self.model.get_version()
|
||||
if res.status_code == 200:
|
||||
for app in res.json():
|
||||
self.cache_apps[app['flatpakAppId']] = app
|
||||
finally:
|
||||
self.lock_db_read.release()
|
||||
|
||||
def _request_app_data(self, app_id: str):
|
||||
|
||||
try:
|
||||
res = self.http_session.get('{}/apps/{}'.format(__FLATHUB_API_URL__, app_id), timeout=30)
|
||||
|
||||
if res.status_code == 200:
|
||||
return res.json()
|
||||
else:
|
||||
print("Could not retrieve app data for id '{}'. Server response: {}".format(app_id, res.status_code))
|
||||
except:
|
||||
print("Could not retrieve app data for id '{}'. Timeout".format(app_id))
|
||||
return None
|
||||
|
||||
def _fill_api_data(self, app: dict):
|
||||
|
||||
api_data = self.cache_apps.get(app['id'])
|
||||
|
||||
if (not app['runtime'] and not api_data) or (api_data and api_data.get('expires_at') and api_data['expires_at'] <= datetime.utcnow()): # if api data is not cached or expired, tries to retrieve it
|
||||
api_data = self._request_app_data(app['id'])
|
||||
|
||||
if api_data:
|
||||
|
||||
if self.cache_expire > 0:
|
||||
api_data['expires_at'] = datetime.utcnow() + timedelta(seconds=self.cache_expire)
|
||||
|
||||
self.cache_apps[app['id']] = api_data
|
||||
|
||||
if not api_data:
|
||||
for attr in ('latest_version', 'icon', 'description'):
|
||||
if attr not in app:
|
||||
app[attr] = None
|
||||
else:
|
||||
app['latest_version'] = api_data.get('currentReleaseVersion')
|
||||
app['icon'] = api_data.get('iconMobileUrl')
|
||||
|
||||
for attr in ('name', 'description'):
|
||||
if not app.get(attr):
|
||||
app[attr] = api_data.get(attr)
|
||||
|
||||
if app['icon'].startswith('/'):
|
||||
app['icon'] = __FLATHUB_URL__ + app['icon']
|
||||
|
||||
def search(self, word: str) -> List[dict]:
|
||||
|
||||
res = []
|
||||
apps_found = flatpak.search(word)
|
||||
|
||||
if apps_found:
|
||||
|
||||
already_read = set()
|
||||
installed_apps = self.read_installed()
|
||||
|
||||
if installed_apps:
|
||||
for app in apps_found:
|
||||
for installed_app in installed_apps:
|
||||
if app['id'] == installed_app['id']:
|
||||
res.append(installed_app)
|
||||
already_read.add(app['id'])
|
||||
|
||||
for app in apps_found:
|
||||
if app['id'] not in already_read:
|
||||
app['update'] = False
|
||||
app['installed'] = False
|
||||
self._fill_api_data(app)
|
||||
res.append(app)
|
||||
|
||||
return res
|
||||
|
||||
def read_installed(self) -> List[dict]:
|
||||
|
||||
self.lock_read.acquire()
|
||||
|
||||
try:
|
||||
installed = flatpak.list_installed()
|
||||
|
||||
if installed:
|
||||
installed.sort(key=lambda p: p['name'].lower())
|
||||
|
||||
available_updates = flatpak.list_updates_as_str()
|
||||
|
||||
for app in installed:
|
||||
self._fill_api_data(app)
|
||||
app['update'] = app['id'] in available_updates
|
||||
app['installed'] = True
|
||||
|
||||
return installed
|
||||
|
||||
finally:
|
||||
self.lock_read.release()
|
||||
|
||||
def downgrade_app(self, app: dict, root_password: str):
|
||||
|
||||
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 flatpak.downgrade_and_stream(app['ref'], commits[commit_idx + 1], root_password)
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import re
|
||||
import subprocess
|
||||
from typing import List
|
||||
|
||||
from fpakman.core import system
|
||||
|
||||
BASE_CMD = 'flatpak'
|
||||
|
||||
|
||||
def app_str_to_json(line: str, version: str) -> dict:
|
||||
|
||||
@@ -32,33 +35,53 @@ def app_str_to_json(line: str, version: str) -> dict:
|
||||
else:
|
||||
raise Exception('Unsupported version')
|
||||
|
||||
info = re.findall(r'\w+:\s.+', get_app_info(app['id'], app['branch']))
|
||||
fields_to_get = ['origin', 'arch', 'ref']
|
||||
|
||||
for field in info:
|
||||
field_val = field.split(':')
|
||||
field_name = field_val[0].lower()
|
||||
|
||||
if field_name in fields_to_get:
|
||||
app[field_name] = field_val[1].strip()
|
||||
|
||||
if field_name == 'ref':
|
||||
app['runtime'] = app['ref'].startswith('runtime/')
|
||||
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('flatpak --version')
|
||||
res = system.run_cmd('{} --version'.format(BASE_CMD))
|
||||
return res.split(' ')[1].strip() if res else None
|
||||
|
||||
|
||||
def get_app_info(app_id: str, branch: str):
|
||||
return system.run_cmd('flatpak info {} {}'.format(app_id, branch))
|
||||
return system.run_cmd('{} info {} {}'.format(BASE_CMD, app_id, branch))
|
||||
|
||||
|
||||
def list_installed() -> List[dict]:
|
||||
apps_str = system.run_cmd('flatpak list')
|
||||
apps_str = system.run_cmd('{} list'.format(BASE_CMD))
|
||||
|
||||
if apps_str:
|
||||
version = get_version()
|
||||
@@ -68,18 +91,123 @@ def list_installed() -> List[dict]:
|
||||
return []
|
||||
|
||||
|
||||
def update(app_ref: str) -> bool:
|
||||
return bool(system.run_cmd('flatpak update -y ' + app_ref))
|
||||
|
||||
|
||||
def update_and_stream(app_ref: str):
|
||||
"""
|
||||
Updates the app reference and streams Flatpak output,
|
||||
:param app_ref:
|
||||
:return:
|
||||
"""
|
||||
return system.stream_cmd(['flatpak', 'update', '-y', app_ref])
|
||||
return system.stream_cmd([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.stream_cmd([BASE_CMD, 'uninstall', app_ref, '-y'])
|
||||
|
||||
|
||||
def list_updates_as_str():
|
||||
return system.run_cmd('flatpak update', ignore_return_code=True)
|
||||
return system.run_cmd('{} update'.format(BASE_CMD), ignore_return_code=True)
|
||||
|
||||
|
||||
def downgrade_and_stream(app_ref: str, commit: str, root_password: str):
|
||||
|
||||
pwdin, downgrade_cmd = None, []
|
||||
|
||||
if root_password is not None:
|
||||
downgrade_cmd.extend(['sudo', '-S'])
|
||||
pwdin = system.stream_cmd(['echo', root_password])
|
||||
|
||||
downgrade_cmd.extend([BASE_CMD, 'update', '--commit={}'.format(commit), app_ref, '-y'])
|
||||
return subprocess.Popen(downgrade_cmd, stdin=pwdin, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL).stdout
|
||||
|
||||
|
||||
def get_app_commits(app_ref: str, origin: str) -> List[str]:
|
||||
log = system.run_cmd('{} remote-info --log {} {}'.format(BASE_CMD, origin, app_ref))
|
||||
return re.findall(r'Commit+:\s(.+)', log)
|
||||
|
||||
|
||||
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))
|
||||
|
||||
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) -> 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':
|
||||
version = info_list[3].strip()
|
||||
found.append({
|
||||
'name': info_list[0].strip(),
|
||||
'description': info_list[1].strip(),
|
||||
'id': info_list[2].strip(),
|
||||
'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':
|
||||
desc = info_list[0].split('-')
|
||||
version = info_list[2].strip()
|
||||
found.append({
|
||||
'name': desc[0].strip(),
|
||||
'description': desc[1].strip(),
|
||||
'id': info_list[1].strip(),
|
||||
'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:
|
||||
version = info_list[1].strip()
|
||||
found.append({
|
||||
'name': '',
|
||||
'description': info_list[4].strip(),
|
||||
'id': info_list[0].strip(),
|
||||
'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
|
||||
})
|
||||
return found
|
||||
|
||||
|
||||
def install_and_stream(app_id: str, origin: str):
|
||||
return system.stream_cmd([BASE_CMD, 'install', origin, app_id, '-y'])
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
from threading import Lock, Thread
|
||||
from typing import List
|
||||
|
||||
import requests
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtGui import QPixmap, QIcon
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
|
||||
|
||||
from fpakman.core import flatpak
|
||||
|
||||
__FLATHUB_URL__ = 'https://flathub.org'
|
||||
__FLATHUB_API_URL__ = __FLATHUB_URL__ + '/api/v1'
|
||||
|
||||
|
||||
class FlatpakManager:
|
||||
|
||||
def __init__(self):
|
||||
self.apps = []
|
||||
self.apps_db = {}
|
||||
self.http_session = requests.Session()
|
||||
self.lock_db_read = Lock()
|
||||
|
||||
def load_database_async(self):
|
||||
Thread(target=self.load_database, daemon=True).start()
|
||||
|
||||
def load_database(self):
|
||||
|
||||
self.lock_db_read.acquire()
|
||||
|
||||
try:
|
||||
res = self.http_session.get(__FLATHUB_API_URL__ + '/apps')
|
||||
|
||||
if res.status_code == 200:
|
||||
for app in res.json():
|
||||
self.apps_db[app['flatpakAppId']] = app
|
||||
finally:
|
||||
self.lock_db_read.release()
|
||||
|
||||
def get_version(self):
|
||||
return flatpak.get_version()
|
||||
|
||||
def read_installed(self) -> List[dict]:
|
||||
|
||||
installed = flatpak.list_installed()
|
||||
|
||||
if installed:
|
||||
installed.sort(key=lambda p: p['name'].lower())
|
||||
|
||||
available_updates = flatpak.list_updates_as_str()
|
||||
|
||||
for app in installed:
|
||||
|
||||
if not self.apps_db:
|
||||
self.load_database()
|
||||
|
||||
if self.apps_db:
|
||||
app_data = self.apps_db.get(app['id'], None)
|
||||
else:
|
||||
app_data = None
|
||||
|
||||
if not app_data:
|
||||
app['latest_version'] = None
|
||||
app['icon'] = None
|
||||
else:
|
||||
app['latest_version'] = app_data['currentReleaseVersion']
|
||||
app['icon'] = app_data['iconMobileUrl']
|
||||
|
||||
if app['icon'].startswith('/'):
|
||||
app['icon'] = __FLATHUB_URL__ + app['icon']
|
||||
|
||||
app['update'] = app['id'] in available_updates
|
||||
|
||||
self.apps = installed
|
||||
return [*self.apps]
|
||||
|
||||
def update_apps(self, refs: List[str]) -> List[dict]:
|
||||
|
||||
if self.apps:
|
||||
|
||||
for ref in refs:
|
||||
package_found = [app for app in self.apps if app['ref'] == ref]
|
||||
|
||||
if package_found:
|
||||
package_found = package_found[0]
|
||||
updated = flatpak.update(ref)
|
||||
|
||||
if updated:
|
||||
package_found['update'] = not updated
|
||||
|
||||
return [*self.apps]
|
||||
|
||||
return []
|
||||
|
||||
def update_app(self, ref: str):
|
||||
|
||||
"""
|
||||
:param ref:
|
||||
:return: the update command stream
|
||||
"""
|
||||
|
||||
if self.apps:
|
||||
|
||||
package_found = [app for app in self.apps if app['ref'] == ref]
|
||||
|
||||
if package_found:
|
||||
return flatpak.update_and_stream(ref)
|
||||
|
||||
return None
|
||||
@@ -1,11 +1,18 @@
|
||||
import os
|
||||
import subprocess
|
||||
from typing import List
|
||||
|
||||
from fpakman.core import resource
|
||||
|
||||
|
||||
def run_cmd(cmd: str, expected_code: int = 0, ignore_return_code: bool = False) -> str:
|
||||
res = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE)
|
||||
res = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, env={'LANG': 'en'})
|
||||
return res.stdout.decode() if ignore_return_code or res.returncode == expected_code else None
|
||||
|
||||
|
||||
def stream_cmd(cmd: List[str]):
|
||||
return subprocess.Popen(cmd, stdout=subprocess.PIPE).stdout
|
||||
return subprocess.Popen(cmd, stdout=subprocess.PIPE, env={'LANG': 'en'}).stdout
|
||||
|
||||
|
||||
def notify_user(msg: str, icon_path: str = resource.get_path('img/logo.svg')):
|
||||
os.system("notify-send {} '{}'".format("-i {}".format(icon_path) if icon_path else '', msg))
|
||||
|
||||
@@ -2,13 +2,20 @@ import locale
|
||||
|
||||
from fpakman.core import resource
|
||||
import glob
|
||||
import re
|
||||
|
||||
HTML_RE = re.compile(r'<[^>]+>')
|
||||
|
||||
|
||||
def get_locale_keys():
|
||||
def get_locale_keys(key: str = None):
|
||||
|
||||
current_locale = locale.getdefaultlocale()
|
||||
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]
|
||||
|
||||
@@ -34,3 +41,7 @@ def get_locale_keys():
|
||||
locale_obj[keyval[0].strip()] = keyval[1].strip()
|
||||
|
||||
return locale_obj
|
||||
|
||||
|
||||
def strip_html(string: str):
|
||||
return HTML_RE.sub('', string)
|
||||
|
||||
62
fpakman/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 |
@@ -8,13 +8,13 @@
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
viewBox="0 0 45 45"
|
||||
viewBox="0 0 24 24"
|
||||
enable-background="new 0 0 26 26"
|
||||
id="svg8"
|
||||
sodipodi:docname="update.svg"
|
||||
sodipodi:docname="downgrade.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
width="45"
|
||||
height="45">
|
||||
width="24"
|
||||
height="24">
|
||||
<metadata
|
||||
id="metadata14">
|
||||
<rdf:RDF>
|
||||
@@ -39,13 +39,13 @@
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="739"
|
||||
inkscape:window-height="703"
|
||||
id="namedview10"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
inkscape:zoom="9.0769231"
|
||||
inkscape:cx="13"
|
||||
inkscape:cy="20.813559"
|
||||
inkscape:zoom="4.5384616"
|
||||
inkscape:cx="-20.270304"
|
||||
inkscape:cy="12.13426"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="260"
|
||||
inkscape:window-maximized="1"
|
||||
@@ -56,14 +56,17 @@
|
||||
fit-margin-bottom="0" />
|
||||
<g
|
||||
id="g6"
|
||||
transform="matrix(1.7307692,0,0,1.875,0,-1.875)">
|
||||
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" />
|
||||
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" />
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#ff0000" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -9,13 +9,13 @@
|
||||
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"
|
||||
width="47.919052"
|
||||
height="48.110374"
|
||||
viewBox="0 0 12.678582 12.729203"
|
||||
version="1.1"
|
||||
id="svg3871"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||
sodipodi:docname="flathub_logo.svg">
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="flathub.svg">
|
||||
<defs
|
||||
id="defs3865" />
|
||||
<sodipodi:namedview
|
||||
@@ -26,16 +26,16 @@
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.35"
|
||||
inkscape:cx="354.2246"
|
||||
inkscape:cy="117.98098"
|
||||
inkscape:cx="8.4698372"
|
||||
inkscape:cy="118.04149"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
inkscape:window-width="1360"
|
||||
inkscape:window-height="707"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="703"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-y="260"
|
||||
inkscape:window-maximized="1"
|
||||
fit-margin-bottom="-0.2"
|
||||
fit-margin-top="0"
|
||||
@@ -58,9 +58,9 @@
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-12.111406,-169.00616)">
|
||||
transform="translate(-12.122116,-167.33593)">
|
||||
<g
|
||||
transform="matrix(0.08047549,0,0,0.0997485,-51.028672,86.747244)"
|
||||
transform="matrix(0.08875425,0,0,0.11490723,-57.513376,72.576168)"
|
||||
id="g10674"
|
||||
style="stroke-width:0.77710575">
|
||||
<path
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
220
fpakman/resources/img/history.svg
Executable file
|
After Width: | Height: | Size: 40 KiB |
155
fpakman/resources/img/info.svg
Executable file
@@ -0,0 +1,155 @@
|
||||
<?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="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="info.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"><metadata
|
||||
id="metadata128"><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="defs126"><linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient153"><stop
|
||||
style="stop-color:#0000ff;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop149" /><stop
|
||||
style="stop-color:#0000ff;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop151" /></linearGradient><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient153"
|
||||
id="linearGradient155"
|
||||
x1="-180.77992"
|
||||
y1="-30.518711"
|
||||
x2="337.13013"
|
||||
y2="868.49902"
|
||||
gradientUnits="userSpaceOnUse" /><linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient153"
|
||||
id="linearGradient157"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="-180.77992"
|
||||
y1="-30.518711"
|
||||
x2="337.13013"
|
||||
y2="868.49902" /></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="namedview124"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="1.8698707"
|
||||
inkscape:cx="143.59281"
|
||||
inkscape:cy="26.740827"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="260"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Capa_1" />
|
||||
|
||||
<g
|
||||
id="g93"
|
||||
transform="translate(0,-414.534)">
|
||||
</g>
|
||||
<g
|
||||
id="g95"
|
||||
transform="translate(0,-414.534)">
|
||||
</g>
|
||||
<g
|
||||
id="g97"
|
||||
transform="translate(0,-414.534)">
|
||||
</g>
|
||||
<g
|
||||
id="g99"
|
||||
transform="translate(0,-414.534)">
|
||||
</g>
|
||||
<g
|
||||
id="g101"
|
||||
transform="translate(0,-414.534)">
|
||||
</g>
|
||||
<g
|
||||
id="g103"
|
||||
transform="translate(0,-414.534)">
|
||||
</g>
|
||||
<g
|
||||
id="g105"
|
||||
transform="translate(0,-414.534)">
|
||||
</g>
|
||||
<g
|
||||
id="g107"
|
||||
transform="translate(0,-414.534)">
|
||||
</g>
|
||||
<g
|
||||
id="g109"
|
||||
transform="translate(0,-414.534)">
|
||||
</g>
|
||||
<g
|
||||
id="g111"
|
||||
transform="translate(0,-414.534)">
|
||||
</g>
|
||||
<g
|
||||
id="g113"
|
||||
transform="translate(0,-414.534)">
|
||||
</g>
|
||||
<g
|
||||
id="g115"
|
||||
transform="translate(0,-414.534)">
|
||||
</g>
|
||||
<g
|
||||
id="g117"
|
||||
transform="translate(0,-414.534)">
|
||||
</g>
|
||||
<g
|
||||
id="g119"
|
||||
transform="translate(0,-414.534)">
|
||||
</g>
|
||||
<g
|
||||
id="g121"
|
||||
transform="translate(0,-414.534)">
|
||||
</g>
|
||||
<g
|
||||
id="g96"
|
||||
transform="scale(0.50000002)"><g
|
||||
style="fill:url(#linearGradient155);fill-opacity:1"
|
||||
transform="scale(0.10945609,0.1094556)"
|
||||
id="g91">
|
||||
<path
|
||||
style="fill:url(#linearGradient157);fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
id="path89"
|
||||
d="M 409.133,109.203 C 389.525,75.611 362.928,49.014 329.335,29.407 295.736,9.801 259.058,0 219.273,0 179.492,0 142.803,9.801 109.21,29.407 75.615,49.011 49.018,75.608 29.41,109.203 9.801,142.8 0,179.489 0,219.267 c 0,39.78 9.804,76.463 29.407,110.062 19.607,33.592 46.204,60.189 79.799,79.798 33.597,19.605 70.283,29.407 110.063,29.407 39.78,0 76.47,-9.802 110.065,-29.407 33.593,-19.602 60.189,-46.206 79.795,-79.798 19.603,-33.596 29.403,-70.284 29.403,-110.062 0.001,-39.782 -9.8,-76.472 -29.399,-110.064 z M 182.727,54.813 c 0,-2.666 0.855,-4.853 2.57,-6.565 1.712,-1.711 3.903,-2.57 6.567,-2.57 h 54.82 c 2.662,0 4.853,0.859 6.561,2.57 1.711,1.712 2.573,3.899 2.573,6.565 v 45.682 c 0,2.664 -0.862,4.854 -2.573,6.564 -1.708,1.712 -3.898,2.568 -6.561,2.568 h -54.82 c -2.664,0 -4.854,-0.856 -6.567,-2.568 -1.715,-1.709 -2.57,-3.9 -2.57,-6.564 z m 109.632,301.496 c 0,2.662 -0.863,4.853 -2.57,6.561 -1.704,1.714 -3.895,2.57 -6.563,2.57 H 155.317 c -2.667,0 -4.854,-0.856 -6.567,-2.57 -1.712,-1.708 -2.568,-3.898 -2.568,-6.564 v -45.682 c 0,-2.67 0.856,-4.853 2.568,-6.567 1.713,-1.708 3.903,-2.57 6.567,-2.57 h 27.41 v -91.358 h -27.41 c -2.667,0 -4.853,-0.855 -6.567,-2.568 -1.712,-1.711 -2.568,-3.901 -2.568,-6.567 v -45.679 c 0,-2.666 0.856,-4.853 2.568,-6.567 1.715,-1.713 3.905,-2.568 6.567,-2.568 h 91.367 c 2.662,0 4.853,0.855 6.561,2.568 1.711,1.714 2.573,3.901 2.573,6.567 v 146.179 h 27.401 c 2.669,0 4.859,0.855 6.57,2.566 1.704,1.712 2.566,3.901 2.566,6.567 v 45.683 h 0.004 z" />
|
||||
</g><path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path88"
|
||||
d="m 16.36476,36.447391 v -3.208779 h 1.871787 1.871787 V 27.890649 22.542686 H 18.236547 16.36476 V 19.333908 16.12513 h 5.615361 5.615362 v 8.556741 8.556741 h 2.139185 2.139186 v 3.208779 3.208778 H 24.119307 16.36476 Z"
|
||||
style="fill:#ffffff;stroke-width:0.53479636" /><path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path90"
|
||||
d="M 20.571824,11.490228 C 20.375732,11.294136 20.215294,9.8501861 20.215294,8.2814501 V 5.429203 h 3.743574 3.743574 v 3.208778 3.208778 h -3.387043 c -1.862874,0 -3.547483,-0.160439 -3.743575,-0.356531 z"
|
||||
style="fill:#ffffff;stroke-width:0.53479636" /></g></svg>
|
||||
|
After Width: | Height: | Size: 5.6 KiB |
120
fpakman/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
fpakman/resources/img/logo.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
477
fpakman/resources/img/logo.svg
Executable file
|
After Width: | Height: | Size: 75 KiB |
BIN
fpakman/resources/img/logo_update.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
473
fpakman/resources/img/logo_update.svg
Executable file
|
After Width: | Height: | Size: 74 KiB |
115
fpakman/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 |
81
fpakman/resources/img/refresh.svg
Normal file → Executable file
@@ -7,22 +7,39 @@
|
||||
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="45"
|
||||
height="44.999996"
|
||||
viewBox="0 0 44.999999 44.999995"
|
||||
width="48"
|
||||
height="48"
|
||||
viewBox="0 0 47.999999 47.999999"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="refresh_blue.svg"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"><metadata
|
||||
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" /><sodipodi:namedview
|
||||
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"
|
||||
@@ -31,16 +48,16 @@
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1360"
|
||||
inkscape:window-height="707"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="703"
|
||||
id="namedview41"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
inkscape:zoom="0.48437082"
|
||||
inkscape:cx="306.00515"
|
||||
inkscape:cy="-4.129072"
|
||||
inkscape:zoom="1.9374833"
|
||||
inkscape:cx="165.58246"
|
||||
inkscape:cy="-10.288149"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-y="260"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Capa_1"
|
||||
fit-margin-top="0"
|
||||
@@ -49,81 +66,81 @@
|
||||
fit-margin-bottom="0" />
|
||||
<g
|
||||
id="g8"
|
||||
transform="matrix(0.09887825,0,0,0.09235884,-1.5881978,0)"
|
||||
style="fill:#0088aa">
|
||||
transform="matrix(0.10547014,0,0,0.0985161,-1.6940777,0)"
|
||||
style="fill:url(#linearGradient542);fill-opacity:1">
|
||||
<g
|
||||
id="g6"
|
||||
style="fill:#0088aa">
|
||||
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:#0088aa" />
|
||||
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:#0088aa" />
|
||||
style="fill:url(#linearGradient542);fill-opacity:1" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g10"
|
||||
transform="translate(-16.062155,-442.23)">
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g12"
|
||||
transform="translate(-16.062155,-442.23)">
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g14"
|
||||
transform="translate(-16.062155,-442.23)">
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g16"
|
||||
transform="translate(-16.062155,-442.23)">
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g18"
|
||||
transform="translate(-16.062155,-442.23)">
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g20"
|
||||
transform="translate(-16.062155,-442.23)">
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g22"
|
||||
transform="translate(-16.062155,-442.23)">
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g24"
|
||||
transform="translate(-16.062155,-442.23)">
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g26"
|
||||
transform="translate(-16.062155,-442.23)">
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g28"
|
||||
transform="translate(-16.062155,-442.23)">
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g30"
|
||||
transform="translate(-16.062155,-442.23)">
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g32"
|
||||
transform="translate(-16.062155,-442.23)">
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g34"
|
||||
transform="translate(-16.062155,-442.23)">
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g36"
|
||||
transform="translate(-16.062155,-442.23)">
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
<g
|
||||
id="g38"
|
||||
transform="translate(-16.062155,-442.23)">
|
||||
transform="translate(-16.062155,-439.23)">
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.8 KiB |
120
fpakman/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 |
@@ -9,13 +9,13 @@
|
||||
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="45"
|
||||
height="45"
|
||||
viewBox="0 0 11.90625 11.90625"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 6.35 6.35"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="toogle_red.svg">
|
||||
sodipodi:docname="toggle_off.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
@@ -26,8 +26,8 @@
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.35"
|
||||
inkscape:cx="911.42852"
|
||||
inkscape:cy="-80.000008"
|
||||
inkscape:cx="398.57142"
|
||||
inkscape:cy="-80.000033"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
@@ -38,7 +38,7 @@
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="739"
|
||||
inkscape:window-height="703"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="260"
|
||||
inkscape:window-maximized="1" />
|
||||
@@ -50,7 +50,7 @@
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
@@ -58,12 +58,12 @@
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(198.05951,-115.76041)">
|
||||
transform="translate(198.05952,-121.31665)">
|
||||
<circle
|
||||
style="fill:#b3b3b3;stroke-width:0.04630494"
|
||||
style="fill:#b3b3b3;stroke-width:0.02469599"
|
||||
id="path10"
|
||||
cx="-192.10638"
|
||||
cy="121.71353"
|
||||
r="5.953125" />
|
||||
cx="-194.88452"
|
||||
cy="124.49165"
|
||||
r="3.175" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
@@ -7,17 +7,41 @@
|
||||
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"
|
||||
width="45"
|
||||
height="45"
|
||||
viewBox="0 0 11.90625 11.90625"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 6.35 6.35"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="pak_update_green.svg">
|
||||
sodipodi:docname="toggle_on.svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
id="linearGradient66">
|
||||
<stop
|
||||
style="stop-color:#55d400;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop62" />
|
||||
<stop
|
||||
style="stop-color:#55d400;stop-opacity:0;"
|
||||
offset="1"
|
||||
id="stop64" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient66"
|
||||
id="linearGradient68"
|
||||
x1="-210.35391"
|
||||
y1="112.4323"
|
||||
x2="-215.96657"
|
||||
y2="126.86485"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.53333336,0,0,0.53333336,-92.427766,59.577773)" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
@@ -25,9 +49,9 @@
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.35"
|
||||
inkscape:cx="802.85709"
|
||||
inkscape:cy="-80.000008"
|
||||
inkscape:zoom="0.98994949"
|
||||
inkscape:cx="185.81986"
|
||||
inkscape:cy="20.785442"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
@@ -38,7 +62,7 @@
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="739"
|
||||
inkscape:window-height="703"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="260"
|
||||
inkscape:window-maximized="1" />
|
||||
@@ -58,12 +82,12 @@
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(198.05951,-115.76041)">
|
||||
transform="translate(198.05951,-121.31666)">
|
||||
<circle
|
||||
style="fill:#66ff00;stroke-width:0.04630494"
|
||||
style="fill:url(#linearGradient68);fill-opacity:1;stroke-width:0.02469597"
|
||||
id="path10"
|
||||
cx="-192.10638"
|
||||
cy="121.71353"
|
||||
r="5.953125" />
|
||||
cx="-194.88451"
|
||||
cy="124.49166"
|
||||
r="3.175" />
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.6 KiB |
96
fpakman/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 |
@@ -5,16 +5,17 @@
|
||||
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 45 45"
|
||||
viewBox="0 0 48 48"
|
||||
enable-background="new 0 0 26 26"
|
||||
id="svg8"
|
||||
sodipodi:docname="update_green.svg"
|
||||
sodipodi:docname="update_green_2.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
width="45"
|
||||
height="45">
|
||||
width="48"
|
||||
height="48">
|
||||
<metadata
|
||||
id="metadata14">
|
||||
<rdf:RDF>
|
||||
@@ -28,7 +29,47 @@
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs12" />
|
||||
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"
|
||||
@@ -39,13 +80,13 @@
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1366"
|
||||
inkscape:window-height="739"
|
||||
inkscape:window-height="703"
|
||||
id="namedview10"
|
||||
showgrid="false"
|
||||
showborder="false"
|
||||
inkscape:zoom="9.0769231"
|
||||
inkscape:cx="-4.5169491"
|
||||
inkscape:cy="20.813559"
|
||||
inkscape:zoom="3.2091769"
|
||||
inkscape:cx="11.132699"
|
||||
inkscape:cy="36.020085"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="260"
|
||||
inkscape:window-maximized="1"
|
||||
@@ -56,17 +97,21 @@
|
||||
fit-margin-bottom="0" />
|
||||
<g
|
||||
id="g6"
|
||||
transform="matrix(1.7307692,0,0,1.875,0,-1.875)"
|
||||
style="fill:#00ff00">
|
||||
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:#00ff00" />
|
||||
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:#00ff00" />
|
||||
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>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 3.9 KiB |
@@ -1,17 +1,72 @@
|
||||
manage_window.columns.name=Name
|
||||
manage_window.columns.version=Version
|
||||
manage_window.title=Flatpak Application Manager
|
||||
manage_window.columns.latest_version=Latest Version
|
||||
manage_window.columns.branch=Branch
|
||||
manage_window.columns.arch=Arch
|
||||
manage_window.columns.ref=Ref
|
||||
manage_window.columns.origin=Origin
|
||||
manage_window.columns.update=Update ?
|
||||
manage_window.columns.installed=Installed
|
||||
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=Uninstall
|
||||
manage_window.apps_table.row.actions.uninstall.popup.title=Uninstall
|
||||
manage_window.apps_table.row.actions.uninstall.popup.body=Remove {} ?
|
||||
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.upgrade_toggle.tooltip=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.updating=Updating
|
||||
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.bt.refresh.tooltip=Reload the data about installed applications
|
||||
manage_window.bt.upgrade.tooltip=Upgrade all selected applications
|
||||
popup.flatpak_not_installed.title=Error
|
||||
popup.flatpak_not_installed.msg=flatpak seems not to be installed. Exiting
|
||||
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
|
||||
notification.new_updates=Updates found
|
||||
tray.action.about=About
|
||||
tray.action.refreshing=Refreshing
|
||||
notification.new_updates={} updates
|
||||
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
|
||||
about.info.desc=Non-official Flatpak 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
|
||||
@@ -1,17 +1,72 @@
|
||||
manage_window.columns.name=Nombre
|
||||
manage_window.columns.version=Versión
|
||||
manage_window.title=Administrador de Aplicativos Flatpak
|
||||
manage_window.columns.latest_version=Ultima Versión
|
||||
manage_window.columns.branch=Branch
|
||||
manage_window.columns.arch=Arch
|
||||
manage_window.columns.ref=Ref
|
||||
manage_window.columns.origin=Origen
|
||||
manage_window.columns.update=¿Actualizar?
|
||||
manage_window.checkbox.only_apps=Apps
|
||||
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 {}?
|
||||
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.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.updating=Actualizando
|
||||
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.bt.refresh.tooltip=Recarga los datos sobre los aplicativos instalados
|
||||
manage_window.bt.upgrade.tooltip=Actualiza todos los aplicativos seleccionados
|
||||
popup.flatpak_not_installed.title=Error
|
||||
popup.flatpak_not_installed.msg=flatpak no parece estar instalado. Saliendo
|
||||
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
|
||||
notification.new_updates=Actualizaciones encontradas
|
||||
tray.action.about=Sobre
|
||||
tray.action.refreshing=Recargando
|
||||
notification.new_updates=Actualizaciones {}
|
||||
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
|
||||
about.info.desc=Interfaz grafica no oficial para administración de aplicativos Flatpak
|
||||
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
|
||||
@@ -1,17 +1,72 @@
|
||||
manage_window.columns.name=Nome
|
||||
manage_window.columns.version=Versão
|
||||
manage_window.title=Gerenciador de Aplicativos Flatpak
|
||||
manage_window.columns.latest_version=Última Versão
|
||||
manage_window.columns.branch=Branch
|
||||
manage_window.columns.arch=Arch
|
||||
manage_window.columns.ref=Ref
|
||||
manage_window.columns.origin=Origem
|
||||
manage_window.columns.update=Atualizar ?
|
||||
manage_window.checkbox.only_apps=Apps
|
||||
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 {}?
|
||||
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.upgrade_toggle.tooltip=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.updating=Atualizando
|
||||
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.bt.refresh.tooltip=Recarrega os dados sobre os aplicativos instalados
|
||||
manage_window.bt.upgrade.tooltip=Atualiza todos os aplicativos selecionados
|
||||
popup.flatpak_not_installed.title=Erro
|
||||
popup.flatpak_not_installed.msg=flatpak não parece estar instalado. Saindo
|
||||
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
|
||||
notification.new_updates=Atualizações encontradas
|
||||
tray.action.about=Sobre
|
||||
tray.action.refreshing=Recarregando
|
||||
notification.new_updates=Atualizações {}
|
||||
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
|
||||
about.info.desc=Interface gráfica não oficial para gerenciamento de aplicativos Flatpak
|
||||
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
|
||||
71
fpakman/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 fpakman import __version__
|
||||
from fpakman.core import resource
|
||||
|
||||
PROJECT_URL = 'https://github.com/vinifmor/fpakman'
|
||||
LICENSE_URL = 'https://raw.githubusercontent.com/vinifmor/fpakman/master/LICENSE'
|
||||
|
||||
|
||||
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('fpakman ( {} {} )'.format(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()
|
||||
225
fpakman/view/qt/apps_table.py
Normal file
@@ -0,0 +1,225 @@
|
||||
from typing import List
|
||||
|
||||
from PyQt5.QtCore import Qt, QUrl
|
||||
from PyQt5.QtGui import QPixmap, QIcon, QColor, QCursor
|
||||
from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager
|
||||
from PyQt5.QtWidgets import QTableWidget, QTableView, QMenu, QAction, QTableWidgetItem, QToolButton, QWidget, \
|
||||
QHeaderView, QLabel
|
||||
|
||||
from fpakman.core import resource, util
|
||||
from fpakman.view.qt import dialog
|
||||
|
||||
|
||||
class UpdateToggleButton(QToolButton):
|
||||
|
||||
def __init__(self, model: dict, root: QWidget, locale_keys: dict, checked: bool = True):
|
||||
super(UpdateToggleButton, self).__init__()
|
||||
self.app = model
|
||||
self.root = root
|
||||
self.setCheckable(True)
|
||||
self.clicked.connect(self.change_state)
|
||||
self.icon_on = QIcon(resource.get_path('img/toggle_on.svg'))
|
||||
self.icon_off = QIcon(resource.get_path('img/toggle_off.svg'))
|
||||
self.setIcon(self.icon_on)
|
||||
self.setStyleSheet('border: 0px;')
|
||||
self.setToolTip(locale_keys['manage_window.apps_table.upgrade_toggle.tooltip'])
|
||||
|
||||
if not checked:
|
||||
self.click()
|
||||
|
||||
def change_state(self, not_checked: bool):
|
||||
self.app['update_checked'] = not not_checked
|
||||
self.setIcon(self.icon_on if not not_checked else self.icon_off)
|
||||
self.root.change_update_state()
|
||||
|
||||
|
||||
class AppsTable(QTableWidget):
|
||||
|
||||
def __init__(self, parent: QWidget):
|
||||
super(AppsTable, self).__init__()
|
||||
self.setParent(parent)
|
||||
self.window = parent
|
||||
self.column_names = [parent.locale_keys[key].capitalize() for key in ['flatpak.info.name',
|
||||
'flatpak.info.version',
|
||||
'manage_window.columns.latest_version',
|
||||
'flatpak.info.branch',
|
||||
'flatpak.info.arch',
|
||||
'flatpak.info.id',
|
||||
'flatpak.info.origin',
|
||||
'manage_window.columns.installed',
|
||||
'manage_window.columns.update']]
|
||||
self.setColumnCount(len(self.column_names))
|
||||
self.setFocusPolicy(Qt.NoFocus)
|
||||
self.setShowGrid(False)
|
||||
self.verticalHeader().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.network_man = QNetworkAccessManager()
|
||||
self.network_man.finished.connect(self._load_icon)
|
||||
|
||||
self.icon_cache = {}
|
||||
|
||||
def contextMenuEvent(self, QContextMenuEvent): # selected row right click event
|
||||
|
||||
app = self.get_selected_app()
|
||||
|
||||
menu_row = QMenu()
|
||||
|
||||
if app['model']['installed']:
|
||||
action_info = QAction(self.window.locale_keys["manage_window.apps_table.row.actions.info"])
|
||||
action_info.setIcon(QIcon(resource.get_path('img/info.svg')))
|
||||
action_info.triggered.connect(self._get_app_info)
|
||||
menu_row.addAction(action_info)
|
||||
|
||||
action_history = QAction(self.window.locale_keys["manage_window.apps_table.row.actions.history"])
|
||||
action_history.setIcon(QIcon(resource.get_path('img/history.svg')))
|
||||
action_history.triggered.connect(self._get_app_history)
|
||||
menu_row.addAction(action_history)
|
||||
|
||||
action_uninstall = QAction(self.window.locale_keys["manage_window.apps_table.row.actions.uninstall"])
|
||||
action_uninstall.setIcon(QIcon(resource.get_path('img/uninstall.svg')))
|
||||
action_uninstall.triggered.connect(self._uninstall_app)
|
||||
menu_row.addAction(action_uninstall)
|
||||
|
||||
if not app['model']['runtime']: # not available for runtimes
|
||||
action_downgrade = QAction(self.window.locale_keys["manage_window.apps_table.row.actions.downgrade"])
|
||||
action_downgrade.triggered.connect(self._downgrade_app)
|
||||
action_downgrade.setIcon(QIcon(resource.get_path('img/downgrade.svg')))
|
||||
menu_row.addAction(action_downgrade)
|
||||
else:
|
||||
action_install = QAction(self.window.locale_keys["manage_window.apps_table.row.actions.install"])
|
||||
action_install.setIcon(QIcon(resource.get_path('img/install.svg')))
|
||||
action_install.triggered.connect(self._install_app)
|
||||
menu_row.addAction(action_install)
|
||||
|
||||
menu_row.adjustSize()
|
||||
menu_row.popup(QCursor.pos())
|
||||
menu_row.exec_()
|
||||
|
||||
def get_selected_app(self):
|
||||
return self.window.apps[self.currentRow()]
|
||||
|
||||
def get_selected_app_icon(self):
|
||||
return self.item(self.currentRow(), 0).icon()
|
||||
|
||||
def _uninstall_app(self):
|
||||
selected_app = self.get_selected_app()
|
||||
|
||||
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(selected_app['model']['name']),
|
||||
locale_keys=self.window.locale_keys):
|
||||
self.window.uninstall_app(selected_app['model']['ref'])
|
||||
|
||||
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']['name']),
|
||||
locale_keys=self.window.locale_keys):
|
||||
self.window.downgrade_app(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):
|
||||
self.window.install_app(self.get_selected_app())
|
||||
|
||||
def _load_icon(self, http_response):
|
||||
icon_url = http_response.url().toString()
|
||||
pixmap = QPixmap()
|
||||
pixmap.loadFromData(http_response.readAll())
|
||||
icon = QIcon(pixmap)
|
||||
self.icon_cache[icon_url] = icon
|
||||
|
||||
for idx, app in enumerate(self.window.apps):
|
||||
if app['model']['icon'] == icon_url:
|
||||
self.item(idx, 0).setIcon(icon)
|
||||
self.window.resize_and_center()
|
||||
break
|
||||
|
||||
def update_apps(self, apps: List[dict]):
|
||||
self.setEnabled(True)
|
||||
self.setRowCount(len(apps) if apps else 0)
|
||||
|
||||
if apps:
|
||||
for idx, app in enumerate(apps):
|
||||
|
||||
tooltip = util.strip_html(app['model']['description']) if app['model']['description'] else None
|
||||
|
||||
col_name = QTableWidgetItem()
|
||||
col_name.setText(app['model']['name'])
|
||||
col_name.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
col_name.setToolTip(tooltip)
|
||||
|
||||
if not app['model']['icon']:
|
||||
col_name.setIcon(self.icon_flathub)
|
||||
else:
|
||||
cached_icon = self.icon_cache.get(app['model']['icon'])
|
||||
|
||||
if cached_icon:
|
||||
col_name.setIcon(cached_icon)
|
||||
else:
|
||||
col_name.setIcon(self.icon_flathub)
|
||||
self.network_man.get(QNetworkRequest(QUrl(app['model']['icon'])))
|
||||
|
||||
self.setItem(idx, 0, col_name)
|
||||
|
||||
col_version = QTableWidgetItem()
|
||||
col_version.setText(app['model']['version'])
|
||||
col_version.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
col_version.setToolTip(tooltip)
|
||||
self.setItem(idx, 1, col_version)
|
||||
|
||||
col_release = QTableWidgetItem()
|
||||
col_release.setText(app['model']['latest_version'])
|
||||
col_release.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
col_release.setToolTip(tooltip)
|
||||
self.setItem(idx, 2, col_release)
|
||||
|
||||
if app['model']['version'] and app['model']['latest_version'] and app['model']['version'] < app['model']['latest_version']:
|
||||
col_release.setForeground(QColor('orange'))
|
||||
|
||||
col_branch = QTableWidgetItem()
|
||||
col_branch.setText(app['model']['branch'])
|
||||
col_branch.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
col_branch.setToolTip(tooltip)
|
||||
self.setItem(idx, 3, col_branch)
|
||||
|
||||
col_arch = QTableWidgetItem()
|
||||
col_arch.setText(app['model']['arch'])
|
||||
col_arch.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
col_arch.setToolTip(tooltip)
|
||||
self.setItem(idx, 4, col_arch)
|
||||
|
||||
col_id = QTableWidgetItem()
|
||||
col_id.setText(app['model']['id'])
|
||||
col_id.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
col_id.setToolTip(tooltip)
|
||||
self.setItem(idx, 5, col_id)
|
||||
|
||||
col_origin = QTableWidgetItem()
|
||||
col_origin.setText(app['model']['origin'])
|
||||
col_origin.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
col_origin.setToolTip(tooltip)
|
||||
self.setItem(idx, 6, col_origin)
|
||||
|
||||
col_installed = QLabel()
|
||||
col_installed.setPixmap((QPixmap(resource.get_path('img/{}.svg'.format('checked' if app['model']['installed'] else 'red_cross')))))
|
||||
col_installed.setToolTip(tooltip)
|
||||
col_installed.setAlignment(Qt.AlignCenter)
|
||||
|
||||
self.setCellWidget(idx, 7, col_installed)
|
||||
|
||||
col_update = UpdateToggleButton(app, self.window, self.window.locale_keys, app['model']['update']) if app['model']['update'] else None
|
||||
self.setCellWidget(idx, 8, col_update)
|
||||
|
||||
def change_headers_policy(self, policy: QHeaderView = QHeaderView.ResizeToContents):
|
||||
header_horizontal = self.horizontalHeader()
|
||||
for i in range(self.columnCount()):
|
||||
header_horizontal.setSectionResizeMode(i, policy)
|
||||
32
fpakman/view/qt/dialog.py
Normal file
@@ -0,0 +1,32 @@
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from fpakman.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 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)
|
||||
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
|
||||
54
fpakman/view/qt/history.py
Normal file
@@ -0,0 +1,54 @@
|
||||
import operator
|
||||
from functools import reduce
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QTableWidget, QTableWidgetItem, QHeaderView
|
||||
|
||||
|
||||
class HistoryDialog(QDialog):
|
||||
|
||||
def __init__(self, app: dict, app_icon: QIcon, locale_keys: dict):
|
||||
super(HistoryDialog, self).__init__()
|
||||
|
||||
self.setWindowTitle('{} - {} '.format(locale_keys['popup.history.title'], app['model']['name']))
|
||||
self.setWindowIcon(app_icon)
|
||||
|
||||
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['commits'][0]))
|
||||
table_history.setRowCount(len(app['commits']))
|
||||
table_history.setHorizontalHeaderLabels([locale_keys['flatpak.info.' + key].capitalize() for key in sorted(app['commits'][0].keys())])
|
||||
|
||||
for row, commit in enumerate(app['commits']):
|
||||
|
||||
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(Qt.darkYellow if row != 0 else Qt.darkGreen)
|
||||
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())
|
||||
39
fpakman/view/qt/info.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QFormLayout, QGroupBox, \
|
||||
QLineEdit, QLabel, QPlainTextEdit
|
||||
|
||||
|
||||
class InfoDialog(QDialog):
|
||||
|
||||
def __init__(self, app: dict, app_icon: QIcon, locale_keys: dict):
|
||||
super(InfoDialog, self).__init__()
|
||||
self.setWindowTitle(app['name'])
|
||||
self.setWindowIcon(app_icon)
|
||||
layout = QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
gbox_info_layout = QFormLayout()
|
||||
gbox_info = QGroupBox()
|
||||
gbox_info.setLayout(gbox_info_layout)
|
||||
layout.addWidget(gbox_info)
|
||||
|
||||
for attr in sorted(app.keys()):
|
||||
|
||||
if attr != 'name' and app[attr]:
|
||||
if attr == 'description':
|
||||
text = QPlainTextEdit()
|
||||
text.appendHtml(app[attr])
|
||||
else:
|
||||
text = QLineEdit()
|
||||
text.setText(app[attr])
|
||||
text.setCursorPosition(0)
|
||||
text.setStyleSheet("width: 400px")
|
||||
|
||||
text.setReadOnly(True)
|
||||
|
||||
label = QLabel("{}: ".format(locale_keys.get('flatpak.info.' + attr, attr)).capitalize())
|
||||
label.setStyleSheet("font-weight: bold")
|
||||
|
||||
gbox_info_layout.addRow(label, text)
|
||||
|
||||
self.adjustSize()
|
||||
45
fpakman/view/qt/root.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import io
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
from PyQt5.QtWidgets import QInputDialog, QLineEdit
|
||||
|
||||
from fpakman.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())
|
||||
@@ -1,82 +1,139 @@
|
||||
import os
|
||||
import time
|
||||
from typing import List
|
||||
|
||||
from PyQt5.QtCore import QThread, pyqtSignal, QCoreApplication
|
||||
from PyQt5.QtCore import QThread, pyqtSignal, QCoreApplication, Qt
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import QSystemTrayIcon, QMenu
|
||||
|
||||
from fpakman.core import resource
|
||||
from fpakman.core.controller import FlatpakController
|
||||
from fpakman.core import resource, system
|
||||
from fpakman.core.controller import FlatpakManager
|
||||
from fpakman.view.qt.about import AboutDialog
|
||||
from fpakman.view.qt.window import ManageWindow
|
||||
|
||||
|
||||
class UpdateCheck(QThread):
|
||||
|
||||
signal = pyqtSignal(int)
|
||||
signal = pyqtSignal(list)
|
||||
|
||||
def __init__(self, check_interval: int, controller: FlatpakController, parent=None):
|
||||
def __init__(self, manager: FlatpakManager, check_interval: int, parent=None):
|
||||
super(UpdateCheck, self).__init__(parent)
|
||||
self.controller = controller
|
||||
self.check_interval = check_interval
|
||||
self.manager = manager
|
||||
|
||||
def run(self):
|
||||
|
||||
while True:
|
||||
|
||||
apps = self.controller.refresh()
|
||||
apps = self.manager.read_installed()
|
||||
|
||||
updates = len([app for app in apps if app['update']])
|
||||
updates = [app for app in apps if app['update']]
|
||||
|
||||
self.signal.emit(updates)
|
||||
if updates:
|
||||
self.signal.emit(updates)
|
||||
|
||||
time.sleep(self.check_interval)
|
||||
|
||||
|
||||
class LoadDatabase(QThread):
|
||||
|
||||
signal_finished = pyqtSignal()
|
||||
|
||||
def __init__(self, manager: FlatpakManager, parent=None):
|
||||
super(LoadDatabase, self).__init__(parent)
|
||||
self.manager = manager
|
||||
|
||||
def run(self):
|
||||
self.manager.load_full_database()
|
||||
self.signal_finished.emit()
|
||||
|
||||
|
||||
class TrayIcon(QSystemTrayIcon):
|
||||
|
||||
def __init__(self, locale_keys: dict, controller: FlatpakController, check_interval: int = 60):
|
||||
def __init__(self, locale_keys: dict, manager: FlatpakManager, check_interval: int = 60, update_notification: bool = True):
|
||||
super(TrayIcon, self).__init__()
|
||||
self.locale_keys = locale_keys
|
||||
self.controller = controller
|
||||
self.manager = manager
|
||||
|
||||
self.icon_default = QIcon(resource.get_path('img/flathub_45.svg'))
|
||||
self.icon_update = QIcon(resource.get_path('img/update_logo.svg'))
|
||||
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_refreshing = self.menu.addAction(self.locale_keys['tray.action.refreshing'] + '...')
|
||||
self.action_refreshing.setEnabled(False)
|
||||
|
||||
self.action_manage = self.menu.addAction(self.locale_keys['tray.action.manage'])
|
||||
self.action_manage.triggered.connect(self.show_manage_window)
|
||||
self.action_manage.setVisible(False)
|
||||
|
||||
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 = ManageWindow(locale_keys=self.locale_keys, controller=controller, tray_icon=self)
|
||||
self.check_thread = UpdateCheck(check_interval=check_interval, controller=self.controller)
|
||||
self.manage_window = None
|
||||
self.check_thread = UpdateCheck(check_interval=check_interval, manager=self.manager)
|
||||
self.check_thread.signal.connect(self.notify_updates)
|
||||
self.check_thread.start()
|
||||
|
||||
def notify_updates(self, updates: int):
|
||||
if updates > 0:
|
||||
if self.icon().cacheKey() != self.icon_update.cacheKey():
|
||||
self.setIcon(self.icon_update)
|
||||
self.dialog_about = None
|
||||
|
||||
msg = '{}: {}'.format(self.locale_keys['notification.new_updates'], updates)
|
||||
self.thread_database = LoadDatabase(manager)
|
||||
self.thread_database.signal_finished.connect(self._update_menu)
|
||||
self.last_updates = set()
|
||||
self.update_notification = update_notification
|
||||
|
||||
def load_database(self):
|
||||
self.thread_database.start()
|
||||
|
||||
def _update_menu(self):
|
||||
self.action_refreshing.setVisible(False)
|
||||
self.action_manage.setVisible(True)
|
||||
|
||||
def notify_updates(self, updates: List[dict]):
|
||||
|
||||
if len(updates) > 0:
|
||||
|
||||
update_keys = {'{}:{}'.format(app['id'], app['latest_version']) for app 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'].format('Flatpak'), len(updates))
|
||||
self.setToolTip(msg)
|
||||
|
||||
if bool(os.getenv('FPAKMAN_UPDATE_NOTIFICATION', 1)):
|
||||
os.system("notify-send -i {} '{}'".format(resource.get_path('img/flathub_45.svg'), msg))
|
||||
|
||||
if self.manage_window:
|
||||
self.manage_window.refresh()
|
||||
if self.update_notification:
|
||||
system.notify_user(msg)
|
||||
|
||||
else:
|
||||
self.setIcon(self.icon_default)
|
||||
new_icon = self.icon_default
|
||||
self.setToolTip(None)
|
||||
|
||||
if self.icon().cacheKey() != new_icon.cacheKey(): # changes the icon if needed
|
||||
self.setIcon(new_icon)
|
||||
|
||||
def show_manage_window(self):
|
||||
|
||||
if not self.manage_window:
|
||||
self.manage_window = ManageWindow(controller=self.controller)
|
||||
if self.manage_window is None:
|
||||
self.manage_window = ManageWindow(locale_keys=self.locale_keys,
|
||||
manager=self.manager,
|
||||
tray_icon=self)
|
||||
|
||||
self.manage_window.refresh()
|
||||
self.manage_window.show()
|
||||
if self.manage_window.isMinimized():
|
||||
self.manage_window.setWindowState(Qt.WindowNoState)
|
||||
else:
|
||||
self.manage_window.refresh()
|
||||
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()
|
||||
|
||||
186
fpakman/view/qt/thread.py
Normal file
@@ -0,0 +1,186 @@
|
||||
import time
|
||||
|
||||
from PyQt5.QtCore import QThread, pyqtSignal
|
||||
|
||||
from fpakman.core import flatpak
|
||||
from fpakman.core.controller import FlatpakManager
|
||||
from fpakman.view.qt import dialog
|
||||
|
||||
|
||||
class UpdateSelectedApps(QThread):
|
||||
|
||||
signal_finished = pyqtSignal()
|
||||
signal_output = pyqtSignal(str)
|
||||
|
||||
def __init__(self):
|
||||
super(UpdateSelectedApps, self).__init__()
|
||||
self.refs_to_update = []
|
||||
|
||||
def run(self):
|
||||
|
||||
for app_ref in self.refs_to_update:
|
||||
for output in flatpak.update_and_stream(app_ref):
|
||||
line = output.decode().strip()
|
||||
if line:
|
||||
self.signal_output.emit(line)
|
||||
|
||||
self.signal_finished.emit()
|
||||
|
||||
|
||||
class RefreshApps(QThread):
|
||||
|
||||
signal = pyqtSignal(list)
|
||||
|
||||
def __init__(self, manager: FlatpakManager):
|
||||
super(RefreshApps, self).__init__()
|
||||
self.manager = manager
|
||||
|
||||
def run(self):
|
||||
self.signal.emit(self.manager.read_installed())
|
||||
|
||||
|
||||
class UninstallApp(QThread):
|
||||
signal_finished = pyqtSignal()
|
||||
signal_output = pyqtSignal(str)
|
||||
|
||||
def __init__(self):
|
||||
super(UninstallApp, self).__init__()
|
||||
self.app_ref = None
|
||||
|
||||
def run(self):
|
||||
if self.app_ref:
|
||||
for output in flatpak.uninstall_and_stream(self.app_ref):
|
||||
line = output.decode().strip()
|
||||
if line:
|
||||
self.signal_output.emit(line)
|
||||
|
||||
self.signal_finished.emit()
|
||||
|
||||
|
||||
class DowngradeApp(QThread):
|
||||
signal_finished = pyqtSignal()
|
||||
signal_output = pyqtSignal(str)
|
||||
|
||||
def __init__(self, manager: FlatpakManager, locale_keys: dict):
|
||||
super(DowngradeApp, self).__init__()
|
||||
self.manager = manager
|
||||
self.app = None
|
||||
self.root_password = None
|
||||
self.locale_keys = locale_keys
|
||||
|
||||
def run(self):
|
||||
if self.app:
|
||||
|
||||
stream = self.manager.downgrade_app(self.app['model'], self.root_password)
|
||||
|
||||
if stream is None:
|
||||
dialog.show_error(title=self.locale_keys['popup.downgrade.impossible.title'],
|
||||
body=self.locale_keys['popup.downgrade.impossible.body'])
|
||||
else:
|
||||
for output in self.manager.downgrade_app(self.app['model'], self.root_password):
|
||||
line = output.decode().strip()
|
||||
if line:
|
||||
self.signal_output.emit(line)
|
||||
|
||||
self.app = None
|
||||
self.root_password = None
|
||||
self.signal_finished.emit()
|
||||
|
||||
|
||||
class GetAppInfo(QThread):
|
||||
signal_finished = pyqtSignal(dict)
|
||||
|
||||
def __init__(self):
|
||||
super(GetAppInfo, self).__init__()
|
||||
self.app = None
|
||||
|
||||
def run(self):
|
||||
if self.app:
|
||||
app_info = flatpak.get_app_info_fields(self.app['model']['id'], self.app['model']['branch'])
|
||||
app_info['name'] = self.app['model']['name']
|
||||
app_info['type'] = 'runtime' if self.app['model']['runtime'] else 'app'
|
||||
app_info['description'] = self.app['model']['description']
|
||||
self.signal_finished.emit(app_info)
|
||||
self.app = None
|
||||
|
||||
|
||||
class GetAppHistory(QThread):
|
||||
signal_finished = pyqtSignal(dict)
|
||||
|
||||
def __init__(self):
|
||||
super(GetAppHistory, self).__init__()
|
||||
self.app = None
|
||||
|
||||
def run(self):
|
||||
if self.app:
|
||||
commits = flatpak.get_app_commits_data(self.app['model']['ref'], self.app['model']['origin'])
|
||||
self.signal_finished.emit({'model': self.app['model'], 'commits': commits})
|
||||
self.app = None
|
||||
|
||||
|
||||
class SearchApps(QThread):
|
||||
signal_finished = pyqtSignal(list)
|
||||
|
||||
def __init__(self, manager: FlatpakManager):
|
||||
super(SearchApps, self).__init__()
|
||||
self.word = None
|
||||
self.manager = manager
|
||||
|
||||
def run(self):
|
||||
apps_found = []
|
||||
|
||||
if self.word:
|
||||
apps_found = self.manager.search(self.word)
|
||||
|
||||
self.signal_finished.emit(apps_found)
|
||||
self.word = None
|
||||
|
||||
|
||||
class InstallApp(QThread):
|
||||
|
||||
signal_finished = pyqtSignal()
|
||||
signal_output = pyqtSignal(str)
|
||||
|
||||
def __init__(self):
|
||||
super(InstallApp, self).__init__()
|
||||
self.app = None
|
||||
|
||||
def run(self):
|
||||
|
||||
if self.app:
|
||||
for output in flatpak.install_and_stream(self.app['model']['id'], self.app['model']['origin']):
|
||||
line = output.decode().strip()
|
||||
if line:
|
||||
self.signal_output.emit(line)
|
||||
|
||||
self.app = None
|
||||
self.signal_finished.emit()
|
||||
|
||||
|
||||
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
|
||||
@@ -3,81 +3,84 @@ from functools import reduce
|
||||
from threading import Lock
|
||||
from typing import List
|
||||
|
||||
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QUrl, QEvent
|
||||
from PyQt5.QtGui import QIcon, QColor, QPixmap, QWindowStateChangeEvent
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QTableWidget, \
|
||||
QTableWidgetItem, QTableView, QCheckBox, QHeaderView, QToolButton, QToolBar, \
|
||||
QSizePolicy, QLabel, QMessageBox, QPlainTextEdit
|
||||
from PyQt5.QtCore import QEvent
|
||||
from PyQt5.QtGui import QIcon, QWindowStateChangeEvent, QPixmap
|
||||
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QCheckBox, QHeaderView, QToolButton, QToolBar, \
|
||||
QSizePolicy, QLabel, QPlainTextEdit, QLineEdit, QProgressBar
|
||||
|
||||
from fpakman import __version__
|
||||
from fpakman.core import resource
|
||||
from fpakman.core.controller import FlatpakController
|
||||
from fpakman.core import resource, flatpak
|
||||
from fpakman.core.controller import FlatpakManager
|
||||
from fpakman.view.qt import dialog
|
||||
from fpakman.view.qt.apps_table import AppsTable
|
||||
from fpakman.view.qt.history import HistoryDialog
|
||||
from fpakman.view.qt.info import InfoDialog
|
||||
from fpakman.view.qt.root import is_root, ask_root_password
|
||||
from fpakman.view.qt.thread import UpdateSelectedApps, RefreshApps, UninstallApp, DowngradeApp, GetAppInfo, \
|
||||
GetAppHistory, SearchApps, InstallApp, AnimateProgress
|
||||
|
||||
|
||||
class UpdateToggleButton(QToolButton):
|
||||
|
||||
def __init__(self, model: dict, root: QWidget, checked: bool = True):
|
||||
super(UpdateToggleButton, self).__init__()
|
||||
self.model = model
|
||||
self.root = root
|
||||
self.setCheckable(True)
|
||||
self.clicked.connect(self.change_state)
|
||||
self.icon_on = QIcon(resource.get_path('img/toggle_on.svg'))
|
||||
self.icon_off = QIcon(resource.get_path('img/toggle_off.svg'))
|
||||
self.setIcon(self.icon_on)
|
||||
self.setStyleSheet('border: 0px;')
|
||||
|
||||
if not checked:
|
||||
self.click()
|
||||
|
||||
def change_state(self, not_checked: bool):
|
||||
self.model['update_checked'] = not not_checked
|
||||
self.setIcon(self.icon_on if not not_checked else self.icon_off)
|
||||
self.root.change_update_state()
|
||||
DARK_ORANGE = '#FF4500'
|
||||
|
||||
|
||||
class ManageWindow(QWidget):
|
||||
|
||||
__BASE_HEIGHT__ = 400
|
||||
|
||||
def __init__(self, locale_keys: dict, controller: FlatpakController, tray_icon = None):
|
||||
def __init__(self, locale_keys: dict, manager: FlatpakManager, tray_icon=None):
|
||||
super(ManageWindow, self).__init__()
|
||||
self.locale_keys = locale_keys
|
||||
self.column_names = [locale_keys['manage_window.columns.name'],
|
||||
locale_keys['manage_window.columns.version'],
|
||||
locale_keys['manage_window.columns.latest_version'],
|
||||
locale_keys['manage_window.columns.branch'],
|
||||
locale_keys['manage_window.columns.arch'],
|
||||
locale_keys['manage_window.columns.ref'],
|
||||
locale_keys['manage_window.columns.origin'],
|
||||
locale_keys['manage_window.columns.update']]
|
||||
self.controller = controller
|
||||
self.icon_cache = {}
|
||||
self.manager = manager
|
||||
self.tray_icon = tray_icon
|
||||
self.thread_lock = Lock()
|
||||
self.working = False # restrict the number of threaded actions
|
||||
self.apps = []
|
||||
self.label_flatpak = None
|
||||
|
||||
self.network_man = QNetworkAccessManager()
|
||||
self.network_man.finished.connect(self._load_icon)
|
||||
|
||||
self.icon_flathub = QIcon(resource.get_path('img/flathub_45.svg'))
|
||||
self.icon_flathub = QIcon(resource.get_path('img/logo.svg'))
|
||||
self._check_flatpak_installed()
|
||||
self.resize(ManageWindow.__BASE_HEIGHT__, ManageWindow.__BASE_HEIGHT__)
|
||||
self.setWindowTitle('fpakman ({})'.format(__version__))
|
||||
self.setWindowTitle(locale_keys['manage_window.title'])
|
||||
self.setWindowIcon(self.icon_flathub)
|
||||
|
||||
self.layout = QVBoxLayout()
|
||||
self.setLayout(self.layout)
|
||||
|
||||
self.toolbar_search = QToolBar()
|
||||
self.toolbar_search.setStyleSheet("spacing: 0px;")
|
||||
self.toolbar_search.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
spacer = QWidget()
|
||||
spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
|
||||
self.toolbar_search.addWidget(spacer)
|
||||
|
||||
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.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: grey; 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)
|
||||
|
||||
spacer = QWidget()
|
||||
spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
self.toolbar_search.addWidget(spacer)
|
||||
self.layout.addWidget(self.toolbar_search)
|
||||
|
||||
toolbar = QToolBar()
|
||||
|
||||
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.filter_only_apps)
|
||||
|
||||
toolbar = QToolBar()
|
||||
toolbar.addWidget(self.checkbox_only_apps)
|
||||
|
||||
spacer = QWidget()
|
||||
@@ -86,7 +89,7 @@ class ManageWindow(QWidget):
|
||||
|
||||
self.label_status = QLabel()
|
||||
self.label_status.setText('')
|
||||
self.label_status.setStyleSheet("color: orange")
|
||||
self.label_status.setStyleSheet("color: {}; font-weight: bold".format(DARK_ORANGE))
|
||||
toolbar.addWidget(self.label_status)
|
||||
|
||||
spacer = QWidget()
|
||||
@@ -94,28 +97,22 @@ class ManageWindow(QWidget):
|
||||
toolbar.addWidget(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(clear_output=True))
|
||||
toolbar.addWidget(self.bt_refresh)
|
||||
|
||||
self.bt_update = QToolButton()
|
||||
self.bt_update.setIcon(QIcon(resource.get_path('img/update_green.svg')))
|
||||
self.bt_update.setEnabled(False)
|
||||
self.bt_update.clicked.connect(self.update_selected)
|
||||
toolbar.addWidget(self.bt_update)
|
||||
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)
|
||||
toolbar.addWidget(self.bt_upgrade)
|
||||
|
||||
self.layout.addWidget(toolbar)
|
||||
|
||||
self.table_apps = QTableWidget()
|
||||
self.table_apps.setColumnCount(len(self.column_names))
|
||||
self.table_apps.setFocusPolicy(Qt.NoFocus)
|
||||
self.table_apps.setShowGrid(False)
|
||||
self.table_apps.verticalHeader().setVisible(False)
|
||||
self.table_apps.setSelectionBehavior(QTableView.SelectRows)
|
||||
self.table_apps.setHorizontalHeaderLabels(self.column_names)
|
||||
self.table_apps.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
|
||||
self._change_table_headers_policy()
|
||||
self.table_apps = AppsTable(self)
|
||||
self.table_apps.change_headers_policy()
|
||||
|
||||
self.layout.addWidget(self.table_apps)
|
||||
|
||||
@@ -126,35 +123,66 @@ class ManageWindow(QWidget):
|
||||
self.textarea_output.setVisible(False)
|
||||
self.textarea_output.setReadOnly(True)
|
||||
|
||||
self.thread_update = UpdateSelectedApps(self.controller)
|
||||
self.thread_update = UpdateSelectedApps()
|
||||
self.thread_update.signal_output.connect(self._update_action_output)
|
||||
self.thread_update.signal_finished.connect(self._finish_update_selected)
|
||||
|
||||
self.thread_refresh = RefreshApps(self.controller)
|
||||
self.thread_refresh = RefreshApps(self.manager)
|
||||
self.thread_refresh.signal.connect(self._finish_refresh)
|
||||
|
||||
self.thread_uninstall = UninstallApp()
|
||||
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.thread_get_info.signal_finished.connect(self._finish_get_info)
|
||||
|
||||
self.thread_get_history = GetAppHistory()
|
||||
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()
|
||||
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.toolbar_bottom = QToolBar()
|
||||
self.label_updates = QLabel('')
|
||||
self.label_updates.setStyleSheet("color: {}; font-weight: bold".format(DARK_ORANGE))
|
||||
self.toolbar_bottom.addWidget(self.label_updates)
|
||||
|
||||
spacer = QWidget()
|
||||
spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
self.toolbar_bottom.addWidget(spacer)
|
||||
|
||||
self.progress_bar = QProgressBar()
|
||||
self.progress_bar.setTextVisible(False)
|
||||
self.ref_progress_bar = self.toolbar_bottom.addWidget(self.progress_bar)
|
||||
|
||||
spacer = QWidget()
|
||||
spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
self.toolbar_bottom.addWidget(spacer)
|
||||
|
||||
self.label_flatpak = QLabel(self._get_flatpak_label())
|
||||
self.toolbar_bottom.addWidget(self.label_flatpak)
|
||||
|
||||
self.layout.addWidget(self.toolbar_bottom)
|
||||
|
||||
self.centralize()
|
||||
|
||||
def _change_table_headers_policy(self, policy: QHeaderView = QHeaderView.ResizeToContents):
|
||||
header_horizontal = self.table_apps.horizontalHeader()
|
||||
for i in range(0, len(self.column_names)):
|
||||
header_horizontal.setSectionResizeMode(i, policy)
|
||||
|
||||
def changeEvent(self, e: QEvent):
|
||||
|
||||
if isinstance(e, QWindowStateChangeEvent):
|
||||
self._change_table_headers_policy(QHeaderView.Stretch if self.isMaximized() else QHeaderView.ResizeToContents)
|
||||
policy = QHeaderView.Stretch if self.isMaximized() else QHeaderView.ResizeToContents
|
||||
self.table_apps.change_headers_policy(policy)
|
||||
|
||||
def closeEvent(self, event):
|
||||
|
||||
@@ -162,35 +190,19 @@ class ManageWindow(QWidget):
|
||||
event.ignore()
|
||||
self.hide()
|
||||
|
||||
def _load_icon(self, http_response):
|
||||
icon_url = http_response.url().toString()
|
||||
pixmap = QPixmap()
|
||||
pixmap.loadFromData(http_response.readAll())
|
||||
icon = QIcon(pixmap)
|
||||
self.icon_cache[icon_url] = icon
|
||||
|
||||
for idx, app in enumerate(self.apps):
|
||||
if app['model']['icon'] == icon_url:
|
||||
self.table_apps.item(idx, 0).setIcon(icon)
|
||||
self.resize_and_center()
|
||||
break
|
||||
|
||||
def _check_flatpak_installed(self):
|
||||
|
||||
if not self.controller.check_installed():
|
||||
error_msg = QMessageBox()
|
||||
error_msg.setIcon(QMessageBox.Critical)
|
||||
error_msg.setWindowTitle(self.locale_keys['popup.flatpak_not_installed.title'])
|
||||
error_msg.setText(self.locale_keys['popup.flatpak_not_installed.msg'] + '...')
|
||||
error_msg.setWindowIcon(self.icon_flathub)
|
||||
error_msg.exec_()
|
||||
if not flatpak.is_installed():
|
||||
dialog.show_error(title=self.locale_keys['popup.flatpak_not_installed.title'],
|
||||
body=self.locale_keys['popup.flatpak_not_installed.msg'] + '...',
|
||||
icon=self.icon_flathub)
|
||||
exit(1)
|
||||
|
||||
if self.label_flatpak:
|
||||
self.label_flatpak.setText(self._get_flatpak_label())
|
||||
|
||||
def _get_flatpak_label(self):
|
||||
return 'flatpak: ' + self.controller.get_version()
|
||||
return 'flatpak: ' + flatpak.get_version()
|
||||
|
||||
def _acquire_lock(self):
|
||||
|
||||
@@ -211,24 +223,52 @@ class ManageWindow(QWidget):
|
||||
|
||||
self.thread_lock.release()
|
||||
|
||||
def _hide_output(self):
|
||||
self.textarea_output.clear()
|
||||
self.textarea_output.hide()
|
||||
|
||||
def _show_output(self):
|
||||
self.textarea_output.clear()
|
||||
self.textarea_output.show()
|
||||
|
||||
def refresh(self, clear_output: bool = True):
|
||||
|
||||
if self._acquire_lock():
|
||||
self._check_flatpak_installed()
|
||||
self._begin_action(self.locale_keys['manage_window.status.refreshing'] + '...')
|
||||
self._begin_action(self.locale_keys['manage_window.status.refreshing'])
|
||||
|
||||
if clear_output:
|
||||
self.textarea_output.clear()
|
||||
self.textarea_output.hide()
|
||||
self._hide_output()
|
||||
|
||||
self.thread_refresh.start()
|
||||
|
||||
def _finish_refresh(self):
|
||||
def _finish_refresh(self, apps: List[dict]):
|
||||
|
||||
self.update_apps(self.thread_refresh.apps)
|
||||
self.update_apps(apps)
|
||||
self.finish_action()
|
||||
self._release_lock()
|
||||
|
||||
def uninstall_app(self, app_ref: str):
|
||||
self._check_flatpak_installed()
|
||||
|
||||
if self._acquire_lock():
|
||||
self.textarea_output.clear()
|
||||
self.textarea_output.setVisible(True)
|
||||
self._begin_action(self.locale_keys['manage_window.status.uninstalling'])
|
||||
|
||||
self.thread_uninstall.app_ref = app_ref
|
||||
self.thread_uninstall.start()
|
||||
|
||||
def _finish_uninstall(self):
|
||||
self.finish_action()
|
||||
self._release_lock()
|
||||
self.refresh(clear_output=False)
|
||||
|
||||
def _finish_downgrade(self):
|
||||
self.finish_action()
|
||||
self._release_lock()
|
||||
self.refresh(clear_output=False)
|
||||
|
||||
def filter_only_apps(self, only_apps: int):
|
||||
|
||||
if self.apps:
|
||||
@@ -240,8 +280,8 @@ class ManageWindow(QWidget):
|
||||
app['visible'] = not hidden
|
||||
|
||||
self.change_update_state()
|
||||
self._change_table_headers_policy(QHeaderView.Stretch)
|
||||
self._change_table_headers_policy()
|
||||
self.table_apps.change_headers_policy(QHeaderView.Stretch)
|
||||
self.table_apps.change_headers_policy()
|
||||
self.resize_and_center()
|
||||
|
||||
def change_update_state(self):
|
||||
@@ -259,7 +299,10 @@ class ManageWindow(QWidget):
|
||||
|
||||
total_updates = app_updates + runtime_updates
|
||||
if total_updates > 0:
|
||||
self.label_updates.setText('{}: {} ( {} apps | {} runtimes )'.format(self.locale_keys['manage_window.label.updates'], total_updates, app_updates, runtime_updates))
|
||||
self.label_updates.setText('{}: {}'.format(self.locale_keys['manage_window.label.updates'], total_updates))
|
||||
self.label_updates.setToolTip('{} {} | {} runtimes'.format(app_updates,
|
||||
self.locale_keys['manage_window.checkbox.only_apps'].lower(),
|
||||
runtime_updates))
|
||||
else:
|
||||
self.label_updates.setText('')
|
||||
|
||||
@@ -268,9 +311,8 @@ class ManageWindow(QWidget):
|
||||
enable_bt_update = True
|
||||
break
|
||||
|
||||
self.bt_update.setEnabled(enable_bt_update)
|
||||
|
||||
self.tray_icon.notify_updates(total_updates)
|
||||
self.bt_upgrade.setEnabled(enable_bt_update)
|
||||
self.tray_icon.notify_updates([app['model'] for app in self.apps if app['model']['update']])
|
||||
|
||||
def centralize(self):
|
||||
geo = self.frameGeometry()
|
||||
@@ -282,79 +324,33 @@ class ManageWindow(QWidget):
|
||||
def update_apps(self, apps: List[dict]):
|
||||
self._check_flatpak_installed()
|
||||
|
||||
self.table_apps.setEnabled(True)
|
||||
self.apps = []
|
||||
|
||||
self.table_apps.setRowCount(len(apps) if apps else 0)
|
||||
napps = 0 # number of apps (not runtimes)
|
||||
|
||||
if apps:
|
||||
for idx, app in enumerate(apps):
|
||||
|
||||
col_name = QTableWidgetItem()
|
||||
col_name.setText(app['name'])
|
||||
col_name.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
|
||||
if not app['icon']:
|
||||
col_name.setIcon(self.icon_flathub)
|
||||
else:
|
||||
cached_icon = self.icon_cache.get(app['icon'])
|
||||
|
||||
if cached_icon:
|
||||
col_name.setIcon(cached_icon)
|
||||
else:
|
||||
col_name.setIcon(self.icon_flathub)
|
||||
self.network_man.get(QNetworkRequest(QUrl(app['icon'])))
|
||||
|
||||
self.table_apps.setItem(idx, 0, col_name)
|
||||
|
||||
col_version = QTableWidgetItem()
|
||||
col_version.setText(app['version'])
|
||||
col_version.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
self.table_apps.setItem(idx, 1, col_version)
|
||||
|
||||
col_release = QTableWidgetItem()
|
||||
col_release.setText(app['latest_version'])
|
||||
col_release.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
self.table_apps.setItem(idx, 2, col_release)
|
||||
|
||||
if app['update']:
|
||||
col_release.setForeground(QColor('orange'))
|
||||
|
||||
col_branch = QTableWidgetItem()
|
||||
col_branch.setText(app['branch'])
|
||||
col_branch.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
self.table_apps.setItem(idx, 3, col_branch)
|
||||
|
||||
col_arch = QTableWidgetItem()
|
||||
col_arch.setText(app['arch'])
|
||||
col_arch.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
self.table_apps.setItem(idx, 4, col_arch)
|
||||
|
||||
col_package = QTableWidgetItem()
|
||||
col_package.setText(app['ref'])
|
||||
col_package.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
self.table_apps.setItem(idx, 5, col_package)
|
||||
|
||||
col_origin = QTableWidgetItem()
|
||||
col_origin.setText(app['origin'])
|
||||
col_origin.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
self.table_apps.setItem(idx, 6, col_origin)
|
||||
|
||||
for app in apps:
|
||||
app_model = {'model': app,
|
||||
'update_checked': app['update'],
|
||||
'visible': not app['runtime'] or not self.checkbox_only_apps.isChecked()}
|
||||
|
||||
col_update = UpdateToggleButton(app_model, self, app['update']) if app['update'] else None
|
||||
self.table_apps.setCellWidget(idx, 7, col_update)
|
||||
|
||||
napps += 1 if not app['runtime'] else 0
|
||||
self.apps.append(app_model)
|
||||
|
||||
if napps == 0:
|
||||
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.table_apps.update_apps(self.apps)
|
||||
self.change_update_state()
|
||||
self.filter_only_apps(2 if self.checkbox_only_apps.isChecked() else 0)
|
||||
self.resize_and_center()
|
||||
|
||||
def resize_and_center(self):
|
||||
new_width = reduce(operator.add, [self.table_apps.columnWidth(i) for i in range(len(self.column_names))]) * 1.05
|
||||
new_width = reduce(operator.add, [self.table_apps.columnWidth(i) for i in range(len(self.table_apps.column_names))]) * 1.05
|
||||
self.resize(new_width, self.height())
|
||||
self.centralize()
|
||||
|
||||
@@ -369,7 +365,7 @@ class ManageWindow(QWidget):
|
||||
self.textarea_output.clear()
|
||||
self.textarea_output.setVisible(True)
|
||||
|
||||
self._begin_action(self.locale_keys['manage_window.status.updating'] + '...')
|
||||
self._begin_action(self.locale_keys['manage_window.status.upgrading'])
|
||||
self.thread_update.refs_to_update = to_update
|
||||
self.thread_update.start()
|
||||
|
||||
@@ -382,50 +378,121 @@ class ManageWindow(QWidget):
|
||||
self.textarea_output.appendPlainText(output)
|
||||
|
||||
def _begin_action(self, action_label: str):
|
||||
self.label_status.setText(action_label)
|
||||
self.bt_update.setEnabled(False)
|
||||
self.thread_animate_progress.stop = False
|
||||
self.thread_animate_progress.start()
|
||||
self.ref_progress_bar.setVisible(True)
|
||||
self.progress_bar.setValue(50)
|
||||
self.label_status.setText(action_label + "...")
|
||||
self.toolbar_search.setVisible(False)
|
||||
self.bt_upgrade.setEnabled(False)
|
||||
self.bt_refresh.setEnabled(False)
|
||||
self.checkbox_only_apps.setEnabled(False)
|
||||
self.table_apps.setEnabled(False)
|
||||
|
||||
def finish_action(self):
|
||||
def finish_action(self, clear_search: bool = True):
|
||||
self.thread_animate_progress.stop = True
|
||||
self.ref_progress_bar.setVisible(False)
|
||||
self.progress_bar.setValue(0)
|
||||
self.progress_bar.setVisible(False)
|
||||
self.bt_refresh.setEnabled(True)
|
||||
self.toolbar_search.setVisible(True)
|
||||
self.checkbox_only_apps.setEnabled(True)
|
||||
self.table_apps.setEnabled(True)
|
||||
self.input_search.setEnabled(True)
|
||||
self.label_status.setText('')
|
||||
|
||||
if clear_search:
|
||||
self.input_search.setText('')
|
||||
|
||||
# Threaded actions
|
||||
class UpdateSelectedApps(QThread):
|
||||
def downgrade_app(self, app: dict):
|
||||
|
||||
signal_finished = pyqtSignal()
|
||||
signal_output = pyqtSignal(str)
|
||||
self._check_flatpak_installed()
|
||||
|
||||
def __init__(self, controller: FlatpakController):
|
||||
super(UpdateSelectedApps, self).__init__()
|
||||
self.controller = controller
|
||||
self.refs_to_update = []
|
||||
if self._acquire_lock():
|
||||
|
||||
def run(self):
|
||||
pwd = None
|
||||
|
||||
for app_ref in self.refs_to_update:
|
||||
for output in self.controller.update(app_ref):
|
||||
line = output.decode().strip()
|
||||
if line:
|
||||
self.signal_output.emit(line)
|
||||
if not is_root():
|
||||
pwd, ok = ask_root_password(self.locale_keys)
|
||||
|
||||
self.signal_finished.emit()
|
||||
if not ok:
|
||||
self._release_lock()
|
||||
return
|
||||
|
||||
self.textarea_output.clear()
|
||||
self.textarea_output.setVisible(True)
|
||||
self._begin_action(self.locale_keys['manage_window.status.downgrading'])
|
||||
|
||||
class RefreshApps(QThread):
|
||||
self.thread_downgrade.app = app
|
||||
self.thread_downgrade.root_password = pwd
|
||||
self.thread_downgrade.start()
|
||||
|
||||
signal = pyqtSignal()
|
||||
def get_app_info(self, app: dict):
|
||||
|
||||
def __init__(self, controller: FlatpakController):
|
||||
super(RefreshApps, self).__init__()
|
||||
self.controller = controller
|
||||
self.apps = None
|
||||
if self._acquire_lock():
|
||||
self.textarea_output.clear()
|
||||
self.textarea_output.setVisible(False)
|
||||
self._begin_action(self.locale_keys['manage_window.status.info'])
|
||||
|
||||
def run(self):
|
||||
self.apps = self.controller.refresh()
|
||||
self.signal.emit()
|
||||
self.thread_get_info.app = app
|
||||
self.thread_get_info.start()
|
||||
|
||||
def get_app_history(self, app: dict):
|
||||
if self._acquire_lock():
|
||||
self.textarea_output.clear()
|
||||
self.textarea_output.setVisible(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._release_lock()
|
||||
self.finish_action()
|
||||
self.change_update_state()
|
||||
dialog_info = InfoDialog(app_info, self.table_apps.get_selected_app_icon(), self.locale_keys)
|
||||
dialog_info.exec_()
|
||||
|
||||
def _finish_get_history(self, app: dict):
|
||||
self._release_lock()
|
||||
self.finish_action()
|
||||
self.change_update_state()
|
||||
dialog_history = HistoryDialog(app, self.table_apps.get_selected_app_icon(), self.locale_keys)
|
||||
dialog_history.exec_()
|
||||
|
||||
def search(self):
|
||||
|
||||
word = self.input_search.text().strip()
|
||||
|
||||
if word and self._acquire_lock():
|
||||
self.textarea_output.clear()
|
||||
self.textarea_output.setVisible(False)
|
||||
self._begin_action(self.locale_keys['manage_window.status.searching'])
|
||||
self.thread_search.word = word
|
||||
self.thread_search.start()
|
||||
|
||||
def _finish_search(self, apps_found: List[dict]):
|
||||
|
||||
self._release_lock()
|
||||
self.finish_action(clear_search=False)
|
||||
self.update_apps(apps_found)
|
||||
|
||||
def install_app(self, app: dict):
|
||||
|
||||
self._check_flatpak_installed()
|
||||
|
||||
if self._acquire_lock():
|
||||
self._begin_action(self.locale_keys['manage_window.status.installing'])
|
||||
self._show_output()
|
||||
|
||||
self.thread_install.app = app
|
||||
self.thread_install.start()
|
||||
|
||||
def _finish_install(self):
|
||||
self.input_search.setText('')
|
||||
self.finish_action()
|
||||
self._release_lock()
|
||||
self.refresh(clear_output=False)
|
||||
|
||||
def _update_progress(self, value: int):
|
||||
self.progress_bar.setValue(value)
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
pyqt5>=5.12
|
||||
requests>=2.22
|
||||
requests>=2.22
|
||||
colorama>=0.4.1
|
||||