This commit is contained in:
Vinícius Moreira
2022-11-05 15:07:04 -03:00
committed by GitHub
24 changed files with 321 additions and 115 deletions

View File

@@ -4,6 +4,36 @@ 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/). The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [0.10.4] 2022-11-05
### Improvements
- Arch
- replaced some system calls by Python calls
### Fixes
- AppImage
- some desktop entries not being displayed on the desktop environment menu (requires the AppImage to be reinstalled) [#287](https://github.com/vinifmor/bauh/issues/287)
- Arch
- not detecting some package replacements when upgrading due to conflict information having logic operators (e.g: `at-spi2-core: 2.44.1 -> 2.46.0` should replace the installed `at-spi2-atk: 2.38`)
- not considering some conflict expressions when retrieving upgrade requirements (it could lead to a system breakage depending on the conflict)
- Debian
- not properly handling packages with names ending with `:i386` [#298](https://github.com/vinifmor/bauh/issues/298)
- Packaging
- AppImage: download certificate issue [#280](https://github.com/vinifmor/bauh/issues/280)
- GUI
- initialization panel size not based on the current display device size
- management panel:
- not fully maximizing
- not maximizing based on the current display device size (for multiple display setups)
- not respecting a minimal/maximum width based on the current display device size (for multiple display setup)
- dialogs not respecting the current display device width
- not readjusting size after maximizing/minimizing
### Localization (i18n)
- Italian ([@albanobattistella](https://github.com/albanobattistella), [@luca-digrazia](https://github.com/luca-digrazia))
- Turkish ([@agahemir](https://github.com/agahemir))
## [0.10.3] 2022-05-30 ## [0.10.3] 2022-05-30
### Features ### Features

View File

@@ -554,6 +554,7 @@ the `view` code is only attached to them (it does not know how the `gems` handle
- Separate modules for each packaging technology - Separate modules for each packaging technology
- Memory and performance improvements - Memory and performance improvements
- Improve user experience - Improve user experience
- The current development changes can be checked [here](https://github.com/vinifmor/bauh/blob/staging/CHANGELOG.md)
#### <a name="donations">Donations</a> #### <a name="donations">Donations</a>
- You can support this project through [ko-fi](https://ko-fi.com/vinifmor). - You can support this project through [ko-fi](https://ko-fi.com/vinifmor).

View File

@@ -1,4 +1,4 @@
__version__ = '0.10.3' __version__ = '0.10.4'
__app_name__ = 'bauh' __app_name__ = 'bauh'
import os import os

View File

@@ -528,13 +528,13 @@ class AppImageManager(SoftwareManager, SettingsController):
finally: finally:
releases_con.close() releases_con.close()
def _find_desktop_file(self, folder: str) -> str: def _find_desktop_file(self, folder: str) -> Optional[str]:
for r, d, files in os.walk(folder): for r, d, files in os.walk(folder):
for f in files: for f in files:
if f.endswith('.desktop'): if f.endswith('.desktop'):
return f return f
def _find_icon_file(self, folder: str) -> str: def _find_icon_file(self, folder: str) -> Optional[str]:
for f in glob.glob(folder + ('/**' if not folder.endswith('/') else '**'), recursive=True): for f in glob.glob(folder + ('/**' if not folder.endswith('/') else '**'), recursive=True):
if RE_ICON_ENDS_WITH.match(f): if RE_ICON_ENDS_WITH.match(f):
return f return f

View File

@@ -1,15 +1,16 @@
import os import os
import re import re
from typing import Optional
RE_DESKTOP_EXEC = re.compile(r'(Exec\s*=(.+)(\n?))') RE_DESKTOP_EXEC = re.compile(r'(\n?\s*\w*Exec\s*=(.+))')
RE_MANY_SPACES = re.compile(r'\s+') RE_MANY_SPACES = re.compile(r'\s+')
def find_appimage_file(folder: str) -> str: def find_appimage_file(folder: str) -> Optional[str]:
for r, d, files in os.walk(folder): for r, d, files in os.walk(folder):
for f in files: for f in files:
if f.lower().endswith('.appimage'): if f.lower().endswith('.appimage'):
return '{}/{}'.format(folder, f) return f'{folder}/{f}'
def replace_desktop_entry_exec_command(desktop_entry: str, appname: str, file_path: str) -> str: def replace_desktop_entry_exec_command(desktop_entry: str, appname: str, file_path: str) -> str:
@@ -23,6 +24,11 @@ def replace_desktop_entry_exec_command(desktop_entry: str, appname: str, file_pa
for exec_groups in execs: for exec_groups in execs:
full_match = exec_groups[0] full_match = exec_groups[0]
if full_match.strip().startswith("TryExec"): # TryExec cause issues in some DE to display the app icon
final_entry = final_entry.replace(full_match, "")
continue
cmd = RE_MANY_SPACES.sub(' ', exec_groups[1].strip()) cmd = RE_MANY_SPACES.sub(' ', exec_groups[1].strip())
if cmd: if cmd:
words = cmd.split(' ') words = cmd.split(' ')
@@ -30,12 +36,12 @@ def replace_desktop_entry_exec_command(desktop_entry: str, appname: str, file_pa
for idx in range(len(words)): for idx in range(len(words)):
if words[idx].lower() == treated_name: if words[idx].lower() == treated_name:
words[idx] = '"{}"'.format(file_path) words[idx] = f'"{file_path}"'
changed = True changed = True
break break
if not changed: if not changed:
words = ['"{}"'.format(file_path)] words = [f'"{file_path}"']
final_entry = final_entry.replace(full_match, full_match.replace(exec_groups[1], ' '.join(words))) final_entry = final_entry.replace(full_match, full_match.replace(exec_groups[1], ' '.join(words)))

View File

@@ -2,7 +2,7 @@ import re
import traceback import traceback
from logging import Logger from logging import Logger
from threading import Thread from threading import Thread
from typing import Set, List, Tuple, Dict, Iterable, Optional, Generator from typing import Set, List, Tuple, Dict, Iterable, Optional, Generator, Pattern
from bauh.api.abstract.handler import ProcessWatcher from bauh.api.abstract.handler import ProcessWatcher
from bauh.gems.arch import pacman, message, sorting, confirmation from bauh.gems.arch import pacman, message, sorting, confirmation
@@ -14,12 +14,20 @@ from bauh.view.util.translation import I18n
class DependenciesAnalyser: class DependenciesAnalyser:
_re_dep_operator: Optional[Pattern] = None
def __init__(self, aur_client: AURClient, i18n: I18n, logger: Logger): def __init__(self, aur_client: AURClient, i18n: I18n, logger: Logger):
self.aur_client = aur_client self.aur_client = aur_client
self.i18n = i18n self.i18n = i18n
self.re_dep_operator = re.compile(r'([<>=]+)')
self._log = logger self._log = logger
@classmethod
def re_dep_operator(cls) -> Pattern:
if not cls._re_dep_operator:
cls._re_dep_operator = re.compile(r'([<>=]+)')
return cls._re_dep_operator
def _fill_repository(self, name: str, output: List[Tuple[str, str]]): def _fill_repository(self, name: str, output: List[Tuple[str, str]]):
repository = pacman.read_repository_from_info(name) repository = pacman.read_repository_from_info(name)
@@ -225,7 +233,7 @@ class DependenciesAnalyser:
raise Exception(f"Could not retrieve information from providers: " raise Exception(f"Could not retrieve information from providers: "
f"{', '.join(data_not_found)}") f"{', '.join(data_not_found)}")
split_informed_dep = self.re_dep_operator.split(dep_exp) split_informed_dep = self.re_dep_operator().split(dep_exp)
version_required = split_informed_dep[2] version_required = split_informed_dep[2]
exp_op = split_informed_dep[1] if split_informed_dep[1] != '=' else '==' exp_op = split_informed_dep[1] if split_informed_dep[1] != '=' else '=='
@@ -237,7 +245,7 @@ class DependenciesAnalyser:
info = missing_providers_data[p] info = missing_providers_data[p]
for provided_exp in info['p']: for provided_exp in info['p']:
split_dep = self.re_dep_operator.split(provided_exp) split_dep = self.re_dep_operator().split(provided_exp)
if len(split_dep) == 3 and split_dep[0] == dep_name: if len(split_dep) == 3 and split_dep[0] == dep_name:
version_provided = split_dep[2] version_provided = split_dep[2]
@@ -253,7 +261,7 @@ class DependenciesAnalyser:
return return
else: else:
for _, dep_data in self.aur_client.gen_updates_data((dep_name,)): for _, dep_data in self.aur_client.gen_updates_data((dep_name,)):
split_informed_dep = self.re_dep_operator.split(dep_exp) split_informed_dep = self.re_dep_operator().split(dep_exp)
version_required = split_informed_dep[2] version_required = split_informed_dep[2]
exp_op = split_informed_dep[1].strip() exp_op = split_informed_dep[1].strip()
@@ -270,7 +278,7 @@ class DependenciesAnalyser:
if dep_name == dep_exp: if dep_name == dep_exp:
version_required, exp_op = None, None version_required, exp_op = None, None
else: else:
split_informed_dep = self.re_dep_operator.split(dep_exp) split_informed_dep = self.re_dep_operator().split(dep_exp)
version_required = split_informed_dep[2] version_required = split_informed_dep[2]
exp_op = split_informed_dep[1] if split_informed_dep[1] != '=' else '==' exp_op = split_informed_dep[1] if split_informed_dep[1] != '=' else '=='
@@ -405,7 +413,7 @@ class DependenciesAnalyser:
if dep in pkgs_data: if dep in pkgs_data:
continue continue
if dep not in provided_map: if dep not in provided_map:
dep_split = self.re_dep_operator.split(dep) dep_split = self.re_dep_operator().split(dep)
dep_name = dep_split[0].strip() dep_name = dep_split[0].strip()
if dep_name not in deps_checked: if dep_name not in deps_checked:

View File

@@ -5,7 +5,7 @@ import shutil
import traceback import traceback
from io import StringIO from io import StringIO
from threading import Thread from threading import Thread
from typing import List, Set, Tuple, Dict, Iterable, Optional, Any from typing import List, Set, Tuple, Dict, Iterable, Optional, Any, Pattern, Collection
from colorama import Fore from colorama import Fore
@@ -26,6 +26,7 @@ RE_REMOVE_TRANSITIVE_DEPS = re.compile(r'removing\s([\w\-_]+)\s.+required\sby\s(
RE_AVAILABLE_MIRRORS = re.compile(r'.+\s+OK\s+.+\s+(\d+:\d+)\s+.+(http.+)') RE_AVAILABLE_MIRRORS = re.compile(r'.+\s+OK\s+.+\s+(\d+:\d+)\s+.+(http.+)')
RE_PACMAN_SYNC_FIRST = re.compile(r'SyncFirst\s*=\s*(.+)') RE_PACMAN_SYNC_FIRST = re.compile(r'SyncFirst\s*=\s*(.+)')
RE_DESKTOP_FILES = re.compile(r'\n?([\w\-_]+)\s+(/usr/share/.+\.desktop)') RE_DESKTOP_FILES = re.compile(r'\n?([\w\-_]+)\s+(/usr/share/.+\.desktop)')
RE_IGNORED_PACKAGES: Optional[Pattern] = None
def is_available() -> bool: def is_available() -> bool:
@@ -234,21 +235,26 @@ def sign_key(key: str, root_password: Optional[str]) -> SystemProcess:
def list_ignored_packages(config_path: str = '/etc/pacman.conf') -> Set[str]: def list_ignored_packages(config_path: str = '/etc/pacman.conf') -> Set[str]:
ignored = set() ignored = set()
try: try:
pacman_conf = new_subprocess(['cat', config_path]) with open(config_path, 'r') as f:
grep = new_subprocess(['grep', '-Eo', r'\s*#*\s*ignorepkg\s*=\s*.+'], stdin=pacman_conf.stdout) file_content = f.read()
for o in grep.stdout:
if o:
line = o.decode().strip()
if not line.startswith('#'): global RE_IGNORED_PACKAGES
ignored.add(line.split('=')[1].strip())
pacman_conf.terminate() if not RE_IGNORED_PACKAGES:
grep.terminate() RE_IGNORED_PACKAGES = re.compile(r'[\s#]*ignorepkg\s*=\s*.+', re.IGNORECASE)
return ignored
except: for raw_line in RE_IGNORED_PACKAGES.findall(file_content):
line = raw_line.strip()
if line and not line.startswith("#"):
ignored.add(line.split("=")[1].strip())
except (FileNotFoundError, OSError):
pass
except Exception:
traceback.print_exc() traceback.print_exc()
return ignored return ignored
@@ -1075,3 +1081,18 @@ def map_available_packages() -> Optional[Dict[str, Any]]:
'r': package_data[0].strip(), 'r': package_data[0].strip(),
'i': len(package_data) == 4 and 'installed' in package_data[3]} 'i': len(package_data) == 4 and 'installed' in package_data[3]}
return res return res
def map_installed(pkgs: Optional[Collection[str]] = None) -> Optional[Dict[str, str]]:
output = run_cmd(f"pacman -Q {' '.join({*pkgs} if pkgs else '')}".strip(), print_error=False)
if output:
res = dict()
for raw_line in output.split("\n"):
line = raw_line.strip()
if line:
pkg_version = line.split(" ")
if len(pkg_version) == 2:
res[pkg_version[0]] = pkg_version[1]
return res

View File

@@ -22,7 +22,7 @@ class UpdateRequirementsContext:
aur_to_update: Dict[str, ArchPackage], repo_to_install: Dict[str, ArchPackage], aur_to_update: Dict[str, ArchPackage], repo_to_install: Dict[str, ArchPackage],
aur_to_install: Dict[str, ArchPackage], to_install: Dict[str, ArchPackage], aur_to_install: Dict[str, ArchPackage], to_install: Dict[str, ArchPackage],
pkgs_data: Dict[str, dict], cannot_upgrade: Dict[str, UpgradeRequirement], pkgs_data: Dict[str, dict], cannot_upgrade: Dict[str, UpgradeRequirement],
to_remove: Dict[str, UpgradeRequirement], installed_names: Set[str], provided_map: Dict[str, Set[str]], to_remove: Dict[str, UpgradeRequirement], installed_names: Dict[str, str], provided_map: Dict[str, Set[str]],
aur_index: Set[str], arch_config: dict, remote_provided_map: Dict[str, Set[str]], remote_repo_map: Dict[str, str], aur_index: Set[str], arch_config: dict, remote_provided_map: Dict[str, Set[str]], remote_repo_map: Dict[str, str],
root_password: Optional[str], aur_supported: bool): root_password: Optional[str], aur_supported: bool):
self.to_update = to_update self.to_update = to_update
@@ -33,7 +33,7 @@ class UpdateRequirementsContext:
self.pkgs_data = pkgs_data self.pkgs_data = pkgs_data
self.cannot_upgrade = cannot_upgrade self.cannot_upgrade = cannot_upgrade
self.root_password = root_password self.root_password = root_password
self.installed_names = installed_names self.installed = installed_names
self.provided_map = provided_map self.provided_map = provided_map
self.to_remove = to_remove self.to_remove = to_remove
self.to_install = to_install self.to_install = to_install
@@ -149,12 +149,21 @@ class UpdatesSummarizer:
for p, data in context.pkgs_data.items(): for p, data in context.pkgs_data.items():
if data['c']: if data['c']:
for c in data['c']: for c in data['c']:
if c and c != p and c in context.installed_names: if c:
# source = provided_map[c] name_op_exp = DependenciesAnalyser.re_dep_operator().split(c)
root_conflict[c] = p conflict_name = name_op_exp[0]
if (p, c) in root_conflict.items(): if conflict_name != p:
mutual_conflicts[c] = p conflict_version = context.installed.get(conflict_name)
if conflict_version:
if len(name_op_exp) == 1 or match_required_version(conflict_version,
name_op_exp[1],
name_op_exp[2]):
root_conflict[conflict_name] = p
if (p, conflict_name) in root_conflict.items():
mutual_conflicts[conflict_name] = p
if mutual_conflicts: if mutual_conflicts:
for pkg1, pkg2 in mutual_conflicts.items(): for pkg1, pkg2 in mutual_conflicts.items():
@@ -278,8 +287,8 @@ class UpdatesSummarizer:
ti = time.time() ti = time.time()
self.logger.info("Filling provided names") self.logger.info("Filling provided names")
if not context.installed_names: if not context.installed:
context.installed_names = pacman.list_installed_names() context.installed = pacman.map_installed()
installed_to_ignore = set() installed_to_ignore = set()
@@ -305,8 +314,8 @@ class UpdatesSummarizer:
if len(split_provided) > 1 and split_provided[0] != p: if len(split_provided) > 1 and split_provided[0] != p:
pacman.fill_provided_map(split_provided[0], pkgname, context.provided_map) pacman.fill_provided_map(split_provided[0], pkgname, context.provided_map)
if installed_to_ignore: # filling the provided names of the installed if context.installed and installed_to_ignore: # filling the provided names of the installed
installed_to_query = context.installed_names.difference(installed_to_ignore) installed_to_query = {*context.installed}.difference(installed_to_ignore)
if installed_to_query: if installed_to_query:
context.provided_map.update(pacman.map_provided(remote=False, pkgs=installed_to_query)) context.provided_map.update(pacman.map_provided(remote=False, pkgs=installed_to_query))
@@ -374,7 +383,7 @@ class UpdatesSummarizer:
remote_repo_map = pacman.map_repositories() remote_repo_map = pacman.map_repositories()
context = UpdateRequirementsContext(to_update={}, repo_to_update={}, aur_to_update={}, repo_to_install={}, context = UpdateRequirementsContext(to_update={}, repo_to_update={}, aur_to_update={}, repo_to_install={},
aur_to_install={}, to_install={}, pkgs_data={}, cannot_upgrade={}, aur_to_install={}, to_install={}, pkgs_data={}, cannot_upgrade={},
to_remove={}, installed_names=set(), provided_map={}, aur_index=set(), to_remove={}, installed_names=dict(), provided_map={}, aur_index=set(),
arch_config=arch_config, root_password=root_password, arch_config=arch_config, root_password=root_password,
remote_provided_map=remote_provided_map, remote_repo_map=remote_repo_map, remote_provided_map=remote_provided_map, remote_repo_map=remote_repo_map,
aur_supported=self.aur_supported) aur_supported=self.aur_supported)
@@ -604,7 +613,7 @@ class UpdatesSummarizer:
for r in reqs: for r in reqs:
if r in transaction_pkgs: if r in transaction_pkgs:
reqs_in_transaction.add(r) reqs_in_transaction.add(r)
elif r in context.installed_names: elif r in context.installed:
reqs_not_in_transaction.add(r) reqs_not_in_transaction.add(r)
if not reqs_not_in_transaction and not reqs_in_transaction: if not reqs_not_in_transaction and not reqs_in_transaction:

View File

@@ -241,7 +241,7 @@ class Aptitude:
@property @property
def re_transaction_pkg(self) -> Pattern: def re_transaction_pkg(self) -> Pattern:
if self._re_transaction_pkg is None: if self._re_transaction_pkg is None:
self._re_transaction_pkg = re.compile(r'([a-zA-Z0-9\-_@~.+]+)({\w+})?\s*\[([a-zA-Z0-9\-_@~.+:]+)' self._re_transaction_pkg = re.compile(r'([a-zA-Z0-9\-_@~.+:]+)({\w+})?\s*\[([a-zA-Z0-9\-_@~.+:]+)'
r'(\s+->\s+([a-zA-Z0-9\-_@~.+:]+))?](\s*<([\-+]?[0-9.,]+\s+\w+)>)?') r'(\s+->\s+([a-zA-Z0-9\-_@~.+:]+))?](\s*<([\-+]?[0-9.,]+\s+\w+)>)?')
return self._re_transaction_pkg return self._re_transaction_pkg

View File

@@ -76,7 +76,6 @@ def new_manage_panel(app_args: Namespace, app_config: dict, logger: logging.Logg
manage_window = ManageWindow(i18n=i18n, manage_window = ManageWindow(i18n=i18n,
manager=manager, manager=manager,
icon_cache=icon_cache, icon_cache=icon_cache,
screen_size=screen_size,
config=app_config, config=app_config,
context=context, context=context,
http_client=http_client, http_client=http_client,
@@ -84,8 +83,7 @@ def new_manage_panel(app_args: Namespace, app_config: dict, logger: logging.Logg
force_suggestions=force_suggestions, force_suggestions=force_suggestions,
logger=logger) logger=logger)
prepare = PreparePanel(screen_size=screen_size, prepare = PreparePanel(context=context,
context=context,
manager=manager, manager=manager,
i18n=i18n, i18n=i18n,
manage_window=manage_window, manage_window=manage_window,

View File

@@ -86,7 +86,7 @@ class AdaptableFileDownloader(FileDownloader):
return SimpleProcess(cmd=cmd, cwd=cwd, root_password=root_password) return SimpleProcess(cmd=cmd, cwd=cwd, root_password=root_password)
def _get_wget_process(self, url: str, output_path: str, cwd: str, root_password: Optional[str]) -> SimpleProcess: def _get_wget_process(self, url: str, output_path: str, cwd: str, root_password: Optional[str]) -> SimpleProcess:
cmd = ['wget', url, '-c', '--retry-connrefused', '-t', '10', '--no-config', '-nc'] cmd = ['wget', url, '-c', '--retry-connrefused', '-t', '10', '-nc']
if not self.check_ssl: if not self.check_ssl:
cmd.append('--no-check-certificate') cmd.append('--no-check-certificate')

View File

@@ -15,6 +15,7 @@ from bauh.api.abstract.model import PackageStatus, CustomSoftwareAction
from bauh.commons.html import strip_html, bold from bauh.commons.html import strip_html, bold
from bauh.view.qt.components import IconButton, QCustomMenuAction, QCustomToolbar from bauh.view.qt.components import IconButton, QCustomMenuAction, QCustomToolbar
from bauh.view.qt.dialog import ConfirmationDialog from bauh.view.qt.dialog import ConfirmationDialog
from bauh.view.qt.qt_utils import get_current_screen_geometry
from bauh.view.qt.view_model import PackageView from bauh.view.qt.view_model import PackageView
from bauh.view.util.translation import I18n from bauh.view.util.translation import I18n
@@ -68,9 +69,8 @@ class PackagesTable(QTableWidget):
COL_NUMBER = 9 COL_NUMBER = 9
DEFAULT_ICON_SIZE = QSize(16, 16) DEFAULT_ICON_SIZE = QSize(16, 16)
def __init__(self, parent: QWidget, icon_cache: MemoryCache, download_icons: bool, screen_width: int): def __init__(self, parent: QWidget, icon_cache: MemoryCache, download_icons: bool):
super(PackagesTable, self).__init__() super(PackagesTable, self).__init__()
self.screen_width = screen_width
self.setObjectName('table_packages') self.setObjectName('table_packages')
self.setParent(parent) self.setParent(parent)
self.window = parent self.window = parent
@@ -180,15 +180,16 @@ class PackagesTable(QTableWidget):
action=custom_action) action=custom_action)
def refresh(self, pkg: PackageView): def refresh(self, pkg: PackageView):
self._update_row(pkg, update_check_enabled=False, change_update_col=False) screen_width = get_current_screen_geometry(self.parent()).width()
self._update_row(pkg, screen_width, update_check_enabled=False, change_update_col=False)
def update_package(self, pkg: PackageView, change_update_col: bool = False): def update_package(self, pkg: PackageView, screen_width: int, change_update_col: bool = False):
if self.download_icons and pkg.model.icon_url: if self.download_icons and pkg.model.icon_url:
icon_request = QNetworkRequest(QUrl(pkg.model.icon_url)) icon_request = QNetworkRequest(QUrl(pkg.model.icon_url))
icon_request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True) icon_request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
self.network_man.get(icon_request) self.network_man.get(icon_request)
self._update_row(pkg, change_update_col=change_update_col) self._update_row(pkg, screen_width, change_update_col=change_update_col)
def _uninstall(self, pkg: PackageView): def _uninstall(self, pkg: PackageView):
if ConfirmationDialog(title=self.i18n['manage_window.apps_table.row.actions.uninstall.popup.title'], if ConfirmationDialog(title=self.i18n['manage_window.apps_table.row.actions.uninstall.popup.title'],
@@ -255,6 +256,7 @@ class PackagesTable(QTableWidget):
self.setEnabled(True) self.setEnabled(True)
if pkgs: if pkgs:
screen_width = get_current_screen_geometry(self.parent()).width()
self.setColumnCount(self.COL_NUMBER if update_check_enabled else self.COL_NUMBER - 1) self.setColumnCount(self.COL_NUMBER if update_check_enabled else self.COL_NUMBER - 1)
self.setRowCount(len(pkgs)) self.setRowCount(len(pkgs))
@@ -266,16 +268,17 @@ class PackagesTable(QTableWidget):
icon_request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True) icon_request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
self.network_man.get(icon_request) self.network_man.get(icon_request)
self._update_row(pkg, update_check_enabled) self._update_row(pkg, screen_width, update_check_enabled)
self.scrollToTop() self.scrollToTop()
def _update_row(self, pkg: PackageView, update_check_enabled: bool = True, change_update_col: bool = True): def _update_row(self, pkg: PackageView, screen_width: int,
update_check_enabled: bool = True, change_update_col: bool = True):
self._set_col_icon(0, pkg) self._set_col_icon(0, pkg)
self._set_col_name(1, pkg) self._set_col_name(1, pkg, screen_width)
self._set_col_version(2, pkg) self._set_col_version(2, pkg, screen_width)
self._set_col_description(3, pkg) self._set_col_description(3, pkg, screen_width)
self._set_col_publisher(4, pkg) self._set_col_publisher(4, pkg, screen_width)
self._set_col_type(5, pkg) self._set_col_type(5, pkg)
self._set_col_installed(6, pkg) self._set_col_installed(6, pkg)
self._set_col_actions(7, pkg) self._set_col_actions(7, pkg)
@@ -355,7 +358,7 @@ class PackagesTable(QTableWidget):
col_type_icon.setToolTip(icon_data['tip']) col_type_icon.setToolTip(icon_data['tip'])
self.setCellWidget(pkg.table_index, col, col_type_icon) self.setCellWidget(pkg.table_index, col, col_type_icon)
def _set_col_version(self, col: int, pkg: PackageView): def _set_col_version(self, col: int, pkg: PackageView, screen_width: int):
label_version = QLabel(str(pkg.model.version if pkg.model.version else '?')) label_version = QLabel(str(pkg.model.version if pkg.model.version else '?'))
label_version.setObjectName('app_version') label_version.setObjectName('app_version')
label_version.setAlignment(Qt.AlignCenter) label_version.setAlignment(Qt.AlignCenter)
@@ -383,7 +386,7 @@ class PackagesTable(QTableWidget):
f"{self.i18n['version.latest']}: {pkg.model.latest_version})" f"{self.i18n['version.latest']}: {pkg.model.latest_version})"
label_version.setText(f"{label_version.text()} > {pkg.model.latest_version}") label_version.setText(f"{label_version.text()} > {pkg.model.latest_version}")
if label_version.sizeHint().width() / self.screen_width > 0.22: if label_version.sizeHint().width() / screen_width > 0.22:
label_version.setText(pkg.model.latest_version) label_version.setText(pkg.model.latest_version)
item.setToolTip(tooltip) item.setToolTip(tooltip)
@@ -426,7 +429,7 @@ class PackagesTable(QTableWidget):
self._update_icon(col_icon, icon) self._update_icon(col_icon, icon)
self.setCellWidget(pkg.table_index, col, col_icon) self.setCellWidget(pkg.table_index, col, col_icon)
def _set_col_name(self, col: int, pkg: PackageView): def _set_col_name(self, col: int, pkg: PackageView, screen_width: int):
col_name = QLabel() col_name = QLabel()
col_name.setObjectName('app_name') col_name.setObjectName('app_name')
col_name.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred) col_name.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred)
@@ -439,7 +442,7 @@ class PackagesTable(QTableWidget):
col_name.setToolTip(self.i18n['app.name'].lower()) col_name.setToolTip(self.i18n['app.name'].lower())
col_name.setText(name) col_name.setText(name)
screen_perc = col_name.sizeHint().width() / self.screen_width screen_perc = col_name.sizeHint().width() / screen_width
if screen_perc > 0.15: if screen_perc > 0.15:
max_chars = int(len(name) * 0.15 / screen_perc) - 3 max_chars = int(len(name) * 0.15 / screen_perc) - 3
@@ -454,7 +457,7 @@ class PackagesTable(QTableWidget):
sizes = icon.availableSizes() sizes = icon.availableSizes()
return sizes[-1] if sizes else self.DEFAULT_ICON_SIZE return sizes[-1] if sizes else self.DEFAULT_ICON_SIZE
def _set_col_description(self, col: int, pkg: PackageView): def _set_col_description(self, col: int, pkg: PackageView, screen_width: int):
item = QLabel() item = QLabel()
item.setObjectName('app_description') item.setObjectName('app_description')
@@ -468,7 +471,7 @@ class PackagesTable(QTableWidget):
item.setText(desc) item.setText(desc)
current_width_perc = item.sizeHint().width() / self.screen_width current_width_perc = item.sizeHint().width() / screen_width
if current_width_perc > 0.18: if current_width_perc > 0.18:
max_width = int(len(desc) * 0.18 / current_width_perc) - 3 max_width = int(len(desc) * 0.18 / current_width_perc) - 3
desc = desc[0:max_width] + '...' desc = desc[0:max_width] + '...'
@@ -479,7 +482,7 @@ class PackagesTable(QTableWidget):
self.setCellWidget(pkg.table_index, col, item) self.setCellWidget(pkg.table_index, col, item)
def _set_col_publisher(self, col: int, pkg: PackageView): def _set_col_publisher(self, col: int, pkg: PackageView, screen_width: int):
item = QToolBar() item = QToolBar()
publisher = pkg.model.get_publisher() publisher = pkg.model.get_publisher()
@@ -494,7 +497,7 @@ class PackagesTable(QTableWidget):
if publisher: if publisher:
lb_name.setText(publisher) lb_name.setText(publisher)
screen_perc = lb_name.sizeHint().width() / self.screen_width screen_perc = lb_name.sizeHint().width() / screen_width
if screen_perc > 0.12: if screen_perc > 0.12:
max_chars = int(len(publisher) * 0.12 / screen_perc) - 3 max_chars = int(len(publisher) * 0.12 / screen_perc) - 3

View File

@@ -1,6 +1,6 @@
from collections.abc import Iterable from collections.abc import Iterable
from PyQt5.QtCore import QSize, Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIcon, QCursor from PyQt5.QtGui import QIcon, QCursor
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QGroupBox, \ from PyQt5.QtWidgets import QDialog, QVBoxLayout, QGroupBox, \
QLineEdit, QLabel, QGridLayout, QPushButton, QPlainTextEdit, QScrollArea, QFrame, QWidget, QSizePolicy, \ QLineEdit, QLabel, QGridLayout, QPushButton, QPlainTextEdit, QScrollArea, QFrame, QWidget, QSizePolicy, \
@@ -8,6 +8,7 @@ from PyQt5.QtWidgets import QDialog, QVBoxLayout, QGroupBox, \
from bauh.api.abstract.cache import MemoryCache from bauh.api.abstract.cache import MemoryCache
from bauh.view.qt.components import new_spacer from bauh.view.qt.components import new_spacer
from bauh.view.qt.qt_utils import get_current_screen_geometry
from bauh.view.util.translation import I18n from bauh.view.util.translation import I18n
IGNORED_ATTRS = {'name', '__app__'} IGNORED_ATTRS = {'name', '__app__'}
@@ -15,10 +16,9 @@ IGNORED_ATTRS = {'name', '__app__'}
class InfoDialog(QDialog): class InfoDialog(QDialog):
def __init__(self, pkg_info: dict, icon_cache: MemoryCache, i18n: I18n, screen_size: QSize): def __init__(self, pkg_info: dict, icon_cache: MemoryCache, i18n: I18n):
super(InfoDialog, self).__init__() super(InfoDialog, self).__init__()
self.setWindowTitle(str(pkg_info['__app__'])) self.setWindowTitle(str(pkg_info['__app__']))
self.screen_size = screen_size
self.i18n = i18n self.i18n = i18n
layout = QVBoxLayout() layout = QVBoxLayout()
self.setLayout(layout) self.setLayout(layout)
@@ -108,7 +108,9 @@ class InfoDialog(QDialog):
lower_container.layout().addWidget(self.bt_close) lower_container.layout().addWidget(self.bt_close)
layout.addWidget(lower_container) layout.addWidget(lower_container)
self.setMinimumWidth(int(self.gbox_info.sizeHint().width() * 1.2)) self.setMinimumWidth(int(self.gbox_info.sizeHint().width() * 1.2))
self.setMaximumHeight(int(screen_size.height() * 0.8))
screen_height = get_current_screen_geometry().height()
self.setMaximumHeight(int(screen_height * 0.8))
self.adjustSize() self.adjustSize()
def _gen_show_button(self, idx: int, val): def _gen_show_button(self, idx: int, val):

View File

@@ -15,7 +15,7 @@ from bauh.api.abstract.controller import SoftwareManager, SoftwareAction
from bauh.api.abstract.handler import TaskManager from bauh.api.abstract.handler import TaskManager
from bauh.api import user from bauh.api import user
from bauh.view.qt.components import new_spacer, QCustomToolbar from bauh.view.qt.components import new_spacer, QCustomToolbar
from bauh.view.qt.qt_utils import centralize from bauh.view.qt.qt_utils import centralize, get_current_screen_geometry
from bauh.view.qt.root import RootDialog from bauh.view.qt.root import RootDialog
from bauh.view.qt.thread import AnimateProgress from bauh.view.qt.thread import AnimateProgress
from bauh.view.util.translation import I18n from bauh.view.util.translation import I18n
@@ -145,7 +145,7 @@ class PreparePanel(QWidget, TaskManager):
signal_status = pyqtSignal(int) signal_status = pyqtSignal(int)
signal_password_response = pyqtSignal(bool, str) signal_password_response = pyqtSignal(bool, str)
def __init__(self, context: ApplicationContext, manager: SoftwareManager, screen_size: QSize, def __init__(self, context: ApplicationContext, manager: SoftwareManager,
i18n: I18n, manage_window: QWidget, app_config: dict, force_suggestions: bool = False): i18n: I18n, manage_window: QWidget, app_config: dict, force_suggestions: bool = False):
super(PreparePanel, self).__init__(flags=Qt.CustomizeWindowHint | Qt.WindowTitleHint) super(PreparePanel, self).__init__(flags=Qt.CustomizeWindowHint | Qt.WindowTitleHint)
self.i18n = i18n self.i18n = i18n
@@ -153,9 +153,7 @@ class PreparePanel(QWidget, TaskManager):
self.app_config = app_config self.app_config = app_config
self.manage_window = manage_window self.manage_window = manage_window
self.setWindowTitle('{} ({})'.format(__app_name__, self.i18n['prepare_panel.title.start'].lower())) self.setWindowTitle('{} ({})'.format(__app_name__, self.i18n['prepare_panel.title.start'].lower()))
self.setMinimumWidth(int(screen_size.width() * 0.5))
self.setMinimumHeight(int(screen_size.height() * 0.35))
self.setMaximumHeight(int(screen_size.height() * 0.95))
self.setLayout(QVBoxLayout()) self.setLayout(QVBoxLayout())
self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred) self.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
self.manager = manager self.manager = manager
@@ -292,6 +290,10 @@ class PreparePanel(QWidget, TaskManager):
super(PreparePanel, self).show() super(PreparePanel, self).show()
self.prepare_thread.start() self.prepare_thread.start()
centralize(self) centralize(self)
screen_size = get_current_screen_geometry()
self.setMinimumWidth(int(screen_size.width() * 0.5))
self.setMinimumHeight(int(screen_size.height() * 0.35))
self.setMaximumHeight(int(screen_size.height() * 0.95))
def start(self, tasks: int): def start(self, tasks: int):
self.started_at = time.time() self.started_at = time.time()

View File

@@ -1,16 +1,18 @@
from PyQt5.QtCore import Qt from typing import Optional, Union
from PyQt5.QtCore import Qt, QRect, QPoint
from PyQt5.QtGui import QIcon, QPixmap from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtWidgets import QWidget, QApplication from PyQt5.QtWidgets import QWidget, QApplication, QDesktopWidget
from bauh.view.util import resource from bauh.view.util import resource
desktop: Optional[QDesktopWidget] = None
def centralize(widget: QWidget): def centralize(widget: QWidget):
geo = widget.frameGeometry() screen_geometry = get_current_screen_geometry()
screen = QApplication.desktop().screenNumber(QApplication.desktop().cursor().pos()) widget.frameGeometry().moveCenter(screen_geometry.center())
center_point = QApplication.desktop().screenGeometry(screen).center() widget.move(widget.frameGeometry().topLeft())
geo.moveCenter(center_point)
widget.move(geo.topLeft())
def load_icon(path: str, width: int, height: int = None) -> QIcon: def load_icon(path: str, width: int, height: int = None) -> QIcon:
@@ -27,3 +29,13 @@ def measure_based_on_width(percent: float) -> int:
def measure_based_on_height(percent: float) -> int: def measure_based_on_height(percent: float) -> int:
return round(percent * QApplication.primaryScreen().size().height()) return round(percent * QApplication.primaryScreen().size().height())
def get_current_screen_geometry(source_widget: Optional[Union[QWidget, QPoint]] = None) -> QRect:
global desktop
if not desktop:
desktop = QDesktopWidget()
current_screen_idx = desktop.screenNumber(source_widget if source_widget else desktop.cursor().pos())
return desktop.screen(current_screen_idx).geometry()

View File

@@ -29,6 +29,7 @@ from bauh.view.core import timeshift
from bauh.view.core.config import CoreConfigManager, BACKUP_REMOVE_METHODS, BACKUP_DEFAULT_REMOVE_METHOD from bauh.view.core.config import CoreConfigManager, BACKUP_REMOVE_METHODS, BACKUP_DEFAULT_REMOVE_METHOD
from bauh.view.qt import commons from bauh.view.qt import commons
from bauh.view.qt.commons import sort_packages from bauh.view.qt.commons import sort_packages
from bauh.view.qt.qt_utils import get_current_screen_geometry
from bauh.view.qt.view_model import PackageView, PackageViewStatus from bauh.view.qt.view_model import PackageView, PackageViewStatus
from bauh.view.util.translation import I18n from bauh.view.util.translation import I18n
@@ -231,12 +232,12 @@ class UpgradeSelected(AsyncAction):
SUMMARY_FILE = UPGRADE_LOGS_DIR + '/{}_summary.txt' SUMMARY_FILE = UPGRADE_LOGS_DIR + '/{}_summary.txt'
def __init__(self, manager: SoftwareManager, internet_checker: InternetChecker, i18n: I18n, def __init__(self, manager: SoftwareManager, internet_checker: InternetChecker, i18n: I18n,
screen_width: int, pkgs: List[PackageView] = None): parent_widget: QWidget, pkgs: List[PackageView] = None):
super(UpgradeSelected, self).__init__(i18n=i18n) super(UpgradeSelected, self).__init__(i18n=i18n)
self.pkgs = pkgs self.pkgs = pkgs
self.manager = manager self.manager = manager
self.internet_checker = internet_checker self.internet_checker = internet_checker
self.screen_width = screen_width self._parent_widget = parent_widget
def _req_as_option(self, req: UpgradeRequirement, tooltip: bool = True, custom_tooltip: str = None, required_size: bool = True, display_sizes: bool = True, def _req_as_option(self, req: UpgradeRequirement, tooltip: bool = True, custom_tooltip: str = None, required_size: bool = True, display_sizes: bool = True,
positive_size_symbol: bool = False) -> InputOption: positive_size_symbol: bool = False) -> InputOption:
@@ -495,9 +496,10 @@ class UpgradeSelected(AsyncAction):
comps.insert(0, TextComponent(f'{disc_size_text} ({download_size_text})', size=14)) comps.insert(0, TextComponent(f'{disc_size_text} ({download_size_text})', size=14))
comps.insert(1, TextComponent('')) comps.insert(1, TextComponent(''))
screen_width = get_current_screen_geometry(self._parent_widget).width()
if not self.request_confirmation(title=self.i18n['action.update.summary'].capitalize(), body='', components=comps, if not self.request_confirmation(title=self.i18n['action.update.summary'].capitalize(), body='', components=comps,
confirmation_label=self.i18n['proceed'].capitalize(), deny_label=self.i18n['cancel'].capitalize(), confirmation_label=self.i18n['proceed'].capitalize(), deny_label=self.i18n['cancel'].capitalize(),
confirmation_button=can_upgrade, min_width=int(0.45 * self.screen_width)): confirmation_button=can_upgrade, min_width=int(0.45 * screen_width)):
self.notify_finished({'success': success, 'updated': updated, 'types': updated_types, 'id': None}) self.notify_finished({'success': success, 'updated': updated, 'types': updated_types, 'id': None})
self.pkgs = None self.pkgs = None
return return

View File

@@ -2,12 +2,11 @@ import logging
import operator import operator
import os.path import os.path
import time import time
import traceback
from pathlib import Path from pathlib import Path
from typing import List, Type, Set, Tuple, Optional from typing import List, Type, Set, Tuple, Optional
from PyQt5.QtCore import QEvent, Qt, pyqtSignal, QSize from PyQt5.QtCore import QEvent, Qt, pyqtSignal, QRect
from PyQt5.QtGui import QIcon, QWindowStateChangeEvent, QCursor from PyQt5.QtGui import QIcon, QWindowStateChangeEvent, QCursor, QMoveEvent
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QHeaderView, QToolBar, \ from PyQt5.QtWidgets import QWidget, QVBoxLayout, QCheckBox, QHeaderView, QToolBar, \
QLabel, QPlainTextEdit, QProgressBar, QPushButton, QComboBox, QApplication, QListView, QSizePolicy, \ QLabel, QPlainTextEdit, QProgressBar, QPushButton, QComboBox, QApplication, QListView, QSizePolicy, \
QMenu, QHBoxLayout QMenu, QHBoxLayout
@@ -34,6 +33,7 @@ from bauh.view.qt.components import new_spacer, IconButton, QtComponentsManager,
from bauh.view.qt.dialog import ConfirmationDialog from bauh.view.qt.dialog import ConfirmationDialog
from bauh.view.qt.history import HistoryDialog from bauh.view.qt.history import HistoryDialog
from bauh.view.qt.info import InfoDialog from bauh.view.qt.info import InfoDialog
from bauh.view.qt.qt_utils import get_current_screen_geometry
from bauh.view.qt.root import RootDialog from bauh.view.qt.root import RootDialog
from bauh.view.qt.screenshots import ScreenshotsDialog from bauh.view.qt.screenshots import ScreenshotsDialog
from bauh.view.qt.settings import SettingsWindow from bauh.view.qt.settings import SettingsWindow
@@ -96,7 +96,7 @@ class ManageWindow(QWidget):
signal_table_update = pyqtSignal() signal_table_update = pyqtSignal()
signal_stop_notifying = pyqtSignal() signal_stop_notifying = pyqtSignal()
def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager, screen_size: QSize, config: dict, def __init__(self, i18n: I18n, icon_cache: MemoryCache, manager: SoftwareManager, config: dict,
context: ApplicationContext, http_client: HttpClient, logger: logging.Logger, icon: QIcon, context: ApplicationContext, http_client: HttpClient, logger: logging.Logger, icon: QIcon,
force_suggestions: bool = False): force_suggestions: bool = False):
super(ManageWindow, self).__init__() super(ManageWindow, self).__init__()
@@ -112,7 +112,6 @@ class ManageWindow(QWidget):
self.pkgs_installed = [] # cached installed packages self.pkgs_installed = [] # cached installed packages
self.display_limit = config['ui']['table']['max_displayed'] self.display_limit = config['ui']['table']['max_displayed']
self.icon_cache = icon_cache self.icon_cache = icon_cache
self.screen_size = screen_size
self.config = config self.config = config
self.context = context self.context = context
self.http_client = http_client self.http_client = http_client
@@ -279,8 +278,7 @@ class ManageWindow(QWidget):
self.table_container.layout().setContentsMargins(0, 0, 0, 0) self.table_container.layout().setContentsMargins(0, 0, 0, 0)
self.table_apps = PackagesTable(self, self.icon_cache, self.table_apps = PackagesTable(self, self.icon_cache,
download_icons=bool(self.config['download']['icons']), download_icons=bool(self.config['download']['icons']))
screen_width=int(screen_size.width()))
self.table_apps.change_headers_policy() self.table_apps.change_headers_policy()
self.table_container.layout().addWidget(self.table_apps) self.table_container.layout().addWidget(self.table_apps)
@@ -333,7 +331,7 @@ class ManageWindow(QWidget):
self.thread_update = self._bind_async_action(UpgradeSelected(manager=self.manager, i18n=self.i18n, self.thread_update = self._bind_async_action(UpgradeSelected(manager=self.manager, i18n=self.i18n,
internet_checker=context.internet_checker, internet_checker=context.internet_checker,
screen_width=screen_size.width()), parent_widget=self),
finished_call=self._finish_upgrade_selected) finished_call=self._finish_upgrade_selected)
self.thread_refresh = self._bind_async_action(RefreshApps(i18n, self.manager), finished_call=self._finish_refresh_packages, only_finished=True) self.thread_refresh = self._bind_async_action(RefreshApps(i18n, self.manager), finished_call=self._finish_refresh_packages, only_finished=True)
self.thread_uninstall = self._bind_async_action(UninstallPackage(self.manager, self.icon_cache, self.i18n), finished_call=self._finish_uninstall) self.thread_uninstall = self._bind_async_action(UninstallPackage(self.manager, self.icon_cache, self.i18n), finished_call=self._finish_uninstall)
@@ -433,8 +431,6 @@ class ManageWindow(QWidget):
self.container_progress.add_space() self.container_progress.add_space()
self.layout.addWidget(self.container_progress) self.layout.addWidget(self.container_progress)
qt_utils.centralize(self)
self.filter_only_apps = True self.filter_only_apps = True
self.type_filter = self.any_type_filter self.type_filter = self.any_type_filter
self.category_filter = self.any_category_filter self.category_filter = self.any_category_filter
@@ -457,11 +453,8 @@ class ManageWindow(QWidget):
self.thread_load_installed = NotifyInstalledLoaded() self.thread_load_installed = NotifyInstalledLoaded()
self.thread_load_installed.signal_loaded.connect(self._finish_loading_installed) self.thread_load_installed.signal_loaded.connect(self._finish_loading_installed)
self.setMinimumHeight(int(screen_size.height() * 0.5))
self.setMaximumHeight(int(screen_size.height()))
self.setMinimumWidth(int(screen_size.width() * 0.5))
self.setMaximumWidth(int(screen_size.width() - screen_size.width() * 0.015))
self._register_groups() self._register_groups()
self._screen_geometry: Optional[QRect] = None
def _register_groups(self): def _register_groups(self):
common_filters = (CHECK_APPS, CHECK_UPDATES, COMBO_CATEGORIES, COMBO_TYPES, INP_NAME) common_filters = (CHECK_APPS, CHECK_UPDATES, COMBO_CATEGORIES, COMBO_TYPES, INP_NAME)
@@ -597,6 +590,8 @@ class ManageWindow(QWidget):
self.thread_warnings.start() self.thread_warnings.start()
qt_utils.centralize(self) qt_utils.centralize(self)
self._screen_geometry = get_current_screen_geometry()
self._update_size_limits()
def verify_warnings(self): def verify_warnings(self):
self.thread_warnings.start() self.thread_warnings.start()
@@ -671,7 +666,8 @@ class ManageWindow(QWidget):
if self.table_apps.isEnabled() and self.pkgs is not None and 0 <= idx < len(self.pkgs): if self.table_apps.isEnabled() and self.pkgs is not None and 0 <= idx < len(self.pkgs):
pkg = self.pkgs[idx] pkg = self.pkgs[idx]
pkg.status = PackageViewStatus.READY pkg.status = PackageViewStatus.READY
self.table_apps.update_package(pkg) screen_width = get_current_screen_geometry(self).width()
self.table_apps.update_package(pkg, screen_width=screen_width)
def _reload_categories(self): def _reload_categories(self):
categories = set() categories = set()
@@ -687,11 +683,33 @@ class ManageWindow(QWidget):
if categories: if categories:
self._update_categories(categories, keep_selected=True) self._update_categories(categories, keep_selected=True)
def _update_size_limits(self):
self.setMinimumHeight(int(self._screen_geometry.height() * 0.5))
self.setMinimumWidth(int(self._screen_geometry.width() * 0.5))
self.setMaximumWidth(int(self._screen_geometry.width()))
def changeEvent(self, e: QEvent): def changeEvent(self, e: QEvent):
if isinstance(e, QWindowStateChangeEvent): if isinstance(e, QWindowStateChangeEvent):
self._maximized = self.isMaximized() self._maximized = self.isMaximized()
self.table_apps.change_headers_policy(maximized=self._maximized) self.table_apps.change_headers_policy(maximized=self._maximized)
if not self._maximized:
self._reorganize()
self.adjustSize()
def event(self, e: QEvent) -> bool:
res = super(ManageWindow, self).event(e)
if self.isVisible() and e.type() == 216: # drop event
current_geometry = get_current_screen_geometry()
if current_geometry != self._screen_geometry: # only if the display device has changed
self._screen_geometry = current_geometry
self._update_size_limits()
self._reorganize()
self.adjustSize()
return res
def _handle_console(self, checked: bool): def _handle_console(self, checked: bool):
if checked: if checked:
self.textarea_details.show() self.textarea_details.show()
@@ -780,6 +798,7 @@ class ManageWindow(QWidget):
util.notify_user('{} ({}) {}'.format(src_pkg.model.name, src_pkg.model.get_type(), self.i18n['uninstalled'])) util.notify_user('{} ({}) {}'.format(src_pkg.model.name, src_pkg.model.get_type(), self.i18n['uninstalled']))
if res['removed']: if res['removed']:
screen_width = get_current_screen_geometry(self).width()
for list_idx, pkg_list in enumerate((self.pkgs_available, self.pkgs, self.pkgs_installed)): for list_idx, pkg_list in enumerate((self.pkgs_available, self.pkgs, self.pkgs_installed)):
if pkg_list: if pkg_list:
removed_idxs = [] removed_idxs = []
@@ -796,7 +815,9 @@ class ManageWindow(QWidget):
removed_idxs.append(pkgv_idx) removed_idxs.append(pkgv_idx)
if self.search_performed and list_idx == 1: # only for displayed if self.search_performed and list_idx == 1: # only for displayed
self.table_apps.update_package(pkgv, change_update_col=True) self.table_apps.update_package(pkgv,
screen_width=screen_width,
change_update_col=True)
break # as the model has been found, stops the loop break # as the model has been found, stops the loop
@@ -1286,8 +1307,7 @@ class ManageWindow(QWidget):
if pkg_info: if pkg_info:
if len(pkg_info) > 1: if len(pkg_info) > 1:
dialog_info = InfoDialog(pkg_info=pkg_info, icon_cache=self.icon_cache, dialog_info = InfoDialog(pkg_info=pkg_info, icon_cache=self.icon_cache, i18n=self.i18n)
i18n=self.i18n, screen_size=self.screen_size)
dialog_info.exec_() dialog_info.exec_()
else: else:
dialog.show_message(title=self.i18n['warning'].capitalize(), dialog.show_message(title=self.i18n['warning'].capitalize(),
@@ -1462,10 +1482,11 @@ class ManageWindow(QWidget):
self.pkgs_available.insert(new_idx, data[2]) self.pkgs_available.insert(new_idx, data[2])
# updating the respective table rows: # updating the respective table rows:
screen_width = get_current_screen_geometry(self).width()
for displayed in self.pkgs: for displayed in self.pkgs:
for model in models_updated: for model in models_updated:
if displayed.model == model: if displayed.model == model:
self.table_apps.update_package(displayed, change_update_col=True) self.table_apps.update_package(displayed, screen_width=screen_width, change_update_col=True)
self.update_bt_upgrade() self.update_bt_upgrade()
@@ -1565,7 +1586,8 @@ class ManageWindow(QWidget):
self.settings_window.handle_display() self.settings_window.handle_display()
else: else:
self.settings_window = SettingsWindow(manager=self.manager, i18n=self.i18n, window=self) self.settings_window = SettingsWindow(manager=self.manager, i18n=self.i18n, window=self)
self.settings_window.setMinimumWidth(int(self.screen_size.width() / 4)) screen_width = get_current_screen_geometry(self).width()
self.settings_window.setMinimumWidth(int(screen_width / 4))
self.settings_window.resize(self.size()) self.settings_window.resize(self.size())
self.settings_window.adjustSize() self.settings_window.adjustSize()
qt_utils.centralize(self.settings_window) qt_utils.centralize(self.settings_window)
@@ -1628,10 +1650,11 @@ class ManageWindow(QWidget):
self._update_table_indexes() self._update_table_indexes()
self.update_bt_upgrade() self.update_bt_upgrade()
else: else:
screen_width = get_current_screen_geometry(self).width()
for pkg in self.pkgs: for pkg in self.pkgs:
if pkg == res['pkg']: if pkg == res['pkg']:
pkg.update_model(res['pkg'].model) pkg.update_model(res['pkg'].model)
self.table_apps.update_package(pkg, change_update_col=not any([self.search_performed, self.suggestions_requested])) self.table_apps.update_package(pkg, screen_width=screen_width, change_update_col=not any([self.search_performed, self.suggestions_requested]))
self.update_bt_upgrade() self.update_bt_upgrade()
break break

View File

@@ -0,0 +1,22 @@
FROM ubuntu:20.04
ARG bauh_commit
ENV BAUH_VERSION=$bauh_commit
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
apt-get upgrade && \
apt-get install python3-pip python3-setuptools python3-wheel wget fuse binutils coreutils desktop-file-utils fakeroot patchelf squashfs-tools strace zsync libgdk-pixbuf2.0-dev gtk-update-icon-cache -y && \
mkdir /build && cd /build && \
wget https://github.com/AppImageCrafters/appimage-builder/releases/download/v0.9.2/appimage-builder-0.9.2-35e3eab-x86_64.AppImage -O appimage-builder && \
wget https://github.com/AppImage/AppImageKit/releases/download/13/appimagetool-x86_64.AppImage -O appimage-tool && \
chmod +x appimage-tool && \
chmod +x appimage-builder && \
mv /build/appimage-builder /usr/local/bin/appimage-builder && \
mv /build/appimage-tool /usr/local/bin/appimage-tool
WORKDIR /build
COPY AppImageBuilder.yml /build
CMD [ "appimage-builder", "--skip-tests"]

3
linux_dist/appimage/build.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/bash
docker build -t bauh-appimage --build-arg bauh_commit=$BAUH_COMMIT .
docker run --cap-add=SYS_ADMIN --device /dev/fuse --mount type=bind,source="$(pwd)",target=/build bauh-appimage

View File

@@ -1,4 +1,3 @@
import time
from unittest import TestCase from unittest import TestCase
from bauh.commons.util import size_to_byte, sanitize_command_input from bauh.commons.util import size_to_byte, sanitize_command_input

View File

@@ -8,7 +8,7 @@ class GetHumanSizeStrTest(TestCase):
def setUp(self): def setUp(self):
try: try:
locale.setlocale(locale.LC_NUMERIC, None) locale.setlocale(locale.LC_NUMERIC, "C")
except: except:
print("Error: could not set locale.LC_NUMERIC to None") print("Error: could not set locale.LC_NUMERIC to None")

View File

@@ -79,6 +79,45 @@ class TestUtil(TestCase):
self.assertEqual(expected, res) self.assertEqual(expected, res)
def test_replace_desktop_entry_exec_command__exec_as_the_first_field(self):
desktop_entry = """
Exec=myapp %f
Name=MyApp
Icon=MyApp
"""
res = replace_desktop_entry_exec_command(desktop_entry=desktop_entry,
appname='myapp',
file_path='/path/to/myapp.appimage')
expected = """
Exec="/path/to/myapp.appimage" %f
Name=MyApp
Icon=MyApp
"""
self.assertEqual(expected, res)
def test_replace_desktop_entry_exec_command__try_exec_as_the_first_field(self):
desktop_entry = """
TryExec=MyApp
Exec=myapp %f
Name=MyApp
Icon=MyApp
"""
res = replace_desktop_entry_exec_command(desktop_entry=desktop_entry,
appname='myapp',
file_path='/path/to/myapp.appimage')
expected = """
Exec="/path/to/myapp.appimage" %f
Name=MyApp
Icon=MyApp
"""
self.assertEqual(expected, res)
def test_replace_desktop_entry_exec_command__only_one_exec_field_with_spaces_and_params(self): def test_replace_desktop_entry_exec_command__only_one_exec_field_with_spaces_and_params(self):
desktop_entry = """ desktop_entry = """
Name=MyApp Name=MyApp
@@ -136,7 +175,7 @@ class TestUtil(TestCase):
self.assertEqual(expected, res) self.assertEqual(expected, res)
def test_replace_desktop_entry_exec_command__only_one_tryexec_field_with_spaces_and_params(self): def test_replace_desktop_entry_exec_command__must_remove_try_exec_field(self):
desktop_entry = """ desktop_entry = """
Name=MyApp Name=MyApp
Icon=MyApp Icon=MyApp
@@ -150,12 +189,11 @@ class TestUtil(TestCase):
expected = """ expected = """
Name=MyApp Name=MyApp
Icon=MyApp Icon=MyApp
TryExec ="/path/to/myapp.appimage" %f --a
""" """
self.assertEqual(expected, res) self.assertEqual(expected, res)
def test_replace_desktop_entry_exec_command__exec_and_tryexec_fields(self): def test_replace_desktop_entry_exec_command__must_replace_exec_and_remove_tryexec_fields(self):
desktop_entry = """ desktop_entry = """
Name=MyApp Name=MyApp
Icon=MyApp Icon=MyApp
@@ -171,14 +209,13 @@ class TestUtil(TestCase):
expected = """ expected = """
Name=MyApp Name=MyApp
Icon=MyApp Icon=MyApp
TryExec ="/path/to/myapp.appimage" %f
Exec="/path/to/myapp.appimage" --a Exec="/path/to/myapp.appimage" --a
Terminal=false Terminal=false
""" """
self.assertEqual(expected, res) self.assertEqual(expected, res)
def test_replace_desktop_entry_exec_command__exec_and_tryexec_fields_with_envvars_and_params(self): def test_replace_desktop_entry_exec_command__exec_field_with_envvars_and_params(self):
desktop_entry = """ desktop_entry = """
Name=MyApp Name=MyApp
Icon=MyApp Icon=MyApp
@@ -194,7 +231,6 @@ class TestUtil(TestCase):
expected = """ expected = """
Name=MyApp Name=MyApp
Icon=MyApp Icon=MyApp
TryExec=__MY_VAR=1 "/path/to/myapp.appimage" %f
Exec=NEW_VAR=abc "/path/to/myapp.appimage" --a Exec=NEW_VAR=abc "/path/to/myapp.appimage" --a
Terminal=false Terminal=false
""" """
@@ -228,7 +264,6 @@ Name=RPCS3
GenericName=PlayStation 3 Emulator GenericName=PlayStation 3 Emulator
Comment=An open-source PlayStation 3 emulator/debugger written in C++. Comment=An open-source PlayStation 3 emulator/debugger written in C++.
Icon=rpcs3 Icon=rpcs3
TryExec="/path/to/rpcs3.appimage"
Exec="/path/to/rpcs3.appimage" %f Exec="/path/to/rpcs3.appimage" %f
Terminal=false Terminal=false
Categories=Game;Emulator; Categories=Game;Emulator;

View File

@@ -98,4 +98,4 @@ Include = /etc/pacman.d/mirrorlist
#Server = file:///home/custompkgs #Server = file:///home/custompkgs
ignorepkg=google-chrome ignorepkg=google-chrome
# ignorepkg=abc # ignorepkg=abc
ignorepkg = firefox IgnorePkg = firefox

View File

@@ -166,3 +166,33 @@ Description: GNU C compiler
] ]
self.assertEqual([p.__dict__ for p in expected], [p.__dict__ for p in returned]) self.assertEqual([p.__dict__ for p in expected], [p.__dict__ for p in returned])
def test_map_transaction_output__it_should_map_i386_packages(self):
output = "\nThe following NEW packages will be installed:\n" \
" gcc-12-base:i386{a} [12.1.0-2distro~22.04] <+272 kB> glib-networking:i386{a} [2.72.0-1] <+242 kB>\n" \
"\nThe following packages will be REMOVED:\n" \
" celluloid{a} [0.21-linux+distro] <-1066 kB> libpcre3:i386{a} [2:8.39-13distro0.22.04.1] <-714 kB>"
transaction = self.aptitude.map_transaction_output(output)
to_install = {
DebianPackage(name="gcc-12-base:i386",
version="12.1.0-2distro~22.04",
latest_version="12.1.0-2distro~22.04",
transaction_size=272000.0
),
DebianPackage(name="glib-networking:i386", version="2.72.0-1",
latest_version="2.72.0-1", transaction_size=242000.0)
}
self.assertEqual(to_install, {*transaction.to_install})
to_remove = {
DebianPackage(name="celluloid",
version="0.21-linux+distro",
latest_version="0.21-linux+distro",
transaction_size=-1066000.0
),
DebianPackage(name="libpcre3:i386", version="2:8.39-13distro0.22.04.1",
latest_version="2:8.39-13distro0.22.04.1", transaction_size=-714000.0)
}
self.assertEqual(to_remove, {*transaction.to_remove})