├── .flake8 ├── .gitignore ├── .pre-commit-config.yaml ├── .pylintrc ├── .tx └── config ├── .vscode └── settings.json ├── COPYING ├── ChangeLog ├── Makefile ├── README.md ├── data ├── meson.build └── ui │ ├── errordialog.ui │ ├── preferences.ui │ ├── progresssplash.ui │ ├── shortcuts.ui │ ├── transactionresult.ui │ └── yumex.ui ├── docs ├── Makefile ├── conf.py ├── faq.rst ├── img │ ├── arch-menu.png │ ├── button-apply.png │ ├── button-filters.png │ ├── button-main-menu.png │ ├── button-search-options.png │ ├── button-search-options.svg │ ├── error-running.png │ ├── filter-menu.png │ ├── filters.png │ ├── groups.png │ ├── history.png │ ├── main-menu.png │ ├── options.png │ ├── package-info.png │ ├── queue.png │ ├── search-bar.png │ ├── search-options.png │ ├── search.png │ ├── top-bar.png │ ├── updates-selection.png │ └── updates.png ├── index.rst └── main.rst ├── gfx ├── meson.build ├── progress.gif ├── screenshoots │ ├── yumex-screenshoot-01.png │ ├── yumex-screenshoot-02.png │ ├── yumex-screenshoot-03.png │ └── yumex-screenshoot-04.png ├── yumex-dnf.png └── yumex-dnf.svg ├── meson.build ├── misc ├── meson.build ├── scss │ ├── Dracula.scss │ ├── One-Dark.scss │ ├── System-Dark.scss │ ├── System-Light.scss │ ├── _base.scss │ ├── _colors.scss │ ├── _functions.scss │ ├── _palette.scss │ └── _system_base.scss ├── themes │ ├── Dracula.theme │ ├── One-Dark.theme │ ├── System-Dark.theme │ └── System-Light.theme ├── yumex-dnf-local.desktop.in ├── yumex-dnf-updater.desktop ├── yumex-dnf.appdata.xml.in └── yumex-dnf.desktop.in ├── po ├── LINGUAS ├── POTFILES.in ├── ar.po ├── bg.po ├── bn_IN.po ├── ca.po ├── cmn.po ├── cs.po ├── da.po ├── de.po ├── el.po ├── en_BR.po ├── es.po ├── es_ES.po ├── fa.po ├── fr.po ├── gu.po ├── he_IL.po ├── hu.po ├── id.po ├── it.po ├── ja.po ├── ko.po ├── ky.po ├── meson.build ├── nl.po ├── pl.po ├── pt.po ├── pt_BR.po ├── ru.po ├── ru_RU.po ├── sk.po ├── sr.po ├── sr@latin.po ├── sv.po ├── tr_TR.po ├── uk.po ├── yumex-dnf.pot ├── zh_CN.po └── zh_TW.po ├── src ├── main.py ├── meson.build ├── update.py └── yumex │ ├── __init__.py │ ├── backend │ ├── __init__.py │ └── dnf.py │ ├── base │ └── __init__.py │ ├── common │ ├── __init__.py │ ├── config.py │ └── const.py │ ├── gui │ ├── __init__.py │ ├── dialogs │ │ ├── __init__.py │ │ ├── aboutdialog.py │ │ ├── errordialog.py │ │ ├── preferences.py │ │ ├── progresssplash.py │ │ └── transactionresult.py │ ├── views │ │ ├── __init__.py │ │ ├── groupview.py │ │ ├── historypackageview.py │ │ ├── historyview.py │ │ ├── packagequeue.py │ │ ├── packageview.py │ │ ├── queueview.py │ │ ├── repoview.py │ │ └── selectionview.py │ ├── widgets │ │ ├── __init__.py │ │ ├── content.py │ │ ├── filters.py │ │ ├── mainnenu.py │ │ ├── packageinfo.py │ │ ├── progress.py │ │ └── searchbar.py │ └── window │ │ ├── __init__.py │ │ └── basewindow.py │ └── updater │ └── __init__.py ├── tools ├── git2cl.py └── update-translations.sh └── yumex-dnf.spec /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 88 3 | extend-ignore = E203 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | /.pydevproject 3 | /.settings/org.eclipse.core.resources.prefs 4 | __pycache__/ 5 | *.tar.gz 6 | 7 | /yumex-nextgen.nja 8 | /yumex-dnf.nja 9 | /src/yumex.ui~ 10 | /po/*.mo 11 | *~ 12 | /docs/_* 13 | /build/ 14 | .idea/ 15 | 16 | src/#yumex.ui# 17 | tools/test.py 18 | .vscode/ 19 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | default_stages: [commit, push] 2 | default_language_version: 3 | python: python3.10 4 | repos: 5 | - repo: https://github.com/psf/black 6 | rev: 21.11b1 7 | hooks: 8 | - id: black 9 | args: [ 10 | --line-length=88, 11 | ] 12 | exclude: ^(tools/|docs/) 13 | types: ['python'] 14 | - repo: https://github.com/PyCQA/flake8 15 | rev: 4.0.1 16 | hooks: 17 | - id: flake8 18 | args: [ 19 | --max-line-length=88, 20 | ] 21 | exclude: ^(tools/|docs/) 22 | types: ['python'] -------------------------------------------------------------------------------- /.tx/config: -------------------------------------------------------------------------------- 1 | [main] 2 | host = https://www.transifex.com 3 | 4 | [yumex.yumex-dnf] 5 | file_filter = po/.po 6 | source_file = po/yumex-dnf.pot 7 | source_lang = en 8 | type = PO 9 | 10 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.formatting.provider": "black", 3 | "python.linting.pylintEnabled": false, 4 | "python.linting.enabled": true, 5 | "python.linting.flake8Enabled": true, 6 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | APPNAME = yumex-dnf 2 | DATADIR = /usr/share 3 | PYTHON = python3 4 | SUBDIRS = misc po 5 | VERSION=$(shell awk '/Version:/ { print $$2 }' ${APPNAME}.spec) 6 | GITDATE=git$(shell date +%Y%m%d) 7 | VER_REGEX=\(^Version:\s*[0-9]*\.[0-9]*\.\)\(.*\) 8 | BUMPED_MINOR=${shell VN=`cat ${APPNAME}.spec | grep Version| sed 's/${VER_REGEX}/\2/'`; echo $$(($$VN + 1))} 9 | NEW_VER=${shell cat ${APPNAME}.spec | grep Version| sed 's/\(^Version:\s*\)\([0-9]*\.[0-9]*\.\)\(.*\)/\2${BUMPED_MINOR}/'} 10 | NEW_REL=0.1.${GITDATE} 11 | DIST=${shell rpm --eval "%{dist}"} 12 | GIT_MASTER=develop 13 | CURDIR = ${shell pwd} 14 | BUILDDIR= $(CURDIR)/build 15 | 16 | all: 17 | @echo "Nothing to do, use a specific target" 18 | 19 | clean: 20 | -rm -f *.tar.gz 21 | -rm -rf build 22 | -rm -rf .build 23 | 24 | sass: 25 | pysassc -t expanded misc/scss/Dracula.scss misc/themes/Dracula.theme 26 | pysassc -t expanded misc/scss/One-Dark.scss misc/themes/One-Dark.theme 27 | pysassc -t expanded misc/scss/System-Dark.scss misc/themes/System-Dark.theme 28 | pysassc -t expanded misc/scss/System-Light.scss misc/themes/System-Light.theme 29 | 30 | 31 | get-builddeps: 32 | @sudo dnf build-dep yumex-dnf.spec 33 | 34 | archive: 35 | @rm -rf ${APPNAME}-${VERSION}.tar.gz 36 | @git archive --format=tar --prefix=$(APPNAME)-$(VERSION)/ HEAD | gzip -9v >${APPNAME}-$(VERSION).tar.gz 37 | @mkdir -p ${BUILDDIR}/SOURCES 38 | @cp ${APPNAME}-$(VERSION).tar.gz ${BUILDDIR}/SOURCES 39 | @rm -rf ${APPNAME}-${VERSION}.tar.gz 40 | @echo "The archive is in ${BUILDDIR}/SOURCES/${APPNAME}-$(VERSION).tar.gz" 41 | 42 | changelog: 43 | $(PYTHON) tools/git2cl.py 44 | 45 | upload: 46 | @scp $(BUILDDIR)/SOURCES/${APPNAME}-${VERSION}.tar.gz yum-extender.org:public_html/dnl/yumex/source/. 47 | 48 | release-branch: 49 | @git branch -m ${GIT_MASTER} release-${VERSION} 50 | 51 | release-publish: 52 | @git checkout release-${VERSION} 53 | @git commit -a -m "bumped version to $(VERSION)" 54 | @$(MAKE) changelog 55 | @git commit -a -m "updated ChangeLog" 56 | @git checkout release-devel 57 | @git merge --no-ff release-${VERSION} -m "merge ${APPNAME}-${VERSION} release" 58 | @git tag -f -m "Added ${APPNAME}-${VERSION} release tag" ${APPNAME}-${VERSION} 59 | @git push --tags origin 60 | @git push origin 61 | @$(MAKE) archive 62 | @$(MAKE) rpm 63 | 64 | release-cleanup: 65 | @git checkout develop 66 | @git merge --no-ff release-${VERSION} -m "merge ${APPNAME}-${VERSION} release" 67 | @git push origin 68 | @git branch -D release-${VERSION} 69 | 70 | test-cleanup: 71 | @rm -rf ${APPNAME}-${VERSION}.test.tar.gz 72 | @echo "Cleanup the git release-test local branch" 73 | @git checkout -f 74 | @git checkout ${GIT_MASTER} 75 | @git branch -D release-test 76 | 77 | show-vars: 78 | @echo ${GITDATE} 79 | @echo ${BUMPED_MINOR} 80 | @echo ${NEW_VER}-${NEW_REL} 81 | 82 | test-release: 83 | @git checkout -b release-test 84 | # +1 Minor version and add 0.1-gitYYYYMMDD release 85 | @cat ${APPNAME}.spec | sed -e 's/${VER_REGEX}/\1${BUMPED_MINOR}/' -e 's/\(^Release:\s*\)\([0-9]*\)\(.*\)./\10.1.${GITDATE}%{?dist}/' > ${APPNAME}-test.spec ; mv ${APPNAME}-test.spec ${APPNAME}.spec 86 | @git commit -a -m "bumped ${APPNAME} version ${NEW_VER}-${NEW_REL}" 87 | # Make archive 88 | @rm -rf ${APPNAME}-${NEW_VER}.tar.gz 89 | @git archive --format=tar --prefix=$(APPNAME)-$(NEW_VER)/ HEAD | gzip -9v >${APPNAME}-$(NEW_VER).tar.gz 90 | # Build RPMS 91 | @-rpmbuild --define '_topdir $(BUILDDIR)' -ta ${APPNAME}-${NEW_VER}.tar.gz 92 | @$(MAKE) test-cleanup 93 | 94 | rpm: 95 | @$(MAKE) archive 96 | @rpmbuild --define '_topdir $(BUILDDIR)' -ta ${BUILDDIR}/SOURCES/${APPNAME}-$(VERSION).tar.gz 97 | 98 | test-builds: 99 | @$(MAKE) test-release 100 | @-ssh timlau@fedorapeople.org rm public_html/files/yumex/* 101 | @scp ${APPNAME}-${NEW_VER}.tar.gz timlau@fedorapeople.org:public_html/files/yumex/${APPNAME}-${NEW_VER}-${GITDATE}.tar.gz 102 | @scp $(BUILDDIR)/RPMS/noarch/${APPNAME}-${NEW_VER}*.rpm timlau@fedorapeople.org:public_html/files/yumex/. 103 | @scp $(BUILDDIR)/SRPMS/${APPNAME}-${NEW_VER}*.rpm timlau@fedorapeople.org:public_html/files/yumex/. 104 | 105 | test-upd: 106 | @$(MAKE) test-release 107 | sudo dnf update $(BUILDDIR)/RPMS/noarch/${APPNAME}-${NEW_VER}-${NEW_REL}*.rpm 108 | 109 | test-inst: 110 | @$(MAKE) test-release 111 | sudo dnf install $(BUILDDIR)/RPMS/noarch/${APPNAME}-${NEW_VER}-${NEW_REL}*.rpm 112 | 113 | test-reinst: 114 | @$(MAKE) test-release 115 | sudo dnf reinstall $(BUILDDIR)/RPMS/noarch/${APPNAME}-${NEW_VER}-${NEW_REL}*.rpm 116 | 117 | test-copr: 118 | @$(MAKE) test-release 119 | copr-cli build yumex-dnf $(BUILDDIR)/SRPMS/${APPNAME}-${NEW_VER}-${NEW_REL}*.src.rpm 120 | 121 | 122 | transifex-setup: 123 | tx init 124 | tx set --auto-remote https://www.transifex.com/projects/p/yumex/ 125 | tx set --auto-local -r yumex.${APPNAME} 'po/.po' --source-lang en --source-file po/${APPNAME}.pot --execute 126 | 127 | 128 | transifex-update: 129 | tx pull -a -f 130 | tools/update-translations.sh 131 | tx push -s 132 | git commit -a -m "Updated translations from transifex" 133 | 134 | 135 | status-exit: 136 | /usr/bin/dbus-send --session --print-reply --dest=dk.yumex.StatusIcon / dk.yumex.StatusIcon.Exit 137 | 138 | status-checkupdates: 139 | /usr/bin/dbus-send --session --print-reply --dest=dk.yumex.StatusIcon / dk.yumex.StatusIcon.Start 140 | /usr/bin/dbus-send --session --print-reply --dest=dk.yumex.StatusIcon / dk.yumex.StatusIcon.CheckUpdates 141 | 142 | status-run: 143 | cd dbus && ./dbus_status.py -v -d 144 | 145 | # Run pylint checks 146 | check-pylint: 147 | @-find src -type f -name "*.py" | xargs pylint -E --rcfile=.pylintrc 148 | 149 | # Run flake8 checks 150 | check-flake8: 151 | @-flake8 src/ 152 | 153 | # format python code using black 154 | check-black: 155 | @black src/ 156 | 157 | # install python linters & formatters using pip 158 | check-inst-deps: 159 | @pip install pylint black flake8 160 | 161 | .PHONY: archive clean 162 | .PHONY: test-reinst test-inst mock-build rpm test-release test-cleanup show-vars release upload get-builddeps changelog 163 | .PHONY: test-copr sass check-pylint check-flake8 check-black check-inst-deps 164 | 165 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Yum Extender (yumex-dnf) 2 | 3 | This repository contains a complete rewrite of Yum Extender in python3, Gtk3 and using the dnf-daemon dbus API for 4 | packaging actions 5 | 6 | > **_Jan 2023:_** yumex-dnf is frozen, [yumex-ng](https://github.com/timlau/yumex-ng) is a rewrite from scrach 7 | 8 | 9 | Group/History support is read-only for now, as dnfdaemon support for history/groups is broken 10 | 11 | 12 | ## How to build & install test rpms 13 | ``` 14 | git clone https://github.com/timlau/yumex-dnf.git 15 | cd yumex-dnf 16 | make get-builddeps 17 | make test-inst 18 | ``` 19 | 20 | ## Requirements 21 | 22 | ``` 23 | dnf install python3 python3-gobject 24 | ``` 25 | 26 | [dnf-daemon](https://github.com/timlau/dnf-daemon) python3 bindings must also be installed. 27 | 28 | ``` 29 | dnf install python3-dnfdaemon 30 | ``` 31 | 32 | Or build the latest version from git 33 | 34 | ``` 35 | git clone https://github.com/timlau/dnf-daemon.git 36 | cd dnf-daemon 37 | make test-inst 38 | ``` 39 | 40 | 41 | ## Fedora Copr Repository 42 | yumex-dnf development packages is available in a [fedora Copr repository](https://copr.fedoraproject.org/coprs/timlau/yumex-dnf/) for f34 & Rawhide 43 | 44 | 45 | Use this to enable it. 46 | ``` 47 | sudo dnf copr enable timlau/yumex-dnf 48 | sudo dnf install yumex-dnf 49 | ``` 50 | 51 | ## Contributing 52 | * Please [report bugs](https://github.com/timlau/yumex-dnf/issues) if you find some. 53 | * In case you want to [propose changes](https://github.com/timlau/yumex-dnf/pulls), please do so on Github after [testing](https://github.com/timlau/yumex-dnf/wiki/Testing-yumex-for-developing) them. 54 | * If you want to contribute translations, please do so on [Transifex](https://www.transifex.com/timlau/yumex/). 55 | 56 |
57 |
58 |
59 |
60 | 61 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge)](https://github.com/psf/black)   62 | [![Code linter: flake8](https://img.shields.io/badge/linter-flake8-blue.svg?style=for-the-badge 63 | )](https://github.com/PyCQA/flake8)  64 | [![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?style=for-the-badge&logo=pre-commit&logoColor=white)](https://github.com/pre-commit/pre-commit) 65 | 66 | 67 | -------------------------------------------------------------------------------- /data/meson.build: -------------------------------------------------------------------------------- 1 | DATA_DIR = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name()) 2 | 3 | install_subdir( 4 | 'ui', 5 | install_dir: DATA_DIR 6 | ) -------------------------------------------------------------------------------- /data/ui/errordialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | yumex_error 8 | False 9 | 5 10 | Errors 11 | center-on-parent 12 | 600 13 | 260 14 | True 15 | dialog 16 | 17 | 18 | False 19 | vertical 20 | 2 21 | 22 | 23 | False 24 | end 25 | 26 | 27 | 28 | 29 | 30 | gtk-close 31 | True 32 | True 33 | True 34 | True 35 | 38 | 39 | 40 | False 41 | True 42 | 1 43 | 44 | 45 | 46 | 47 | False 48 | True 49 | end 50 | 0 51 | 52 | 53 | 54 | 55 | True 56 | False 57 | vertical 58 | 59 | 60 | True 61 | True 62 | in 63 | 64 | 65 | 350 66 | True 67 | True 68 | False 69 | 5 70 | 5 71 | 5 72 | 5 73 | False 74 | error_buffer 75 | True 76 | 79 | 80 | 81 | 82 | 83 | True 84 | True 85 | 0 86 | 87 | 88 | 89 | 90 | True 91 | True 92 | 1 93 | 94 | 95 | 96 | 97 | 98 | error_ok 99 | 100 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /data/ui/progresssplash.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | False 7 | 0.9019607843137255 8 | False 9 | True 10 | center-on-parent 11 | True 12 | splashscreen 13 | True 14 | False 15 | False 16 | False 17 | False 18 | 19 | 20 | True 21 | False 22 | 23 | 24 | True 25 | False 26 | 6 27 | 30 | 31 | 32 | -1 33 | 34 | 35 | 36 | 37 | work_top_overlay 38 | True 39 | False 40 | 0.9019607843137255 41 | end 42 | 20 43 | 20 44 | 20 45 | 20 46 | 20 47 | vertical 48 | 49 | 50 | True 51 | False 52 | center 53 | end 54 | Working ... 55 | 56 | 57 | 58 | 59 | 62 | 63 | 64 | False 65 | True 66 | 0 67 | 68 | 69 | 70 | 71 | True 72 | False 73 | center 74 | end 75 | Working ... 76 | 77 | 78 | 79 | 80 | 83 | 84 | 85 | False 86 | True 87 | 1 88 | 89 | 90 | 91 | 92 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /data/ui/shortcuts.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 1 6 | 1 7 | 8 | 9 | shortcuts 10 | 10 11 | 1 12 | 13 | 14 | Pages 15 | 16 | 17 | <alt>1 18 | Go to packages page 19 | 1 20 | 21 | 22 | 23 | 24 | <alt>2 25 | Go to group page 26 | 1 27 | 28 | 29 | 30 | 31 | <alt>3 32 | Go to history page 33 | 1 34 | 35 | 36 | 37 | 38 | <alt>4 39 | Go to queue page 40 | 1 41 | 42 | 43 | 44 | 45 | 46 | 47 | filters 48 | Filters 49 | 1 50 | 51 | 52 | <ctrl>1 53 | Show updates 54 | 1 55 | 56 | 57 | 58 | 59 | <ctrl>2 60 | Show installed 61 | 1 62 | 63 | 64 | 65 | 66 | <ctrl>3 67 | Show available 68 | 1 69 | 70 | 71 | 72 | 73 | <ctrl>4 74 | Show all 75 | 1 76 | 77 | 78 | 79 | 80 | 81 | 82 | info 83 | Info view 84 | 1 85 | 86 | 87 | <ctrl><alt>1 88 | package description 89 | 1 90 | 91 | 92 | 93 | 94 | <ctrl><alt>2 95 | package update info 96 | 1 97 | 98 | 99 | 100 | 101 | <ctrl><alt>3 102 | package files 103 | 1 104 | 105 | 106 | 107 | 108 | <ctrl><alt>4 109 | package requirement 110 | 1 111 | 112 | 113 | 114 | 115 | 116 | 117 | packageview 118 | Package View 119 | 1 120 | 121 | 122 | <ctrl>s 123 | Select all packages in view 124 | 1 125 | 126 | 127 | 128 | 129 | 130 | 131 | other 132 | Other 133 | 1 134 | 135 | 136 | <alt>a 137 | Apply Pending actions 138 | 1 139 | 140 | 141 | 142 | 143 | <alt>x 144 | Filter options 145 | 1 146 | 147 | 148 | 149 | 150 | <ctrl>f 151 | Toggle search bar 152 | 1 153 | 154 | 155 | 156 | 157 | <ctrl>q 158 | Quit 159 | 1 160 | 161 | 162 | 163 | 164 | <alt>Return 165 | Open Preferences 166 | 1 167 | 168 | 169 | 170 | 171 | F1 172 | Open Documentation 173 | 1 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | -------------------------------------------------------------------------------- /data/ui/transactionresult.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | yumex_transaction 7 | False 8 | 5 9 | Package actions to perform 10 | center-on-parent 11 | 600 12 | 260 13 | True 14 | dialog 15 | 16 | 17 | False 18 | vertical 19 | 2 20 | 21 | 22 | False 23 | end 24 | 25 | 26 | gtk-cancel 27 | True 28 | True 29 | True 30 | True 31 | 34 | 35 | 36 | False 37 | True 38 | 0 39 | 40 | 41 | 42 | 43 | gtk-ok 44 | True 45 | True 46 | True 47 | True 48 | 51 | 52 | 53 | False 54 | True 55 | 1 56 | 57 | 58 | 59 | 60 | False 61 | True 62 | end 63 | 0 64 | 65 | 66 | 67 | 68 | True 69 | False 70 | vertical 71 | 5 72 | 73 | 74 | True 75 | True 76 | in 77 | 600 78 | 400 79 | 80 | 81 | True 82 | True 83 | 84 | 85 | 86 | 89 | 90 | 91 | 92 | 93 | True 94 | True 95 | 0 96 | 97 | 98 | 99 | 100 | True 101 | False 102 | 2 103 | 104 | 105 | True 106 | False 107 | Size: 108 | 111 | 112 | 113 | False 114 | True 115 | 10 116 | 0 117 | 118 | 119 | 120 | 121 | True 122 | False 123 | start 124 | 127 | 128 | 129 | False 130 | True 131 | 1 132 | 133 | 134 | 135 | 136 | False 137 | True 138 | 1 139 | 140 | 141 | 142 | 143 | True 144 | True 145 | 1 146 | 147 | 148 | 149 | 150 | 151 | result_cancel 152 | result_ok 153 | 154 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/YumExtender.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/YumExtender.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/YumExtender" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/YumExtender" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/faq.rst: -------------------------------------------------------------------------------- 1 | ================ 2 | Yum Extender FAQ 3 | ================ 4 | 5 | Main 6 | ============= 7 | 8 | What do the package colors mean 9 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 10 | * **red** is an available update 11 | * **green** is an installed package 12 | * **blue** is an obsoleting package (a package replacing one/more packages) 13 | * **black** is an available package in a repository. 14 | 15 | These are default colors, they can be configured in the preferences. 16 | 17 | How to select all updates 18 | ~~~~~~~~~~~~~~~~~~~~~~~~~~ 19 | 20 | Click on the **+** in the header column to select/deselect all updates 21 | 22 | 23 | Configuration 24 | ============= 25 | 26 | How do I setup yumex to not ask for a password on start 27 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 28 | 29 | Run the following command as root to create a PolicyKit rule to run DnfSystem dbus commands without asking for password 30 | 31 | :: 32 | 33 | cat <<- EOF > /usr/share/polkit-1/rules.d/dnfdaemon-user.rules 34 | polkit.addRule(function(action, subject) { 35 | if (action.id == "org.baseurl.DnfSystem" && 36 | subject.active == true && subject.local == true && 37 | subject.user == "USERNAME") { 38 | polkit.log(subject.user+" got access to run org.baseurl.DnfSystem"); 39 | return polkit.Result.YES; 40 | } 41 | }); 42 | EOF 43 | 44 | 45 | Replace **USERNAME** with your login username 46 | 47 | .. warning:: This will also make other applications using the DnfSystem daemon run without asking for password when running as **USERNAME** 48 | 49 | 50 | Troubleshooting 51 | ================= 52 | 53 | Yum Extender is already running 54 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 55 | 56 | Yum Extender crashed and when you start it again, you get a dialog with the **Yum Extender is already running** message 57 | 58 | .. image:: img/error-running.png 59 | 60 | If you press **No** then the already running Yum Extender window will be shown. 61 | If you press **Yes** then the already running Yum Extender window will closed, if possible. 62 | 63 | Yum Extender uses 2 background dbus services, a notification icon service (look for a shield icon in you notification area) and DnfSystem services that is doing 64 | all the dnf related actions. If the gui is crashing, it might not be able to do a clean shutdown of these services. 65 | To clean up open a terminal windows and write the following command. 66 | 67 | :: 68 | 69 | yumex-dnf --exit 70 | 71 | 72 | 73 | It will try to shutdown these services and you should be able to start Yum Extender again. 74 | 75 | 76 | Debug issues in Yum Extender 77 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 78 | 79 | If you want to debug a problem in Yum Extender do the following. 80 | 81 | 1. Open a terminal windows 82 | 2. run : **sudo /usr/share/dnfdaemon/dnfdaemon-system -v -d --notimeout** 83 | 3. Open another terminal window 84 | 4. run : **yumex-dnf -d** 85 | 86 | Now you will be able to see Tracebacks and debug information in the 2 windows 87 | 88 | You can open an issue here_ for any problem found 89 | 90 | .. _here: https://github.com/timlau/yumex-dnf/issues 91 | 92 | 93 | -------------------------------------------------------------------------------- /docs/img/arch-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/arch-menu.png -------------------------------------------------------------------------------- /docs/img/button-apply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/button-apply.png -------------------------------------------------------------------------------- /docs/img/button-filters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/button-filters.png -------------------------------------------------------------------------------- /docs/img/button-main-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/button-main-menu.png -------------------------------------------------------------------------------- /docs/img/button-search-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/button-search-options.png -------------------------------------------------------------------------------- /docs/img/error-running.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/error-running.png -------------------------------------------------------------------------------- /docs/img/filter-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/filter-menu.png -------------------------------------------------------------------------------- /docs/img/filters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/filters.png -------------------------------------------------------------------------------- /docs/img/groups.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/groups.png -------------------------------------------------------------------------------- /docs/img/history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/history.png -------------------------------------------------------------------------------- /docs/img/main-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/main-menu.png -------------------------------------------------------------------------------- /docs/img/options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/options.png -------------------------------------------------------------------------------- /docs/img/package-info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/package-info.png -------------------------------------------------------------------------------- /docs/img/queue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/queue.png -------------------------------------------------------------------------------- /docs/img/search-bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/search-bar.png -------------------------------------------------------------------------------- /docs/img/search-options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/search-options.png -------------------------------------------------------------------------------- /docs/img/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/search.png -------------------------------------------------------------------------------- /docs/img/top-bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/top-bar.png -------------------------------------------------------------------------------- /docs/img/updates-selection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/updates-selection.png -------------------------------------------------------------------------------- /docs/img/updates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/docs/img/updates.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Yum Extender documentation master file, created by 2 | sphinx-quickstart on Sat Mar 29 15:59:17 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to Yum Extender's documentation! 7 | ======================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | main 15 | faq 16 | 17 | 18 | -------------------------------------------------------------------------------- /docs/main.rst: -------------------------------------------------------------------------------- 1 | ========================================== 2 | Yum Extender (yumex-dnf) Documentation 3 | ========================================== 4 | 5 | 6 | Introduction 7 | ============= 8 | 9 | Yum Extender (yumex-dnf) is a graphical software manager to control the software packages on your system. 10 | 11 | .. image:: img/updates.png 12 | 13 | Links 14 | ------ 15 | * Homepage_ 16 | * Development_ 17 | * Translation_ 18 | 19 | .. _Homepage: http://yumex.dk 20 | .. _Development: https://github.com/timlau/yumex-dnf 21 | .. _Translation: https://www.transifex.com/projects/p/yumex/resource/yumex-dnf/ 22 | 23 | 24 | Views 25 | ====== 26 | Yum Extender contains 4 different views to show different things like packages, groups, history & pending package actions. 27 | 28 | Packages View 29 | -------------- 30 | This view is used for normal package operations like browser or search for packages and add packages to the pending action queue 31 | to be installed, upgraded or removed. 32 | 33 | .. image:: img/updates.png 34 | 35 | 36 | Filter Sidebar 37 | ~~~~~~~~~~~~~~ 38 | The filter sidebar is used to limit the packages show to a special kind of status, this can 39 | 40 | 1. **updates** will show available updates for installed packages 41 | 2. **installed** will show packages installed on the local system 42 | 3. **available** will show packages available in the active repositories, there is not installed on the local system 43 | 4. **all** will show all of the above packages. 44 | 45 | There filters applies both to the current search result or all packages if no seach is performed. 46 | 47 | **available** and **all** will can take some time, if used not limited by a search. 48 | 49 | Search Option Menu 50 | ~~~~~~~~~~~~~~~~~~~ 51 | The search option menu is used to specify what type of search is performed when something is entered in 52 | the search entry. 53 | 54 | There is 3 kind of available search types: 55 | 56 | 1. **prefix** is searching for package names there is starting with the keyword in the search entry. 57 | 2. **keyword** is searching for package names there contains the keyword from the search entry. 58 | 3. **fields** is searching for packages where packages attributes like name, summary or description is containing the keywords from the search entry 59 | 60 | Searching 61 | ~~~~~~~~~~ 62 | 63 | Searching is performed by entering a keyword in the search entry and pressing **Enter** 64 | 65 | If a new search is performed then all search results will be shown, but can be filtered for package states, using 66 | the filter menu |filters|. 67 | 68 | Package selection 69 | ~~~~~~~~~~~~~~~~~ 70 | To perform action on packages in the view, you have to click on the checkbox in the selection column 71 | then checked, the package is add to the pending action queue, then action performed on the package depends 72 | on the current installation state of the selected package. 73 | 74 | * An installed package will be queued for removal 75 | * An available package will be queued for installation. 76 | * An available update will be queued for update. 77 | 78 | The selection column header (**+**) can be uses to perform actions on all shown packages in the view. 79 | 80 | clicking on the column header will switch between there states 81 | 82 | * Select all shown packages 83 | * Unselect all shown packages 84 | * Redo single selections by user 85 | 86 | 87 | Groups View 88 | -------------- 89 | The group view shows packages ordered by categories and groups, the selection of packages is the same as for the package view. 90 | 91 | .. image:: img/groups.png 92 | 93 | It is possible to install/remove groups also by selecting the checkbox in the group/category tree. 94 | 95 | History View 96 | -------------- 97 | The history shows the history of transaction performed by dnf on the system. 98 | 99 | .. image:: img/history.png 100 | 101 | .. warning:: If your are using both yum & dnf on the system, they will both have there own history, not including the transactions by the other tool. 102 | 103 | Pending Actions View 104 | -------------------------- 105 | The pending action queue show the pending actions for the packages selected by the user 106 | 107 | Pending actions is exeucted by pressing the apply button 108 | 109 | 110 | 111 | .. |filters| image:: img/button-filters.png 112 | .. |search-options| image:: img/button-search-options.png 113 | .. |main-menu| image:: img/button-main-menu.png 114 | .. |apply| image:: img/button-apply.png 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /gfx/meson.build: -------------------------------------------------------------------------------- 1 | ICON_DIR_SVG = join_paths(get_option('prefix'), get_option('datadir'), 'icons/hicolor/scalable/apps/') 2 | ICON_DIR_PNG = join_paths(get_option('prefix'), get_option('datadir'), 'icons/hicolor/48x48/apps/') 3 | DATA_DIR_GFX = join_paths(get_option('prefix'), get_option('datadir'), APPNAME,'gfx') 4 | 5 | install_data( 6 | APPNAME+'.svg', 7 | install_dir: ICON_DIR_SVG 8 | ) 9 | install_data( 10 | APPNAME+'.png', 11 | install_dir: ICON_DIR_PNG 12 | ) 13 | install_data( 14 | 'progress.gif', 15 | install_dir: DATA_DIR_GFX 16 | ) 17 | -------------------------------------------------------------------------------- /gfx/progress.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/gfx/progress.gif -------------------------------------------------------------------------------- /gfx/screenshoots/yumex-screenshoot-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/gfx/screenshoots/yumex-screenshoot-01.png -------------------------------------------------------------------------------- /gfx/screenshoots/yumex-screenshoot-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/gfx/screenshoots/yumex-screenshoot-02.png -------------------------------------------------------------------------------- /gfx/screenshoots/yumex-screenshoot-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/gfx/screenshoots/yumex-screenshoot-03.png -------------------------------------------------------------------------------- /gfx/screenshoots/yumex-screenshoot-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/gfx/screenshoots/yumex-screenshoot-04.png -------------------------------------------------------------------------------- /gfx/yumex-dnf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/timlau/yumex-dnf-old/1b0eba43d93c37bc7088332c118a9185fe6fb7a4/gfx/yumex-dnf.png -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('yumex-dnf', 2 | version: '4.4.0', 3 | meson_version: '>= 0.50.0' 4 | ) 5 | 6 | APPNAME = meson.project_name() 7 | DATA_DIR = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name()) 8 | BIN_DIR = join_paths(get_option('prefix'), get_option('bindir')) 9 | 10 | subdir('src') 11 | subdir('data') 12 | subdir('gfx') 13 | subdir('misc') 14 | subdir('po') 15 | -------------------------------------------------------------------------------- /misc/meson.build: -------------------------------------------------------------------------------- 1 | sassc = find_program('pysassc') 2 | 3 | 4 | 5 | THEME_DIR=join_paths(DATA_DIR, 'themes') 6 | DESKTOP_DIR = join_paths(get_option('prefix'), get_option('datadir'), 'applications') 7 | METAINFO_DIR = join_paths(get_option('prefix'), get_option('datadir'), 'metainfo') 8 | 9 | 10 | themes = [] 11 | 12 | sassc_opts = [ '-t', 'expanded' ] 13 | 14 | 15 | scss_deps = files([ 16 | 'scss/_base.scss', 17 | 'scss/_colors.scss', 18 | 'scss/_functions.scss', 19 | 'scss/_palette.scss', 20 | 'scss/_system_base.scss' 21 | ]) 22 | 23 | scss_files = [ 24 | 'Dracula', 25 | 'One-Dark', 26 | 'System-Dark', 27 | 'System-Light' 28 | ] 29 | 30 | 31 | foreach scss: scss_files 32 | themes += custom_target('@0@.theme'.format(scss), 33 | input: 'scss/@0@.scss'.format(scss), 34 | output: '@0@.theme'.format(scss), 35 | command: [sassc, sassc_opts, '@INPUT@', '@OUTPUT@'], 36 | depend_files: scss_deps, 37 | install : true, 38 | install_dir : THEME_DIR 39 | ) 40 | endforeach 41 | 42 | i18n = import('i18n') 43 | desktop_file = i18n.merge_file( 44 | input: 'yumex-dnf.desktop.in', 45 | output: 'yumex-dnf.desktop', 46 | type: 'desktop', 47 | po_dir: '../po', 48 | install: true, 49 | install_dir: DESKTOP_DIR 50 | ) 51 | 52 | desktop_file_local = i18n.merge_file( 53 | input: 'yumex-dnf-local.desktop.in', 54 | output: 'yumex-dnf-local.desktop', 55 | type: 'desktop', 56 | po_dir: '../po', 57 | install: true, 58 | install_dir: DESKTOP_DIR 59 | ) 60 | 61 | desktop_utils = find_program('desktop-file-validate', required: false) 62 | if desktop_utils.found() 63 | test('Validate desktop file', desktop_utils, 64 | args: [desktop_file] 65 | ) 66 | test('Validate desktop file', desktop_utils, 67 | args: [desktop_file_local] 68 | ) 69 | endif 70 | 71 | install_data( 72 | 'yumex-dnf-updater.desktop', 73 | install_dir: DATA_DIR 74 | ) 75 | 76 | appstream_file = i18n.merge_file( 77 | input: 'yumex-dnf.appdata.xml.in', 78 | output: 'yumex-dnf.metainfo.xml', 79 | po_dir: '../po', 80 | install: true, 81 | install_dir: METAINFO_DIR 82 | ) 83 | 84 | appstream_util = find_program('appstream-util', required: false) 85 | if appstream_util.found() 86 | test('Validate appstream file', appstream_util, 87 | args: ['validate', '--nonet', appstream_file] 88 | ) 89 | endif -------------------------------------------------------------------------------- /misc/scss/Dracula.scss: -------------------------------------------------------------------------------- 1 | $color_install: #8BE8FD; 2 | $color_update: #FF79C6; 3 | $color_downgrade: #50FA7B; 4 | $color_normal: #FFB86C; 5 | $color_obsolete: #FFB86C; 6 | 7 | $bg_color: #21252b; 8 | $primary_dark: #282A36; 9 | $primary: #44475A; 10 | $primary_light: #566388; 11 | $primary_lighter: #6272A4; 12 | $text_color: #F8F8F2; 13 | $secondary: #FF79C6; 14 | 15 | @import 'colors'; 16 | @import 'base'; 17 | 18 | -------------------------------------------------------------------------------- /misc/scss/One-Dark.scss: -------------------------------------------------------------------------------- 1 | /*Package Type Colors */ 2 | 3 | $color_install: #56b6c2; 4 | $color_update: #e06c75; 5 | $color_downgrade: #98c379; 6 | $color_normal: #d19a66; 7 | $color_obsolete: #e06c75; 8 | 9 | /* UI colors */ 10 | 11 | $bg_color: #282c34; 12 | $primary_dark: #323842; 13 | $primary: #404859; 14 | $primary_light: #5c6370; 15 | $primary_lighter: #5c6370; 16 | $text_color: #abb2bf; 17 | $secondary: #98c379; 18 | 19 | @import 'colors'; 20 | @import 'base'; 21 | -------------------------------------------------------------------------------- /misc/scss/System-Dark.scss: -------------------------------------------------------------------------------- 1 | @import 'palette'; 2 | 3 | $color_install: $Material_Dark_Indigo; 4 | $color_update: $Material_Dark_Pink; 5 | $color_downgrade: $Material_Dark_Lime; 6 | $color_normal: $Material_Dark_Orange; 7 | $color_obsolete: $Material_Dark_Green; 8 | 9 | 10 | @import 'functions'; 11 | @import 'colors'; 12 | @import 'system_base'; -------------------------------------------------------------------------------- /misc/scss/System-Light.scss: -------------------------------------------------------------------------------- 1 | @import 'palette'; 2 | 3 | $color_install: $Material_Light_Indigo; 4 | $color_update: $Material_Light_Pink; 5 | $color_downgrade: $Material_Light_Lime; 6 | $color_normal: $Material_Light_Orange; 7 | $color_obsolete: $Material_Light_Green; 8 | 9 | @import 'functions'; 10 | @import 'colors'; 11 | @import 'system_base'; -------------------------------------------------------------------------------- /misc/scss/_base.scss: -------------------------------------------------------------------------------- 1 | @import 'palette'; 2 | 3 | /* generic vindow & dialog */ 4 | .main, 5 | .transaction, 6 | .error { 7 | background-color: $bg_color; 8 | color: $text_color; 9 | } 10 | 11 | /* Transaction & error dialog buttons */ 12 | .btn { 13 | background-color: $primary; 14 | color: $text_color; 15 | } 16 | 17 | /* Search bar */ 18 | .search { 19 | &__entry.search { 20 | color: $Gtk_orange_2; 21 | background-color: $bg_color; 22 | // padding: 5px; 23 | border: 1px solid $text_color; 24 | border-radius: 5px; 25 | font-weight: normal; 26 | font-size: 120%; 27 | caret-color: $Gtk_orange_2; /* cursor color */ 28 | } 29 | 30 | &__entry.search image { 31 | color: $Gtk_orange_4; 32 | background-color: $bg_color; 33 | } 34 | 35 | & box { 36 | background-color: $primary_dark; 37 | border: none; 38 | outline-style: none; 39 | } 40 | 41 | &__btn { 42 | color: $text_color; 43 | background-color: $primary; 44 | border: none; 45 | box-shadow: none; 46 | transition: all 200ms ease-in; 47 | } 48 | 49 | &__btn:hover { 50 | color: $text_color; 51 | background-color: $primary_light; 52 | } 53 | } 54 | 55 | /* Infobar */ 56 | .info { 57 | background-color: $bg_color; 58 | border: 3px solid $primary; 59 | border-radius: 10px; 60 | padding: 15px; 61 | margin: 10px 50px 1440px 50px; 62 | box-shadow: 0px 2px 5px 0px rgba($primary, .75), 63 | 0 0 0 1440px rgba($bg_color, .5); 64 | transition: all 500ms ease-in-out; 65 | 66 | &__label { 67 | color: $Gtk_orange_2; 68 | } 69 | 70 | &__sublabel { 71 | color: $Gtk_orange_4; 72 | } 73 | 74 | &__spinner { 75 | color: $primary_light; 76 | background-color: $primary_dark; 77 | } 78 | 79 | &__progress > trough { 80 | min-height: 6px; 81 | background-color: $primary_light; 82 | } 83 | 84 | &__progress > trough > progress { 85 | min-height: 8px; 86 | background-color: $secondary; 87 | } 88 | } 89 | 90 | /* Paned */ 91 | .content__paned > separator { 92 | background-color: $primary; 93 | } 94 | 95 | /* Package filter selector */ 96 | .pkgfilter { 97 | background-color: $bg_color; 98 | padding-top: 10px; 99 | border-right: 2px solid $primary; 100 | 101 | & toolbar { 102 | background-color: $primary_dark; 103 | } 104 | 105 | & button { 106 | color: $text_color; 107 | background-color: $primary_dark; 108 | } 109 | 110 | &__list { 111 | background-color: $bg_color; 112 | color: $text_color; 113 | margin-left: 10px; 114 | } 115 | 116 | &__item { 117 | padding-left: 0.5em; 118 | transition: all 200ms ease-in; 119 | color: $primary_lighter; 120 | background-color: $bg_color; 121 | border-left: 5px solid $bg_color; 122 | border-radius: 5px 0 0 5px; 123 | } 124 | 125 | &__item:selected { 126 | color: $text_color; 127 | background-color: $primary; 128 | border-left-color: $secondary; 129 | } 130 | 131 | &__item:hover { 132 | color: $text_color; 133 | } 134 | } 135 | 136 | /* content views (packages, group, history, queue) */ 137 | .content__view { 138 | & treeview { 139 | background-color: $bg_color; 140 | color: $text_color; 141 | } 142 | 143 | & treeview:selected { 144 | background-color: $primary_light; 145 | color: $text_color; 146 | } 147 | 148 | & treeview:hover { 149 | background-color: $primary; 150 | color: $text_color; 151 | } 152 | 153 | & header button { 154 | color: $text_color; 155 | background-color: $primary_dark; 156 | border: none; 157 | border-bottom: 1px solid $primary_lighter; 158 | } 159 | 160 | & header button + button { 161 | border-left: 1px dotted $primary_lighter; 162 | } 163 | } 164 | 165 | /* Package Info Selector */ 166 | .pkginfo { 167 | &__select { 168 | background-color: $bg_color; 169 | border-right: 2px solid $primary; 170 | } 171 | 172 | &__list { 173 | background-color: $bg_color; 174 | margin-left: 5px; 175 | margin-top: 5px; 176 | } 177 | 178 | &__item { 179 | color: $primary_lighter; 180 | background-color: $bg_color; 181 | transition: all 200ms ease-in 100ms; 182 | border-left: 4px solid $bg_color; 183 | border-radius: 2px 0 0 2px; 184 | padding: 0px 5px 0px 5px; 185 | } 186 | 187 | &__item:hover { 188 | color: $text_color; 189 | } 190 | 191 | &__item:selected { 192 | color: $text_color; 193 | background-color: $primary; 194 | border-left-color: $secondary; 195 | } 196 | 197 | &__text text { 198 | background-color: $bg_color; 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /misc/scss/_colors.scss: -------------------------------------------------------------------------------- 1 | /*Package Type Colors */ 2 | 3 | @define-color color_install #{$color_install}; 4 | @define-color color_update #{$color_update}; 5 | @define-color color_downgrade #{$color_downgrade}; 6 | @define-color color_normal #{$color_normal}; 7 | @define-color color_obsolete #{$color_obsolete}; 8 | -------------------------------------------------------------------------------- /misc/scss/_functions.scss: -------------------------------------------------------------------------------- 1 | @function gtkalpha($c,$a) { 2 | @return unquote("alpha(#{$c},#{$a})"); 3 | } 4 | 5 | @function gtkmix($c1,$c2,$r) { 6 | $ratio: 1 - $r / 100%; // match SCSS mix() 7 | @return unquote("mix(#{$c1},#{$c2},#{$ratio})"); 8 | } 9 | 10 | @function gtkshade($c,$s) { 11 | @return unquote("shade(#{$c},#{$s})"); 12 | } 13 | 14 | @function gtkcolor($c) { 15 | @return unquote("@#{$c}"); 16 | } 17 | -------------------------------------------------------------------------------- /misc/scss/_palette.scss: -------------------------------------------------------------------------------- 1 | $Gtk_blue_1: #99c1f1; 2 | $Gtk_blue_2: #62a0ea; 3 | $Gtk_blue_3: #3584e4; 4 | $Gtk_blue_4: #1c71d8; 5 | $Gtk_blue_5: #1a5fb4; 6 | $Gtk_green_1: #8ff0a4; 7 | $Gtk_green_2: #57e389; 8 | $Gtk_green_3: #33d17a; 9 | $Gtk_green_4: #2ec27e; 10 | $Gtk_green_5: #26a269; 11 | $Gtk_yellow_1: #f9f06b; 12 | $Gtk_yellow_2: #f8e45c; 13 | $Gtk_yellow_3: #f6d32d; 14 | $Gtk_yellow_4: #f5c211; 15 | $Gtk_yellow_5: #e5a50a; 16 | $Gtk_orange_1: #ffbe6f; 17 | $Gtk_orange_2: #ffa348; 18 | $Gtk_orange_3: #ff7800; 19 | $Gtk_orange_4: #e66100; 20 | $Gtk_orange_5: #c64600; 21 | $Gtk_red_1: #f66151; 22 | $Gtk_red_2: #ed333b; 23 | $Gtk_red_3: #e01b24; 24 | $Gtk_red_4: #c01c28; 25 | $Gtk_red_5: #a51d2d; 26 | $Gtk_purple_1: #dc8add; 27 | $Gtk_purple_2: #c061cb; 28 | $Gtk_purple_3: #9141ac; 29 | $Gtk_purple_4: #813d9c; 30 | $Gtk_purple_5: #613583; 31 | $Gtk_brown_1: #cdab8f; 32 | $Gtk_brown_2: #b5835a; 33 | $Gtk_brown_3: #986a44; 34 | $Gtk_brown_4: #865e3c; 35 | $Gtk_brown_5: #63452c; 36 | $Gtk_light_1: #ffffff; 37 | $Gtk_light_2: #f6f5f4; 38 | $Gtk_light_3: #deddda; 39 | $Gtk_light_4: #c0bfbc; 40 | $Gtk_light_5: #9a9996; 41 | $Gtk_dark_1: #77767b; 42 | $Gtk_dark_2: #5e5c64; 43 | $Gtk_dark_3: #3d3846; 44 | $Gtk_dark_4: #241f31; 45 | $Gtk_dark_5: #000000; 46 | 47 | $Material_Dark_Red: #EF9A9A; 48 | $Material_Dark_Pink: #F48FB1; 49 | $Material_Dark_Purple: #CE93D8; 50 | $Material_Dark_DeepPurple: #B39DDB; 51 | $Material_Dark_Indigo: #9FA8DA; 52 | $Material_Dark_Blue: #90CAF9; 53 | $Material_Dark_LightBlue: #81D4FA; 54 | $Material_Dark_Cyan: #80DEEA; 55 | $Material_Dark_Teal: #80CBC4; 56 | $Material_Dark_Green: #A5D6A7; 57 | $Material_Dark_LightGreen: #C5E1A5; 58 | $Material_Dark_Lime: #E6EE9C; 59 | $Material_Dark_Yellow: #FFF59D; 60 | $Material_Dark_Amber: #FFE082; 61 | $Material_Dark_Orange: #FFCC80; 62 | $Material_Dark_DeepOrange: #FFAB91; 63 | $Material_Dark_Brown: #BCAAA4; 64 | $Material_Dark_Grey: #EEEEEE; 65 | $Material_Dark_BlueGrey: #B0BEC5; 66 | 67 | $Material_Light_Red: #F44336; 68 | $Material_Light_Pink: #E91E63; 69 | $Material_Light_Purple: #9C27B0; 70 | $Material_Light_DeepPurple: #673AB7; 71 | $Material_Light_Indigo: #3F51B5; 72 | $Material_Light_Blue: #2196F3; 73 | $Material_Light_LightBlue: #03A9F4; 74 | $Material_Light_Cyan: #00BCD4; 75 | $Material_Light_Teal: #009688; 76 | $Material_Light_Green: #4CAF50; 77 | $Material_Light_LightGreen: #8BC34A; 78 | $Material_Light_Lime: #CDDC39; 79 | $Material_Light_Yellow: #FFEB3B; 80 | $Material_Light_Amber: #FFC107; 81 | $Material_Light_Orange: #FF9800; 82 | $Material_Light_DeepOrange: #FF5722; 83 | $Material_Light_Brown: #795548; 84 | $Material_Light_Grey: #9E9E9E; 85 | $Material_Light_BlueGrey: #607D8B; 86 | -------------------------------------------------------------------------------- /misc/scss/_system_base.scss: -------------------------------------------------------------------------------- 1 | .info { 2 | background-color: gtkcolor(theme_bg_color); 3 | border: 3px solid currentColor; 4 | border-radius: 10px; 5 | padding: 15px; 6 | margin: 10px 50px 1440px 50px; 7 | box-shadow: 0px 2px 5px 0px rgba(white, .75), 8 | 0 0 0 1440px rgba(black, .5); 9 | transition: all 500ms ease-in-out; 10 | 11 | } 12 | 13 | /* Package filter selector */ 14 | .pkgfilter { 15 | padding-top: 10px; 16 | 17 | .pkgfilter__list { 18 | margin-left: 10px; 19 | margin-right: 10px; 20 | } 21 | 22 | .pkgfilter__item { 23 | padding-left: 0.5em; 24 | } 25 | } 26 | 27 | /* Package Info Selector */ 28 | 29 | .pkginfo__list { 30 | margin-left: 5px; 31 | margin-right: 5px; 32 | margin-top: 5px; 33 | } 34 | 35 | .pkginfo__item { 36 | padding: 0px 5px 0px 5px; 37 | } 38 | -------------------------------------------------------------------------------- /misc/themes/Dracula.theme: -------------------------------------------------------------------------------- 1 | /*Package Type Colors */ 2 | @define-color color_install #8BE8FD; 3 | @define-color color_update #FF79C6; 4 | @define-color color_downgrade #50FA7B; 5 | @define-color color_normal #FFB86C; 6 | @define-color color_obsolete #FFB86C; 7 | /* generic vindow & dialog */ 8 | .main, 9 | .transaction, 10 | .error { 11 | background-color: #21252b; 12 | color: #F8F8F2; 13 | } 14 | 15 | /* Transaction & error dialog buttons */ 16 | .btn { 17 | background-color: #44475A; 18 | color: #F8F8F2; 19 | } 20 | 21 | /* Search bar */ 22 | .search__entry.search { 23 | color: #ffa348; 24 | background-color: #21252b; 25 | border: 1px solid #F8F8F2; 26 | border-radius: 5px; 27 | font-weight: normal; 28 | font-size: 120%; 29 | caret-color: #ffa348; 30 | /* cursor color */ 31 | } 32 | 33 | .search__entry.search image { 34 | color: #e66100; 35 | background-color: #21252b; 36 | } 37 | 38 | .search box { 39 | background-color: #282A36; 40 | border: none; 41 | outline-style: none; 42 | } 43 | 44 | .search__btn { 45 | color: #F8F8F2; 46 | background-color: #44475A; 47 | border: none; 48 | box-shadow: none; 49 | transition: all 200ms ease-in; 50 | } 51 | 52 | .search__btn:hover { 53 | color: #F8F8F2; 54 | background-color: #566388; 55 | } 56 | 57 | /* Infobar */ 58 | .info { 59 | background-color: #21252b; 60 | border: 3px solid #44475A; 61 | border-radius: 10px; 62 | padding: 15px; 63 | margin: 10px 50px 1440px 50px; 64 | box-shadow: 0px 2px 5px 0px rgba(68, 71, 90, 0.75), 0 0 0 1440px rgba(33, 37, 43, 0.5); 65 | transition: all 500ms ease-in-out; 66 | } 67 | 68 | .info__label { 69 | color: #ffa348; 70 | } 71 | 72 | .info__sublabel { 73 | color: #e66100; 74 | } 75 | 76 | .info__spinner { 77 | color: #566388; 78 | background-color: #282A36; 79 | } 80 | 81 | .info__progress > trough { 82 | min-height: 6px; 83 | background-color: #566388; 84 | } 85 | 86 | .info__progress > trough > progress { 87 | min-height: 8px; 88 | background-color: #FF79C6; 89 | } 90 | 91 | /* Paned */ 92 | .content__paned > separator { 93 | background-color: #44475A; 94 | } 95 | 96 | /* Package filter selector */ 97 | .pkgfilter { 98 | background-color: #21252b; 99 | padding-top: 10px; 100 | border-right: 2px solid #44475A; 101 | } 102 | 103 | .pkgfilter toolbar { 104 | background-color: #282A36; 105 | } 106 | 107 | .pkgfilter button { 108 | color: #F8F8F2; 109 | background-color: #282A36; 110 | } 111 | 112 | .pkgfilter__list { 113 | background-color: #21252b; 114 | color: #F8F8F2; 115 | margin-left: 10px; 116 | } 117 | 118 | .pkgfilter__item { 119 | padding-left: 0.5em; 120 | transition: all 200ms ease-in; 121 | color: #6272A4; 122 | background-color: #21252b; 123 | border-left: 5px solid #21252b; 124 | border-radius: 5px 0 0 5px; 125 | } 126 | 127 | .pkgfilter__item:selected { 128 | color: #F8F8F2; 129 | background-color: #44475A; 130 | border-left-color: #FF79C6; 131 | } 132 | 133 | .pkgfilter__item:hover { 134 | color: #F8F8F2; 135 | } 136 | 137 | /* content views (packages, group, history, queue) */ 138 | .content__view treeview { 139 | background-color: #21252b; 140 | color: #F8F8F2; 141 | } 142 | 143 | .content__view treeview:selected { 144 | background-color: #566388; 145 | color: #F8F8F2; 146 | } 147 | 148 | .content__view treeview:hover { 149 | background-color: #44475A; 150 | color: #F8F8F2; 151 | } 152 | 153 | .content__view header button { 154 | color: #F8F8F2; 155 | background-color: #282A36; 156 | border: none; 157 | border-bottom: 1px solid #6272A4; 158 | } 159 | 160 | .content__view header button + button { 161 | border-left: 1px dotted #6272A4; 162 | } 163 | 164 | /* Package Info Selector */ 165 | .pkginfo__select { 166 | background-color: #21252b; 167 | border-right: 2px solid #44475A; 168 | } 169 | 170 | .pkginfo__list { 171 | background-color: #21252b; 172 | margin-left: 5px; 173 | margin-top: 5px; 174 | } 175 | 176 | .pkginfo__item { 177 | color: #6272A4; 178 | background-color: #21252b; 179 | transition: all 200ms ease-in 100ms; 180 | border-left: 4px solid #21252b; 181 | border-radius: 2px 0 0 2px; 182 | padding: 0px 5px 0px 5px; 183 | } 184 | 185 | .pkginfo__item:hover { 186 | color: #F8F8F2; 187 | } 188 | 189 | .pkginfo__item:selected { 190 | color: #F8F8F2; 191 | background-color: #44475A; 192 | border-left-color: #FF79C6; 193 | } 194 | 195 | .pkginfo__text text { 196 | background-color: #21252b; 197 | } 198 | -------------------------------------------------------------------------------- /misc/themes/One-Dark.theme: -------------------------------------------------------------------------------- 1 | /*Package Type Colors */ 2 | /* UI colors */ 3 | /*Package Type Colors */ 4 | @define-color color_install #56b6c2; 5 | @define-color color_update #e06c75; 6 | @define-color color_downgrade #98c379; 7 | @define-color color_normal #d19a66; 8 | @define-color color_obsolete #e06c75; 9 | /* generic vindow & dialog */ 10 | .main, 11 | .transaction, 12 | .error { 13 | background-color: #282c34; 14 | color: #abb2bf; 15 | } 16 | 17 | /* Transaction & error dialog buttons */ 18 | .btn { 19 | background-color: #404859; 20 | color: #abb2bf; 21 | } 22 | 23 | /* Search bar */ 24 | .search__entry.search { 25 | color: #ffa348; 26 | background-color: #282c34; 27 | border: 1px solid #abb2bf; 28 | border-radius: 5px; 29 | font-weight: normal; 30 | font-size: 120%; 31 | caret-color: #ffa348; 32 | /* cursor color */ 33 | } 34 | 35 | .search__entry.search image { 36 | color: #e66100; 37 | background-color: #282c34; 38 | } 39 | 40 | .search box { 41 | background-color: #323842; 42 | border: none; 43 | outline-style: none; 44 | } 45 | 46 | .search__btn { 47 | color: #abb2bf; 48 | background-color: #404859; 49 | border: none; 50 | box-shadow: none; 51 | transition: all 200ms ease-in; 52 | } 53 | 54 | .search__btn:hover { 55 | color: #abb2bf; 56 | background-color: #5c6370; 57 | } 58 | 59 | /* Infobar */ 60 | .info { 61 | background-color: #282c34; 62 | border: 3px solid #404859; 63 | border-radius: 10px; 64 | padding: 15px; 65 | margin: 10px 50px 1440px 50px; 66 | box-shadow: 0px 2px 5px 0px rgba(64, 72, 89, 0.75), 0 0 0 1440px rgba(40, 44, 52, 0.5); 67 | transition: all 500ms ease-in-out; 68 | } 69 | 70 | .info__label { 71 | color: #ffa348; 72 | } 73 | 74 | .info__sublabel { 75 | color: #e66100; 76 | } 77 | 78 | .info__spinner { 79 | color: #5c6370; 80 | background-color: #323842; 81 | } 82 | 83 | .info__progress > trough { 84 | min-height: 6px; 85 | background-color: #5c6370; 86 | } 87 | 88 | .info__progress > trough > progress { 89 | min-height: 8px; 90 | background-color: #98c379; 91 | } 92 | 93 | /* Paned */ 94 | .content__paned > separator { 95 | background-color: #404859; 96 | } 97 | 98 | /* Package filter selector */ 99 | .pkgfilter { 100 | background-color: #282c34; 101 | padding-top: 10px; 102 | border-right: 2px solid #404859; 103 | } 104 | 105 | .pkgfilter toolbar { 106 | background-color: #323842; 107 | } 108 | 109 | .pkgfilter button { 110 | color: #abb2bf; 111 | background-color: #323842; 112 | } 113 | 114 | .pkgfilter__list { 115 | background-color: #282c34; 116 | color: #abb2bf; 117 | margin-left: 10px; 118 | } 119 | 120 | .pkgfilter__item { 121 | padding-left: 0.5em; 122 | transition: all 200ms ease-in; 123 | color: #5c6370; 124 | background-color: #282c34; 125 | border-left: 5px solid #282c34; 126 | border-radius: 5px 0 0 5px; 127 | } 128 | 129 | .pkgfilter__item:selected { 130 | color: #abb2bf; 131 | background-color: #404859; 132 | border-left-color: #98c379; 133 | } 134 | 135 | .pkgfilter__item:hover { 136 | color: #abb2bf; 137 | } 138 | 139 | /* content views (packages, group, history, queue) */ 140 | .content__view treeview { 141 | background-color: #282c34; 142 | color: #abb2bf; 143 | } 144 | 145 | .content__view treeview:selected { 146 | background-color: #5c6370; 147 | color: #abb2bf; 148 | } 149 | 150 | .content__view treeview:hover { 151 | background-color: #404859; 152 | color: #abb2bf; 153 | } 154 | 155 | .content__view header button { 156 | color: #abb2bf; 157 | background-color: #323842; 158 | border: none; 159 | border-bottom: 1px solid #5c6370; 160 | } 161 | 162 | .content__view header button + button { 163 | border-left: 1px dotted #5c6370; 164 | } 165 | 166 | /* Package Info Selector */ 167 | .pkginfo__select { 168 | background-color: #282c34; 169 | border-right: 2px solid #404859; 170 | } 171 | 172 | .pkginfo__list { 173 | background-color: #282c34; 174 | margin-left: 5px; 175 | margin-top: 5px; 176 | } 177 | 178 | .pkginfo__item { 179 | color: #5c6370; 180 | background-color: #282c34; 181 | transition: all 200ms ease-in 100ms; 182 | border-left: 4px solid #282c34; 183 | border-radius: 2px 0 0 2px; 184 | padding: 0px 5px 0px 5px; 185 | } 186 | 187 | .pkginfo__item:hover { 188 | color: #abb2bf; 189 | } 190 | 191 | .pkginfo__item:selected { 192 | color: #abb2bf; 193 | background-color: #404859; 194 | border-left-color: #98c379; 195 | } 196 | 197 | .pkginfo__text text { 198 | background-color: #282c34; 199 | } 200 | -------------------------------------------------------------------------------- /misc/themes/System-Dark.theme: -------------------------------------------------------------------------------- 1 | /*Package Type Colors */ 2 | @define-color color_install #9FA8DA; 3 | @define-color color_update #F48FB1; 4 | @define-color color_downgrade #E6EE9C; 5 | @define-color color_normal #FFCC80; 6 | @define-color color_obsolete #A5D6A7; 7 | .info { 8 | background-color: @theme_bg_color; 9 | border: 3px solid currentColor; 10 | border-radius: 10px; 11 | padding: 15px; 12 | margin: 10px 50px 1440px 50px; 13 | box-shadow: 0px 2px 5px 0px rgba(255, 255, 255, 0.75), 0 0 0 1440px rgba(0, 0, 0, 0.5); 14 | transition: all 500ms ease-in-out; 15 | } 16 | 17 | /* Package filter selector */ 18 | .pkgfilter { 19 | padding-top: 10px; 20 | } 21 | 22 | .pkgfilter .pkgfilter__list { 23 | margin-left: 10px; 24 | margin-right: 10px; 25 | } 26 | 27 | .pkgfilter .pkgfilter__item { 28 | padding-left: 0.5em; 29 | } 30 | 31 | /* Package Info Selector */ 32 | .pkginfo__list { 33 | margin-left: 5px; 34 | margin-right: 5px; 35 | margin-top: 5px; 36 | } 37 | 38 | .pkginfo__item { 39 | padding: 0px 5px 0px 5px; 40 | } 41 | -------------------------------------------------------------------------------- /misc/themes/System-Light.theme: -------------------------------------------------------------------------------- 1 | /*Package Type Colors */ 2 | @define-color color_install #0319AB; 3 | @define-color color_update #E91E63; 4 | @define-color color_downgrade #CDDC39; 5 | @define-color color_normal #AB2A03; 6 | @define-color color_obsolete #4CAE54; 7 | .info { 8 | background-color: @theme_bg_color; 9 | border: 3px solid currentColor; 10 | border-radius: 10px; 11 | padding: 15px; 12 | margin: 10px 50px 1440px 50px; 13 | box-shadow: 0px 2px 5px 0px rgba(255, 255, 255, 0.75), 0 0 0 1440px rgba(0, 0, 0, 0.5); 14 | transition: all 500ms ease-in-out; 15 | } 16 | 17 | /* Package filter selector */ 18 | .pkgfilter { 19 | padding-top: 10px; 20 | } 21 | 22 | .pkgfilter .pkgfilter__list { 23 | margin-left: 10px; 24 | margin-right: 10px; 25 | } 26 | 27 | .pkgfilter .pkgfilter__item { 28 | padding-left: 0.5em; 29 | } 30 | 31 | /* Package Info Selector */ 32 | .pkginfo__list { 33 | margin-left: 5px; 34 | margin-right: 5px; 35 | margin-top: 5px; 36 | } 37 | 38 | .pkginfo__item { 39 | padding: 0px 5px 0px 5px; 40 | } 41 | -------------------------------------------------------------------------------- /misc/yumex-dnf-local.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Yum Extender Package Installer 3 | Comment=Install local packages on system 4 | Categories=System; 5 | Exec=/usr/bin/yumex-dnf --install %F 6 | Terminal=false 7 | Type=Application 8 | Icon=yumex 9 | StartupNotify=true 10 | NoDisplay=true 11 | MimeType=application/x-rpm;application/x-redhat-package-manager; 12 | 13 | -------------------------------------------------------------------------------- /misc/yumex-dnf-updater.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Version=1.0 3 | Encoding=UTF-8 4 | Name=Yum Extender Update Checker 5 | Type=Application 6 | Exec=yumex-dnf-updatechecker 7 | Terminal=false 8 | Icon=yumex 9 | Comment=Autostart yumex-dnf-updatechecker script 10 | NoDisplay=false 11 | Categories=Utility; 12 | -------------------------------------------------------------------------------- /misc/yumex-dnf.appdata.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | yumex-dnf.desktop 5 | CC0-1.0 6 | GPL-2.0+ 7 | Yum Extender 8 | <_summary>Graphical package manager 9 | Tim Lauridsen 10 | 11 | 12 | <_p> 13 | Yum Extender is a graphical package management application. It allows you to search and browse for packages to install, 14 | remove and update on your computer. 15 | 16 | <_p>It is designed to give you full control over the packages on your computer and to be used by all users. 17 | <_p>Features: 18 |
    19 | <_li>Browse packages available for installation or update 20 | <_li>Browse packages installed on your computer 21 | <_li>Search packages by name, summary, description 22 | <_li>Watch the history of package action on your system 23 | <_li>Browse and install/remove package groups 24 | <_li>Browse packages by size, repository 25 | <_li>Control what package repositories are enabled for the current session 26 |
27 |
28 | 29 | 30 | 31 | https://github.com/timlau/yumex-dnf/raw/develop/gfx/screenshoots/yumex-screenshoot-01.png 32 | 33 | 34 | https://github.com/timlau/yumex-dnf/raw/develop/gfx/screenshoots/yumex-screenshoot-02.png 35 | 36 | 37 | https://github.com/timlau/yumex-dnf/raw/develop/gfx/screenshoots/yumex-screenshoot-03.png 38 | 39 | 40 | https://github.com/timlau/yumex-dnf/raw/develop/gfx/screenshoots/yumex-screenshoot-04.png 41 | 42 | 43 | yumex-dnf.desktop 44 | https://github.com/timlau/yumex-dnf 45 | https://github.com/timlau/yumex-dnf/issues 46 | https://yumex-dnf.readthedocs.io/en/latest/ 47 | 48 | tla_at_rasmil.dk 49 | 50 | 51 | none 52 | none 53 | none 54 | none 55 | none 56 | none 57 | none 58 | none 59 | none 60 | none 61 | none 62 | none 63 | none 64 | none 65 | none 66 | none 67 | none 68 | none 69 | none 70 | none 71 | none 72 | none 73 | none 74 | none 75 | none 76 | none 77 | none 78 | 79 |
80 | 81 | -------------------------------------------------------------------------------- /misc/yumex-dnf.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Yum Extender 3 | Comment=Install, update and remove applications 4 | Categories=System;Settings;X-Red-Hat-Base;X-Fedora; 5 | Icon=yumex-dnf 6 | Exec=/usr/bin/yumex-dnf 7 | Type=Application 8 | Terminal=false 9 | Encoding=UTF-8 10 | GenericName=Software Installer 11 | -------------------------------------------------------------------------------- /po/LINGUAS: -------------------------------------------------------------------------------- 1 | ar 2 | bg 3 | bn_IN 4 | ca 5 | cmn 6 | cs 7 | da 8 | de 9 | el 10 | en_BR 11 | es_ES 12 | es 13 | fa 14 | fr 15 | gu 16 | he_IL 17 | hu 18 | id 19 | it 20 | ja 21 | ko 22 | ky 23 | nl 24 | pl 25 | pt_BR 26 | pt 27 | ru 28 | ru_RU 29 | sk 30 | sr@latin 31 | sr 32 | sv 33 | tr_TR 34 | uk 35 | zh_CN 36 | zh_TW 37 | -------------------------------------------------------------------------------- /po/POTFILES.in: -------------------------------------------------------------------------------- 1 | src/main.py 2 | src/update.py 3 | src/yumex/__init__.py 4 | src/yumex/backend/dnf.py 5 | src/yumex/backend/__init__.py 6 | src/yumex/base/__init__.py 7 | src/yumex/common/config.py 8 | src/yumex/common/const.py 9 | src/yumex/common/__init__.py 10 | src/yumex/gui/__init__.py 11 | src/yumex/updater/__init__.py 12 | src/yumex/gui/dialogs/aboutdialog.py 13 | src/yumex/gui/dialogs/errordialog.py 14 | src/yumex/gui/dialogs/__init__.py 15 | src/yumex/gui/dialogs/preferences.py 16 | src/yumex/gui/dialogs/progresssplash.py 17 | src/yumex/gui/dialogs/transactionresult.py 18 | src/yumex/gui/views/groupview.py 19 | src/yumex/gui/views/historypackageview.py 20 | src/yumex/gui/views/historyview.py 21 | src/yumex/gui/views/__init__.py 22 | src/yumex/gui/views/packagequeue.py 23 | src/yumex/gui/views/packageview.py 24 | src/yumex/gui/views/queueview.py 25 | src/yumex/gui/views/repoview.py 26 | src/yumex/gui/views/selectionview.py 27 | src/yumex/gui/widgets/content.py 28 | src/yumex/gui/widgets/filters.py 29 | src/yumex/gui/widgets/__init__.py 30 | src/yumex/gui/widgets/mainnenu.py 31 | src/yumex/gui/widgets/packageinfo.py 32 | src/yumex/gui/widgets/progress.py 33 | src/yumex/gui/widgets/searchbar.py 34 | src/yumex/gui/window/basewindow.py 35 | src/yumex/gui/window/__init__.py 36 | misc/yumex-dnf.desktop.in 37 | misc/yumex-dnf-local.desktop.in 38 | misc/yumex-dnf.appdata.xml.in 39 | [type: gettext/glade]data/ui/errordialog.ui 40 | [type: gettext/glade]data/ui/preferences.ui 41 | [type: gettext/glade]data/ui/progresssplash.ui 42 | [type: gettext/glade]data/ui/shortcuts.ui 43 | [type: gettext/glade]data/ui/transactionresult.ui 44 | [type: gettext/glade]data/ui/yumex.ui 45 | -------------------------------------------------------------------------------- /po/meson.build: -------------------------------------------------------------------------------- 1 | i18n = import('i18n') 2 | i18n.gettext('yumex-dnf', preset : 'glib') 3 | 4 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: iso-8859-1 -*- 3 | # Yum Exteder (yumex) - A GUI for yum 4 | # Copyright (C) 2013 Tim Lauridsen < timlaufedoraprojectorg > 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to 18 | # the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | """ Main launcher """ 21 | # pylint: disable=broad-except, unused-import, wrong-import-position 22 | 23 | import signal 24 | import subprocess 25 | import sys 26 | import traceback 27 | import gi # isort:skip 28 | 29 | # We need this for else is Gtk 4.0 selected by default 30 | gi.require_version("Gtk", "3.0") # isort:skip 31 | gi.require_version("Notify", "0.7") # isort:skip 32 | from gi.repository import Gtk # noqa: F401, E402 33 | 34 | from yumex import YumexApplication # noqa: E402 35 | 36 | here = sys.path[0] 37 | if here != "/usr/bin": 38 | # git checkout 39 | sys.path[0] = here 40 | print(f"set PYTHONPATH to {here}") 41 | 42 | try: 43 | signal.signal(signal.SIGINT, signal.SIG_DFL) 44 | app = YumexApplication() 45 | exit_status = app.run(sys.argv) 46 | sys.exit(exit_status) 47 | except Exception: 48 | print("Exception in user code:") 49 | print("-" * 80) 50 | traceback.print_exc(file=sys.stdout) 51 | print("-" * 80) 52 | 53 | # Try to close backend dbus daemon 54 | print("Closing backend D-Bus daemons") 55 | try: 56 | subprocess.call( 57 | "/usr/bin/dbus-send --system --print-reply " 58 | "--dest=org.baseurl.DnfSystem / org.baseurl.DnfSystem.Exit", 59 | shell=True, 60 | ) 61 | except Exception: 62 | pass 63 | sys.exit(1) 64 | -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | python = import('python') 2 | 3 | message('Looking for dependencies') 4 | py_installation = python.find_installation('python3') 5 | if not py_installation.found() 6 | error('No valid python3 binary found') 7 | else 8 | message('Found python3 binary') 9 | endif 10 | 11 | python_dir = join_paths(get_option('prefix'), py_installation.get_install_dir()) 12 | 13 | conf = configuration_data() 14 | conf.set('DATA_DIR', DATA_DIR) 15 | conf.set('PYTHON_DIR', python_dir) 16 | conf.set('VERSION', meson.project_version()) 17 | conf.set('PYTHON', python.find_installation('python3').path()) 18 | conf.set('LOCALE_DIR', join_paths(get_option('prefix'), get_option('localedir'))) 19 | 20 | 21 | install_subdir( 22 | 'yumex', 23 | install_dir: python_dir 24 | ) 25 | 26 | message('Preparing bin files') 27 | configure_file( 28 | input: 'main.py', 29 | output: 'yumex-dnf', 30 | configuration: conf, 31 | install_dir: BIN_DIR 32 | ) 33 | configure_file( 34 | input: 'update.py', 35 | output: 'yumex-dnf-updatechecker', 36 | configuration: conf, 37 | install_dir: BIN_DIR 38 | ) 39 | -------------------------------------------------------------------------------- /src/update.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: iso-8859-1 -*- 3 | # Yum Exteder (yumex) - A GUI for yum 4 | # Copyright (C) 2015 Tim Lauridsen < timlaufedoraprojectorg > 5 | # 6 | # This program is free software; you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation; either version 2 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # This program is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with this program; if not, write to 18 | # the Free Software Foundation, Inc., 19 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 20 | 21 | # pylint: disable=broad-except, unused-import, wrong-import-position 22 | 23 | import sys 24 | import traceback 25 | import subprocess 26 | import signal 27 | 28 | # We need this for else is Gtk 4.0 selected by default 29 | import gi # isort:skip 30 | 31 | gi.require_version("Gtk", "3.0") # isort:skip 32 | gi.require_version("Notify", "0.7") # isort:skip 33 | from gi.repository import Gtk # noqa: F401, E402 34 | 35 | from yumex.updater import UpdateApplication # noqa: E402 36 | 37 | here = sys.path[0] 38 | if here != "/usr/bin": 39 | # git checkout 40 | sys.path[0] = here 41 | print(f"set PYTHONPATH to {here}") 42 | 43 | try: 44 | signal.signal(signal.SIGINT, signal.SIG_DFL) 45 | app = UpdateApplication() 46 | exit_status = app.run(sys.argv) 47 | sys.exit(exit_status) 48 | except Exception: 49 | print("Exception in user code:") 50 | print("-" * 80) 51 | traceback.print_exc(file=sys.stdout) 52 | print("-" * 80) 53 | 54 | # Try to close backend dbus daemon 55 | print("Closing backend D-Bus daemons") 56 | try: 57 | subprocess.call( 58 | "/usr/bin/dbus-send --system --print-reply " 59 | "--dest=org.baseurl.DnfSystem / org.baseurl.DnfSystem.Exit", 60 | shell=True, 61 | ) 62 | finally: 63 | sys.exit(1) 64 | -------------------------------------------------------------------------------- /src/yumex/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: iso-8859-1 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version..Win 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | import argparse 21 | import logging 22 | import sys 23 | 24 | import gi # noqa: F401 25 | from gi.repository import Gio, Gtk # isort:skip 26 | 27 | from yumex.common import CONFIG, dbus_dnfsystem, logger_setup 28 | from yumex.gui.window import Window 29 | 30 | logger = logging.getLogger("yumex") 31 | 32 | 33 | class YumexApplication(Gtk.Application): 34 | """Main application.""" 35 | 36 | def __init__(self): 37 | Gtk.Application.__init__( 38 | self, 39 | application_id="dk.yumex.yumex-ui", 40 | flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE, 41 | ) 42 | 43 | self.connect("activate", self.on_activate) 44 | self.connect("command-line", self.on_command_line) 45 | self.connect("shutdown", self.on_shutdown) 46 | self.running = False 47 | self.args = None 48 | self.dont_close = False 49 | self.window = None 50 | self.install_mode = False 51 | self.current_args = None 52 | 53 | def on_activate(self, app): 54 | if not self.running: 55 | self.window = Window( 56 | self, 57 | use_headerbar=CONFIG.conf.headerbar, 58 | install_mode=self.install_mode, 59 | ) 60 | app.add_window(self.window) 61 | self.running = True 62 | self.window.show() 63 | else: 64 | self.window.present() 65 | if self.install_mode and self.window.can_close(): 66 | self.window.rerun_installmode(self.current_args) 67 | 68 | def on_command_line(self, app, args): 69 | parser = argparse.ArgumentParser(prog="app") 70 | parser.add_argument("-d", "--debug", action="store_true") 71 | parser.add_argument( 72 | "-y", "--yes", action="store_true", help="Answer yes/ok to all questions" 73 | ) 74 | parser.add_argument( 75 | "--exit", 76 | action="store_true", 77 | help="tell dnfdaemon dbus services used by yumex to exit", 78 | ) 79 | parser.add_argument( 80 | "-I", "--install", type=str, metavar="PACKAGE", help="Install Package" 81 | ) 82 | parser.add_argument( 83 | "-R", "--remove", type=str, metavar="PACKAGE", help="Remove Package" 84 | ) 85 | parser.add_argument( 86 | "--updateall", action="store_true", help="apply all available updates" 87 | ) 88 | if not self.running: 89 | # First run 90 | self.args = parser.parse_args(args.get_arguments()[1:]) 91 | if self.args.exit: # kill dnf daemon and quit 92 | dbus_dnfsystem("Exit") 93 | sys.exit(0) 94 | 95 | if self.args.debug: 96 | logger_setup(loglvl=logging.DEBUG) 97 | else: 98 | logger_setup() 99 | if self.args.install or self.args.remove or self.args.updateall: 100 | self.install_mode = True 101 | else: 102 | # Second Run 103 | # parse cmdline in a non quitting way 104 | self.current_args = parser.parse_known_args(args.get_arguments()[1:])[0] 105 | if self.current_args.exit: 106 | if self.window.can_close(): 107 | self.quit() 108 | else: 109 | logger.info("Application is busy") 110 | if ( 111 | self.current_args.install 112 | or self.current_args.remove 113 | or self.current_args.updateall 114 | ): 115 | self.install_mode = True 116 | self.activate() 117 | return 0 118 | 119 | def on_shutdown(self, app): 120 | if self.window and not self.install_mode: 121 | CONFIG.conf.info_paned = self.window.main_paned.get_position() 122 | if self.window.cur_maximized: 123 | CONFIG.conf.win_maximized = True 124 | else: 125 | CONFIG.conf.win_width = self.window.cur_width 126 | CONFIG.conf.win_height = self.window.cur_height 127 | CONFIG.conf.win_maximized = False 128 | self.window.release_root_backend(quit_dnfdaemon=True) 129 | logger.info("Saving config on exit") 130 | CONFIG.write() 131 | return 0 132 | -------------------------------------------------------------------------------- /src/yumex/backend/__init__.py: -------------------------------------------------------------------------------- 1 | # Yum Exteder (yumex) - A graphic package management tool 2 | # Copyright (C) 2013-2014 Tim Lauridsen < timlaufedoraprojectorg > 3 | # 4 | # This program is free software; you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation; either version 2 of the License, or 7 | # (at your option) any later version. 8 | # 9 | # This program is distributed in the hope that it will be useful, 10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | # GNU General Public License for more details. 13 | # 14 | # You should have received a copy of the GNU General Public License 15 | # along with this program; if not, write to 16 | # the Free Software Foundation, Inc., 17 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 | 19 | import logging 20 | 21 | import yumex.common.const as const 22 | 23 | logger = logging.getLogger("yumex.backend") 24 | 25 | 26 | class Backend: 27 | """ 28 | Base package manager handling class 29 | it contains a cache for Package based objects, so we don't have 30 | to get the twice from the package manager. 31 | 32 | must be implemented in a sub class 33 | """ 34 | 35 | def __init__(self, frontend, filters=False): 36 | if filters: 37 | self.cache = PackageCacheWithFilters() 38 | else: 39 | self.cache = PackageCache() 40 | self.has_filters = filters 41 | self.frontend = frontend 42 | 43 | def get_filter(self, name): 44 | if self.has_filters: 45 | return self.cache.filters.get(name) 46 | else: 47 | return None 48 | 49 | def exception_handler(self, exc): 50 | """ 51 | send exceptions to the frontend 52 | """ 53 | self.frontend.exception_handler(exc) 54 | 55 | def get_packages(self, flt): 56 | """Get a list of Package objects based on a filter 57 | ('installed', 'available'...) 58 | """ 59 | pkgs = self.cache._get_packages(flt) # pylint: disable=protected-access 60 | return pkgs 61 | 62 | 63 | class BaseFilter: 64 | """Used as base for filters, there can filter a list of packages 65 | based on a different conditions 66 | """ 67 | 68 | def __init__(self, name, active=False): 69 | self.name = name 70 | self.active = active 71 | 72 | def run(self, pkgs): 73 | if not self.active: 74 | return pkgs 75 | 76 | def change(self, archs): 77 | pass 78 | 79 | def set_active(self, state): 80 | self.active = state 81 | 82 | 83 | class ArchFilter(BaseFilter): 84 | """ 85 | Arch Filter to filter a list of packages by arch 86 | """ 87 | 88 | def __init__(self, name, active=False): 89 | BaseFilter.__init__(self, name, active) 90 | self.archs = ["noarch", "i686", "x86_64"] 91 | 92 | def run(self, pkgs): 93 | BaseFilter.run(self, pkgs) 94 | filtered = [po for po in pkgs if po.arch in self.archs] 95 | return filtered 96 | 97 | def change(self, archs): 98 | self.archs = archs 99 | 100 | 101 | class Filters: 102 | """ 103 | Container to contain a number of filters based on the BaseFilter class 104 | """ 105 | 106 | def __init__(self): 107 | self._filters = {} 108 | 109 | def add(self, filter_cls): 110 | if filter_cls.name not in self._filters: 111 | self._filters[filter_cls.name] = filter_cls 112 | 113 | def delete(self, name): 114 | if name in self._filters: 115 | del self._filters[name] 116 | 117 | def run(self, pkgs): 118 | flt_pkgs = pkgs 119 | for flt in self._filters.values(): 120 | flt_pkgs = flt.run(flt_pkgs) 121 | return flt_pkgs 122 | 123 | def get(self, name): 124 | if name in self._filters: 125 | return self._filters[name] 126 | else: 127 | return None 128 | 129 | 130 | class PackageCache: 131 | """ 132 | Package cache to contain packages from backend, 133 | so we dont have get them more than once. 134 | """ 135 | 136 | def __init__(self): 137 | """ 138 | setup the cache 139 | """ 140 | for flt in const.ACTIONS_FILTER.values(): 141 | setattr(self, flt, set()) 142 | self._populated = [] 143 | self._index = {} 144 | 145 | def reset(self): 146 | """ 147 | reset the cache 148 | """ 149 | for flt in const.ACTIONS_FILTER.values(): 150 | setattr(self, flt, set()) 151 | self._populated = [] 152 | self._index = {} 153 | 154 | def _get_packages(self, pkg_filter): 155 | """ 156 | get a list of packages from the cache 157 | @param pkg_filter: the type of packages to get 158 | """ 159 | pkgs = list(getattr(self, str(pkg_filter))) 160 | return pkgs 161 | 162 | def is_populated(self, pkg_filter): 163 | return str(pkg_filter) in self._populated 164 | 165 | def populate(self, pkg_filter, pkgs): 166 | self.find_packages(pkgs) 167 | self._populated.append(str(pkg_filter)) 168 | 169 | def _add(self, po): 170 | if str(po) in self._index: # package is in cache 171 | return self._index[str(po)] 172 | else: 173 | target = getattr(self, const.ACTIONS_FILTER[po.action]) 174 | self._index[str(po)] = po 175 | target.add(po) 176 | return po 177 | 178 | # @TimeFunction 179 | def find_packages(self, packages): 180 | pkgs = [] 181 | i = 0 182 | if packages: 183 | for po in packages: 184 | i += 1 185 | pkgs.append(self._add(po)) 186 | return pkgs 187 | else: 188 | return [] 189 | 190 | 191 | class PackageCacheWithFilters(PackageCache): 192 | """Package cache to contain packages from backend, 193 | so we dont have get them more than once. 194 | This version has filtering, so we can filter packages by fx. arch 195 | """ 196 | 197 | def __init__(self): 198 | """ 199 | setup the cache 200 | """ 201 | PackageCache.__init__(self) 202 | self.filters = Filters() 203 | arch_flt = ArchFilter("arch") 204 | self.filters.add(arch_flt) 205 | 206 | def _get_packages(self, pkg_filter): 207 | """ 208 | get a list of packages from the cache 209 | @param pkg_filter: the type of packages to get 210 | """ 211 | pkgs = PackageCache._get_packages(self, str(pkg_filter)) 212 | pkgs = self.filters.run(pkgs) 213 | return pkgs 214 | 215 | # @TimeFunction 216 | def find_packages(self, packages): 217 | pkgs = PackageCache.find_packages(self, packages) 218 | pkgs = self.filters.run(pkgs) 219 | return pkgs 220 | -------------------------------------------------------------------------------- /src/yumex/base/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | # pylint: disable=no-member 20 | import datetime 21 | import logging 22 | import sys 23 | 24 | import yumex.common.const as const 25 | import yumex.common as common 26 | 27 | from yumex.common import CONFIG, _ 28 | 29 | from yumex.backend.dnf import DnfRootBackend 30 | from yumex.gui.dialogs import show_information 31 | 32 | logger = logging.getLogger("yumex.base") 33 | 34 | 35 | class BaseYumex: 36 | def __init__(self): 37 | self._root_backend = None 38 | self._root_locked = False 39 | self.is_working = False 40 | self.infobar = None 41 | 42 | def set_working(self, state, insensitive=True, splash=False): 43 | """Set the working state. (implement in subclass)""" 44 | raise NotImplementedError 45 | 46 | def _check_cache_expired(self, cache_type): 47 | time_fmt = "%Y-%m-%d %H:%M" 48 | now = datetime.datetime.now() 49 | refresh_period = datetime.timedelta(hours=CONFIG.conf.refresh_interval) 50 | # check if cache management is disabled 51 | if CONFIG.conf.refresh_interval == 0: 52 | return False 53 | if cache_type == "session": 54 | last_refresh = datetime.datetime.strptime( 55 | CONFIG.conf.session_refresh, time_fmt 56 | ) 57 | period = now - last_refresh 58 | logger.debug(f"time since last cache refresh : {period}") 59 | return period > refresh_period 60 | elif cache_type == "system": 61 | last_refresh = datetime.datetime.strptime( 62 | CONFIG.conf.system_refresh, time_fmt 63 | ) 64 | period = now - last_refresh 65 | logger.debug(f"time since last cache refresh : {period}") 66 | return period > refresh_period 67 | 68 | def _set_cache_refreshed(self, cache_type): 69 | time_fmt = "%Y-%m-%d %H:%M" 70 | now = datetime.datetime.now() 71 | now_str = now.strftime(time_fmt) 72 | if cache_type == "session": 73 | CONFIG.conf.session_refresh = now_str 74 | CONFIG.write() 75 | elif cache_type == "system": 76 | CONFIG.conf.system_refresh = now_str 77 | CONFIG.write() 78 | 79 | @property 80 | def backend(self): 81 | return self.get_root_backend() 82 | 83 | @common.exception_handler 84 | def reset_cache(self): 85 | logger.debug("Refresh system cache") 86 | self.set_working(True, True, splash=True) 87 | self.infobar.message(_("Refreshing Repository Metadata")) 88 | rc = self._root_backend.ExpireCache() 89 | self.set_working(False, splash=True) 90 | if rc: 91 | self._set_cache_refreshed("system") 92 | else: 93 | show_information(self, _("Could not refresh the DNF cache (root)")) 94 | 95 | @common.exception_handler 96 | def get_root_backend(self): 97 | """Get the current root backend. 98 | 99 | if it is not setup yet, the create it 100 | if it is not locked, then lock it 101 | """ 102 | if self._root_backend is None: 103 | self._root_backend = DnfRootBackend(self) 104 | if self._root_locked is False: 105 | logger.debug("Lock the DNF root daemon") 106 | locked, msg = self._root_backend.setup() 107 | errmsg = "" 108 | if locked: 109 | self._root_locked = True 110 | if self._check_cache_expired("system"): 111 | logger.debug("cache is expired, reloading") 112 | self.reset_cache() 113 | else: 114 | logger.critical("can't get root backend lock") 115 | if msg == "not-authorized": # user canceled the polkit dialog 116 | errmsg = _( 117 | "DNF root backend was not authorized.\n" 118 | "Yum Extender will exit" 119 | ) 120 | # DNF is locked by another process 121 | elif msg == "locked-by-other": 122 | errmsg = _( 123 | "DNF is locked by another process.\n\n" "Yum Extender will exit" 124 | ) 125 | self.error_dialog.show(errmsg) 126 | sys.exit(1) 127 | return self._root_backend 128 | 129 | @common.exception_handler 130 | def release_root_backend(self, quit_dnfdaemon=False): 131 | """Release the current root backend, if it is setup and locked.""" 132 | if self._root_backend is None: 133 | return 134 | if self._root_locked is True: 135 | logger.debug("Unlock the DNF root daemon") 136 | self._root_backend.Unlock() 137 | self._root_locked = False 138 | if quit_dnfdaemon: 139 | logger.debug("Exit the DNF root daemon") 140 | self._root_backend.Exit() 141 | 142 | def exception_handler(self, e): 143 | """Called if exception occours in methods with the 144 | @ExceptionHandler decorator. 145 | """ 146 | close = True 147 | msg = str(e) 148 | logger.error(f"BASE EXCEPTION : {msg}") 149 | err, errmsg = self._parse_error(msg) 150 | logger.debug(f"BASE err: [{err}] - msg: {errmsg}") 151 | if err == "LockedError": 152 | errmsg = "DNF is locked by another process.\n" "\nYum Extender will exit" 153 | close = False 154 | elif err == "NoReply": 155 | errmsg = "DNF D-Bus backend is not responding.\n" "\nYum Extender will exit" 156 | close = False 157 | if errmsg == "": 158 | errmsg = msg 159 | self.error_dialog.show(errmsg) 160 | 161 | # try to exit the backends, ignore errors 162 | if close: 163 | try: 164 | self.release_root_backend(quit_dnfdaemon=True) 165 | except Exception: # pylint: disable=broad-except 166 | pass 167 | sys.exit(1) 168 | 169 | def _parse_error(self, value): 170 | """Parse values from a DBus releated exception.""" 171 | res = const.DBUS_ERR_RE.match(str(value)) 172 | if res: 173 | err = res.groups()[0] 174 | err = err.split(".")[-1] 175 | msg = res.groups()[1] 176 | return err, msg 177 | return "", "" 178 | -------------------------------------------------------------------------------- /src/yumex/common/const.py: -------------------------------------------------------------------------------- 1 | # -*- coding: iso-8859-1 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | import os.path 21 | import re 22 | import subprocess 23 | import sys 24 | 25 | import hawkey 26 | 27 | from yumex.common import _ 28 | 29 | VERSION = "4.5.0" 30 | 31 | NEEDED_DAEMON_API = 2 # The needed dnfdaemon API version 32 | 33 | # find the data dir for resources 34 | BIN_PATH = os.path.abspath(os.path.dirname(sys.argv[0])) 35 | if BIN_PATH in ["/usr/bin", "/bin"]: 36 | DATA_DIR = "/usr/share/yumex-dnf" 37 | PIX_DIR = DATA_DIR + "/gfx" 38 | UI_DIR = DATA_DIR + "/ui" 39 | MISC_DIR = DATA_DIR 40 | THEME_DIR = DATA_DIR + "/themes" 41 | else: 42 | DATA_DIR = BIN_PATH 43 | PIX_DIR = DATA_DIR + "/../gfx" 44 | MISC_DIR = DATA_DIR + "/../misc" 45 | UI_DIR = DATA_DIR + "/../data/ui" 46 | THEME_DIR = DATA_DIR + "/../misc/themes" 47 | 48 | HOME_DIR = os.environ["HOME"] 49 | AUTOSTART_DIR = HOME_DIR + "/.config/autostart" 50 | USER_DESKTOP_FILE = AUTOSTART_DIR + "/yumex-dnf-updater.desktop" 51 | SYS_DESKTOP_FILE = MISC_DIR + "/yumex-dnf-updater.desktop" 52 | LEGACY_DESKTOP_FILE = AUTOSTART_DIR + "/yumex-dnf.desktop" 53 | 54 | ARCH = subprocess.check_output("/usr/bin/rpm --eval %_arch", shell=True).decode( 55 | "utf-8" 56 | )[:-1] 57 | 58 | ARCH_DICT = { 59 | "x86_64": {"x86_64", "i686", "i386", "noarch"}, 60 | "i386": {"i686", "i386", "noarch"}, 61 | "arm": {"armv7hl", "noarch"}, 62 | } 63 | 64 | # arch for this platform 65 | if ARCH in ARCH_DICT: 66 | PLATFORM_ARCH = ARCH_DICT[ARCH] 67 | else: # use x86_64 as fallback 68 | PLATFORM_ARCH = ARCH_DICT["x86_64"] 69 | 70 | DBUS_ERR_RE = re.compile(r".*GDBus.Error:([\w.]*): (.*)$") 71 | 72 | # Constants 73 | 74 | # Main UI stack names 75 | PAGE_PACKAGES = "packages" 76 | PAGE_QUEUE = "queue" 77 | PAGE_HISTORY = "history" 78 | PAGE_GROUPS = "groups" 79 | 80 | ACTIONS_FILTER = { 81 | "u": "updates", 82 | "i": "available", 83 | "r": "installed", 84 | "o": "obsoletes", 85 | "do": "downgrade", 86 | "ri": "reinstall", 87 | "li": "localinstall", 88 | } 89 | 90 | FILTER_ACTIONS = { 91 | "updates": "u", 92 | "available": "i", 93 | "installed": "r", 94 | "obsoletes": "o", 95 | "downgrade": "do", 96 | "reinstall": "ri", 97 | "localinstall": "li", 98 | "updates_all": "u", 99 | } 100 | 101 | BACKEND_ACTIONS = { 102 | "update": "u", 103 | "install": "i", 104 | "remove": "r", 105 | "obsolete": "o", 106 | "downgrade": "do", 107 | } 108 | 109 | QUEUE_PACKAGE_TYPES = { 110 | "i": "install", 111 | "u": "update", 112 | "r": "remove", 113 | "o": "obsolete", 114 | "ri": "reinstall", 115 | "do": "downgrade", 116 | "li": "localinstall", 117 | } 118 | 119 | # Package info filters (widget : info_xxxxxx) 120 | PKGINFO_FILTERS = ["desc", "updinfo", "changelog", "files", "deps"] 121 | 122 | # FIXME: The url should not be hardcoded 123 | BUGZILLA_URL = "https://bugzilla.redhat.com/show_bug.cgi?id=" 124 | FEDORA_PACKAGES_URL = "https://apps.fedoraproject.org/packages/" 125 | 126 | PACKAGE_LOAD_MSG = { 127 | "installed": _("Getting installed packages"), 128 | "available": _("Getting available packages"), 129 | "updates": _("Getting available updates"), 130 | "all": _("Getting all packages"), 131 | } 132 | 133 | HISTORY_NEW_STATES = ["Update", "Downgrade", "Obsoleting"] 134 | HISTORY_OLD_STATES = ["Updated", "Downgraded", "Obsoleted"] 135 | 136 | HISTORY_UPDATE_STATES = ["Update", "Downgrade", "Updated", "Downgraded"] 137 | 138 | HISTORY_SORT_ORDER = [ 139 | "Install", 140 | "True-Install", 141 | "Reinstall", 142 | "Update", 143 | "Downgrade", 144 | "Obsoleting", 145 | "Obsoleted", 146 | "Erase", 147 | "Removed", 148 | "Dep-Install", 149 | ] 150 | 151 | HISTORY_STATE_LABLES = { 152 | "Update": _("Updated packages"), 153 | "Downgrade": _("Downgraded packages"), 154 | "Obsoleting": _("Obsoleting packages"), 155 | "Obsoleted": _("Obsoleted packages"), 156 | "Erase": _("Erased packages"), 157 | "Removed": _("Removed packages"), 158 | "Install": _("Installed packages"), 159 | "True-Install": _("Installed packages"), 160 | "Dep-Install": _("Installed for dependencies"), 161 | "Reinstall": _("Reinstalled packages"), 162 | } 163 | 164 | TRANSACTION_RESULT_TYPES = { 165 | "install": _("Installing"), 166 | "update": _("Updating"), 167 | "remove": _("Removing"), 168 | "downgrade": _("Downgrading"), 169 | "reinstall": _("Replacing"), 170 | "weak-deps": _("Weak Dependencies"), 171 | } 172 | 173 | RPM_ACTIONS = { 174 | "update": _("Updating: %s"), 175 | "updated": _("Updated: %s"), 176 | "install": _("Installing: %s"), 177 | "reinstall": _("Reinstalling: %s"), 178 | "cleanup": _("Cleanup: %s"), 179 | "erase": _("Removing: %s"), 180 | "obsolete": _("Obsoleting: %s"), 181 | "downgrade": _("Downgrading: %s"), 182 | "verify": _("Verifying: %s"), 183 | "scriptlet": _("Running scriptlet for: %s"), 184 | } 185 | 186 | WIDGETS_INSENSITIVE = [ 187 | "header_menu", 188 | "header_filters", 189 | "header_search_options", 190 | "header_execute", 191 | "search", 192 | ] 193 | 194 | FEDORA_REPOS = ["fedora", "updates", "updates-testing", "rawhide"] 195 | 196 | ADVISORY_TYPES = { 197 | hawkey.ADVISORY_BUGFIX: _("Bugfix"), 198 | hawkey.ADVISORY_UNKNOWN: _("New Package"), 199 | hawkey.ADVISORY_SECURITY: _("Security"), 200 | hawkey.ADVISORY_ENHANCEMENT: _("Enhancement"), 201 | } 202 | -------------------------------------------------------------------------------- /src/yumex/gui/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | import os.path 21 | 22 | from gi.repository import Gtk 23 | import yumex.common.const as const 24 | 25 | 26 | def load_ui(ui_file): 27 | ui = Gtk.Builder() 28 | ui.set_translation_domain("yumex-dnf") 29 | ui.add_from_file(os.path.join(const.UI_DIR, ui_file)) 30 | return ui 31 | -------------------------------------------------------------------------------- /src/yumex/gui/dialogs/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | 21 | import logging 22 | 23 | from gi.repository import Gtk 24 | from yumex.common import _ 25 | 26 | logger = logging.getLogger("yumex.gui.dialogs") 27 | 28 | 29 | def show_information(window, msg, add_msg=None): 30 | dialog = Gtk.MessageDialog( 31 | flags=0, message_type=Gtk.MessageType.INFO, buttons=Gtk.ButtonsType.OK, text=msg 32 | ) 33 | if add_msg: 34 | dialog.format_secondary_text(add_msg) 35 | if window: 36 | dialog.set_transient_for(window) 37 | dialog.run() 38 | dialog.destroy() 39 | 40 | 41 | def yes_no_dialog(window, msg, add_msg=None): 42 | dialog = Gtk.MessageDialog( 43 | flags=0, 44 | message_type=Gtk.MessageType.INFO, 45 | buttons=Gtk.ButtonsType.YES_NO, 46 | text=msg, 47 | ) 48 | if add_msg: 49 | dialog.format_secondary_text(add_msg) 50 | if window: 51 | dialog.set_transient_for(window) 52 | rc = dialog.run() 53 | dialog.destroy() 54 | return rc == Gtk.ResponseType.YES 55 | 56 | 57 | def ask_for_gpg_import(window, values): 58 | (pkg_id, userid, hexkeyid, keyurl, _timestamp) = values 59 | pkg_name = pkg_id.split(",")[0] 60 | msg = _( 61 | " Do you want to import this GPG key\n" 62 | " needed to verify the %s package?\n\n" 63 | " Key : 0x%s:\n" 64 | ' Userid : "%s"\n' 65 | " From : %s" 66 | ) % (pkg_name, hexkeyid, userid, keyurl.replace("file://", "")) 67 | 68 | dialog = Gtk.MessageDialog( 69 | window, 0, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, msg 70 | ) 71 | rc = dialog.run() 72 | dialog.destroy() 73 | return rc == Gtk.ResponseType.YES 74 | -------------------------------------------------------------------------------- /src/yumex/gui/dialogs/aboutdialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | from gi.repository import Gtk 21 | 22 | import yumex.common.const as const 23 | 24 | 25 | class AboutDialog(Gtk.AboutDialog): 26 | def __init__(self, parent): 27 | Gtk.AboutDialog.__init__(self) 28 | self.props.program_name = "Yum Extender (dnf)" 29 | self.props.version = const.VERSION 30 | self.props.authors = ["Tim Lauridsen "] 31 | self.props.license_type = Gtk.License.GPL_2_0 32 | self.props.copyright = "(C) 2021 Tim Lauridsen" 33 | self.props.website = "https://www.yumex.dk" 34 | self.props.logo_icon_name = "yumex-dnf" 35 | self.set_transient_for(parent) 36 | -------------------------------------------------------------------------------- /src/yumex/gui/dialogs/errordialog.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | from gi.repository import Gtk 21 | from yumex.gui import load_ui 22 | 23 | 24 | class ErrorDialog: 25 | def __init__(self, base): 26 | self.ui = load_ui("errordialog.ui") 27 | self.dialog = self.ui.get_object("error_dialog") 28 | self.dialog.set_transient_for(base) 29 | self._buffer = self.ui.get_object("error_buffer") 30 | 31 | def show(self, txt): 32 | self._set_text(txt) 33 | self.dialog.show_all() 34 | rc = self.dialog.run() 35 | self.dialog.hide() 36 | self._buffer.set_text("") 37 | return rc == Gtk.ResponseType.CLOSE 38 | 39 | def _set_text(self, txt): 40 | self._buffer.set_text(txt) 41 | -------------------------------------------------------------------------------- /src/yumex/gui/dialogs/preferences.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import logging 3 | import os 4 | import shutil 5 | 6 | from gi.repository import Gtk 7 | import yumex.common.const as const 8 | from yumex.common import CONFIG, _ 9 | from yumex.gui import load_ui 10 | from yumex.gui.views.repoview import RepoView 11 | 12 | logger = logging.getLogger("yumex.gui.preffernces") 13 | 14 | 15 | class Preferences: 16 | 17 | VALUES = ["update_interval", "refresh_interval", "installonly_limit"] 18 | FLAGS = [ 19 | "autostart", 20 | "clean_unused", 21 | "newest_only", 22 | "headerbar", 23 | "auto_select_updates", 24 | "repo_saved", 25 | "clean_instonly", 26 | "use_dark", 27 | "search_visible", 28 | "show_splash", 29 | ] 30 | 31 | def __init__(self, base): 32 | self.base = base 33 | self.ui = load_ui("preferences.ui") 34 | self.dialog = self.ui.get_object("preferences") 35 | self.dialog.set_transient_for(base) 36 | self.repo_view = RepoView() 37 | widget = self.ui.get_object("repo_sw") 38 | widget.add(self.repo_view) 39 | self.repo_box = self.ui.get_object("box_repos") 40 | # track when repo page is active in stack 41 | self.repo_box.connect("map", self.on_repo_page_active) 42 | self.repos = [] 43 | 44 | def run(self): 45 | self.get_settings() 46 | self.dialog.show_all() 47 | rc = self.dialog.run() 48 | self.dialog.hide() 49 | need_reset = False 50 | if rc == Gtk.ResponseType.OK: 51 | need_reset = self.set_settings() 52 | return need_reset 53 | 54 | def on_repo_page_active(self, *_args): 55 | """Callback for ::map event there is called when repo page is active""" 56 | if not self.repos: 57 | self._load_repositories() 58 | 59 | def _load_repositories(self): 60 | """Lazy load repositories""" 61 | # get the repositories 62 | self.base.set_working(True, splash=True) 63 | self.base.infobar.message(_("Fetching repository information")) 64 | self.repos = self.base.backend.get_repositories() 65 | self.base.infobar.hide() 66 | self.repo_view.populate(self.repos) 67 | self.base.set_working(False, splash=True) 68 | if CONFIG.conf.repo_saved: 69 | self.repo_view.select_by_keys(CONFIG.session.enabled_repos) 70 | 71 | def get_themes(self): 72 | # Get Themes 73 | pattern = os.path.normpath(os.path.join(const.THEME_DIR, "*.theme")) 74 | theme_files = glob.glob(pattern) 75 | theme_names = [os.path.basename(theme).split(".")[0] for theme in theme_files] 76 | widget = self.ui.get_object("pref_theme") 77 | widget.remove_all() 78 | default = CONFIG.conf.theme.split(".")[0] 79 | i = 0 80 | ndx = 0 81 | for theme in sorted(theme_names): 82 | widget.append_text(theme) 83 | if theme == default: 84 | ndx = i 85 | i += 1 86 | widget.set_active(ndx) 87 | 88 | def get_settings(self): 89 | # set boolean states 90 | for option in Preferences.FLAGS: 91 | logger.debug(f"{option} : {getattr(CONFIG.conf, option)}") 92 | widget = self.ui.get_object("pref_" + option) 93 | widget.set_active(getattr(CONFIG.conf, option)) 94 | # cleanup installonly handler 95 | widget = self.ui.get_object("pref_clean_instonly") 96 | widget.connect("notify::active", self.on_clean_instonly) 97 | # Set value states 98 | for name in Preferences.VALUES: 99 | widget = self.ui.get_object("pref_" + name) 100 | widget.set_value(getattr(CONFIG.conf, name)) 101 | self.on_clean_instonly() 102 | # Get Themes 103 | self.get_themes() 104 | 105 | def on_clean_instonly(self, *_args): 106 | """Handler for clean_instonly switch""" 107 | widget = self.ui.get_object("pref_clean_instonly") 108 | state = widget.get_active() 109 | postfix = "installonly_limit" 110 | self._set_sensitive(postfix, state) 111 | 112 | def _set_sensitive(self, postfix, state): 113 | for prefix in ["pref_", "label_"]: 114 | id_ = prefix + postfix 115 | if state: 116 | self.ui.get_object(id_).set_sensitive(True) 117 | else: 118 | self.ui.get_object(id_).set_sensitive(False) 119 | 120 | def set_settings(self): 121 | changed = False 122 | need_reset = False 123 | # handle boolean options 124 | for option in Preferences.FLAGS: 125 | widget = self.ui.get_object("pref_" + option) 126 | state = widget.get_active() 127 | if state != getattr(CONFIG.conf, option): # changed ?? 128 | setattr(CONFIG.conf, option, state) 129 | changed = True 130 | self.handle_setting(option, state) 131 | # handle value options 132 | for name in Preferences.VALUES: 133 | widget = self.ui.get_object("pref_" + name) 134 | value = widget.get_value_as_int() 135 | if value != getattr(CONFIG.conf, name): # changed ?? 136 | setattr(CONFIG.conf, name, value) 137 | changed = True 138 | # handle repos, if the repositories has been loaded 139 | if self.repos: 140 | repo_before = CONFIG.session.enabled_repos 141 | repo_now = self.repo_view.get_selected() 142 | # repo selection changed 143 | if repo_now != repo_before: 144 | CONFIG.session.enabled_repos = repo_now # set the new selection 145 | # we need to reset the gui 146 | need_reset = True 147 | if CONFIG.conf.repo_saved: 148 | CONFIG.conf.repo_enabled = repo_now 149 | changed = True 150 | # Themes 151 | widget = self.ui.get_object("pref_theme") 152 | default = CONFIG.conf.theme.split(".")[0] 153 | theme = widget.get_active_text() 154 | if theme != default: 155 | CONFIG.conf.theme = f"{theme}.theme" 156 | self.base.load_custom_styling() 157 | changed = True 158 | if changed: 159 | CONFIG.write() 160 | return need_reset 161 | 162 | def handle_setting(self, option, state): 163 | if option == "autostart": 164 | if state: # create an autostart .desktop for current user 165 | if not os.path.isdir(const.AUTOSTART_DIR): 166 | logger.info(f"creating autostart directory : {const.AUTOSTART_DIR}") 167 | os.makedirs(const.AUTOSTART_DIR, 0o700) 168 | shutil.copy(const.SYS_DESKTOP_FILE, const.USER_DESKTOP_FILE) 169 | else: # remove the autostart file 170 | if os.path.exists(const.USER_DESKTOP_FILE): 171 | os.unlink(const.USER_DESKTOP_FILE) 172 | -------------------------------------------------------------------------------- /src/yumex/gui/dialogs/progresssplash.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | import yumex.common.const as const 21 | from yumex.gui import load_ui 22 | 23 | 24 | class ProgressSplash: 25 | def __init__(self, base): 26 | self.base = base 27 | self.ui = load_ui("progresssplash.ui") 28 | self.win = self.ui.get_object("win_working") 29 | pix = self.ui.get_object("work_pix") 30 | pix_file = f"{const.PIX_DIR}/progress.gif" 31 | pix.set_from_file(pix_file) 32 | self.label = self.ui.get_object("work_label") 33 | self.sublabel = self.ui.get_object("work_sublabel") 34 | self.win.set_transient_for(self.base) 35 | 36 | def show(self): 37 | self.win.show() 38 | 39 | def hide(self): 40 | self.win.hide() 41 | 42 | def set_label(self, text): 43 | self.label.set_text(text) 44 | 45 | def set_sublabel(self, text): 46 | self.sublabel.set_text(text) 47 | -------------------------------------------------------------------------------- /src/yumex/gui/dialogs/transactionresult.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | from gi.repository import Gtk, GObject 21 | import yumex.common.const as const 22 | import yumex.common 23 | from yumex.common import _, format_number 24 | from yumex.gui import load_ui 25 | 26 | 27 | class TransactionResult: 28 | def __init__(self, base): 29 | self.base = base 30 | self.ui = load_ui("transactionresult.ui") 31 | self.dialog = self.ui.get_object("transaction-results") 32 | self.dialog.set_transient_for(base) 33 | self.view = self.ui.get_object("result_view") 34 | self.store = self.setup_view(self.view) 35 | 36 | def run(self): 37 | self.dialog.show_all() 38 | rc = self.dialog.run() 39 | self.dialog.hide() 40 | return rc == Gtk.ResponseType.OK 41 | 42 | def clear(self): 43 | self.store.clear() 44 | 45 | def setup_view(self, view): 46 | """ 47 | Setup the TreeView 48 | @param view: the TreeView widget 49 | """ 50 | model = Gtk.TreeStore( 51 | GObject.TYPE_STRING, 52 | GObject.TYPE_STRING, 53 | GObject.TYPE_STRING, 54 | GObject.TYPE_STRING, 55 | GObject.TYPE_STRING, 56 | ) 57 | view.set_model(model) 58 | self.create_text_column(_("Name"), view, 0, size=250) 59 | self.create_text_column(_("Arch"), view, 1) 60 | self.create_text_column(_("Ver"), view, 2) 61 | self.create_text_column(_("Repository"), view, 3, size=100) 62 | self.create_text_column(_("Size"), view, 4) 63 | return model 64 | 65 | def create_text_column(self, hdr, view, colno, size=None): 66 | """ 67 | Create at TreeViewColumn 68 | @param hdr: column header text 69 | @param view: the TreeView widget 70 | @param colno: the TreeStore column containing data for the column 71 | @param size: the min column view (optional) 72 | """ 73 | cell = Gtk.CellRendererText() # Size Column 74 | column = Gtk.TreeViewColumn(hdr, cell, markup=colno) 75 | column.set_resizable(True) 76 | if size: 77 | column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) 78 | column.set_fixed_width(size) 79 | view.append_column(column) 80 | 81 | def populate(self, pkglist, dnl_size): 82 | """ 83 | Populate the TreeView with data 84 | @param pkglist: list containing view data 85 | @param dnl_size: 86 | """ 87 | model = self.store 88 | self.store.clear() 89 | total_size = 0 90 | for sub, lvl1 in pkglist: 91 | label = f"{const.TRANSACTION_RESULT_TYPES[sub]}" 92 | level1 = model.append(None, [label, "", "", "", ""]) 93 | for pkgid, size, replaces in lvl1: 94 | (n, _, v, r, a, repo_id) = str(pkgid).split(",") 95 | level2 = model.append( 96 | level1, [n, a, f"{v}.{r}", repo_id, format_number(size)] 97 | ) 98 | # packages there need to be downloaded 99 | if sub in [ 100 | "install", 101 | "update", 102 | "install-deps", 103 | "update-deps", 104 | "obsoletes", 105 | ]: 106 | total_size += size 107 | for r in replaces: 108 | (n, _, v, r, a, repo_id) = str(r).split(",") 109 | action = yumex.common._("replacing") 110 | model.append( 111 | level2, 112 | [ 113 | f"{action} {n}", 114 | a, 115 | f"{v}.{r}", 116 | repo_id, 117 | format_number(size), 118 | ], 119 | ) 120 | self.ui.get_object("result_size").set_text(format_number(total_size)) 121 | self.view.expand_all() 122 | -------------------------------------------------------------------------------- /src/yumex/gui/views/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | """yumex.gui.views module""" 20 | -------------------------------------------------------------------------------- /src/yumex/gui/views/groupview.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | import logging 21 | import os 22 | 23 | from gi.repository import Gtk, GObject, GdkPixbuf 24 | 25 | logger = logging.getLogger("yumex.gui.views") 26 | 27 | 28 | class Group: 29 | """Object to represent a dnf group/category""" 30 | 31 | def __init__(self, grpid, grp_name, grp_desc, inst, is_category=False): 32 | self.id = grpid 33 | self.name = grp_name 34 | self.description = grp_desc 35 | self.installed = inst 36 | self.category = is_category 37 | self.selected = False 38 | 39 | 40 | class GroupView(Gtk.TreeView): 41 | __gsignals__ = { 42 | "group-changed": (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_STRING,)) 43 | } 44 | 45 | def __init__(self, qview, base): 46 | Gtk.TreeView.__init__(self) 47 | self.base = base 48 | self.model = self.setup_view() 49 | self.queue = qview.queue 50 | self.queue_view = qview 51 | self.current_category = None 52 | self._groups = None 53 | self.selected_group = None 54 | self.connect("cursor-changed", self.on_cursor_changed) 55 | 56 | def setup_view(self): 57 | """Setup Group View""" 58 | model = Gtk.TreeStore(GObject.TYPE_PYOBJECT) 59 | 60 | self.set_model(model) 61 | column = Gtk.TreeViewColumn(None, None) 62 | # Selection checkbox 63 | selection = Gtk.CellRendererToggle() # Selection 64 | selection.set_property("activatable", True) 65 | column.pack_start(selection, False) 66 | column.set_cell_data_func(selection, self.set_checkbox) 67 | selection.connect("toggled", self.on_toggled) 68 | self.append_column(column) 69 | column = Gtk.TreeViewColumn(None, None) 70 | # Queue Status (install/remove group) 71 | state = Gtk.CellRendererPixbuf() # Queue Status 72 | state.set_property("stock-size", 1) 73 | column.pack_start(state, False) 74 | column.set_cell_data_func(state, self.queue_pixbuf) 75 | 76 | # category/group icons 77 | icon = Gtk.CellRendererPixbuf() 78 | icon.set_property("stock-size", 1) 79 | column.pack_start(icon, False) 80 | column.set_cell_data_func(icon, self.grp_pixbuf) 81 | 82 | category = Gtk.CellRendererText() 83 | column.pack_start(category, False) 84 | column.set_cell_data_func(category, self.get_data_text, "name") 85 | 86 | self.append_column(column) 87 | self.set_headers_visible(False) 88 | return model 89 | 90 | def get_data_text(self, column, cell, model, iterator, prop): 91 | """property function to get string data from a object in the 92 | TreeStore based on an attributes key 93 | """ 94 | obj = model.get_value(iterator, 0) 95 | if obj: 96 | cell.set_property("text", getattr(obj, prop)) 97 | 98 | def set_checkbox(self, column, cell, model, iterator, data=None): 99 | obj = model.get_value(iterator, 0) 100 | if obj: 101 | if obj.category: 102 | cell.set_property("visible", False) 103 | else: 104 | cell.set_property("visible", True) 105 | cell.set_property("active", obj.selected) 106 | 107 | def on_toggled(self, widget, path): 108 | """Group selection handler""" 109 | iterator = self.model.get_iter(path) 110 | obj = self.model.get_value(iterator, 0) 111 | action = self.queue.has_group(obj) 112 | if action: # Group is in the queue, remove it from the queue 113 | self.queue.remove_group(obj, action) 114 | else: 115 | if obj.installed: # Group is installed add it to queue for removal 116 | self.queue.add_group(obj, "r") # Add for remove 117 | else: # Group is not installed, add it to queue for installation 118 | self.queue.add_group(obj, "i") # Add for install 119 | self.queue_view.refresh() 120 | 121 | def on_cursor_changed(self, widget): 122 | """ 123 | a new group is selected in group view 124 | """ 125 | if widget.get_selection(): 126 | (model, iterator) = widget.get_selection().get_selected() 127 | if model is not None and iterator is not None: 128 | obj = self.model.get_value(iterator, 0) 129 | if not obj.category and obj.id != self.selected_group: 130 | self.selected_group = obj.id 131 | # send the group-changed signal 132 | self.emit("group-changed", obj.id) 133 | 134 | def populate(self, data): 135 | self.freeze_child_notify() 136 | self.set_model(None) 137 | self.model.clear() 138 | self._groups = data 139 | self.set_model(self.model) 140 | for cat, catgrps in data: 141 | # cat: [category_id, category_name, category_desc] 142 | (catid, name, desc) = cat 143 | obj = Group(catid, name, desc, False, True) 144 | node = self.model.append(None, [obj]) 145 | for grp in catgrps: 146 | # [group_id, group_name, group_desc, group_is_installed] 147 | (grpid, grp_name, grp_desc, inst) = grp 148 | obj = Group(grpid, grp_name, grp_desc, inst, False) 149 | self.model.append(node, [obj]) 150 | self.thaw_child_notify() 151 | self.selected_group = None 152 | 153 | def queue_pixbuf(self, column, cell, model, iterator, data=None): 154 | """ 155 | Cell Data function for 156 | """ 157 | obj = model.get_value(iterator, 0) 158 | if not obj.category: 159 | action = self.queue.has_group(obj.id) 160 | icon = "non-starred-symbolic" 161 | if obj.installed: 162 | icon = "starred" 163 | if action: 164 | if action == "i": 165 | icon = "network-server" 166 | else: 167 | icon = "edit-delete" 168 | cell.set_property("icon-name", icon) 169 | cell.set_property("visible", True) 170 | else: 171 | cell.set_property("visible", False) 172 | 173 | def grp_pixbuf(self, column, cell, model, iterator, data=None): 174 | """ 175 | Cell Data function for recent Column, shows pixmap 176 | if recent Value is True. 177 | """ 178 | obj = model.get_value(iterator, 0) 179 | pix = None 180 | filename = f"/usr/share/pixmaps/comps/{obj.id}.png" 181 | if os.access(filename, os.R_OK): 182 | pix = self._get_pix(filename) 183 | else: # Try to get the parent icon 184 | parent = model.iter_parent(iterator) 185 | if parent: 186 | cat_id = model[parent][0].id # get the parent cat_id 187 | filename = f"/usr/share/pixmaps/comps/{cat_id}.png" 188 | if os.access(filename, os.R_OK): 189 | pix = self._get_pix(filename) 190 | if pix: 191 | cell.set_property("visible", True) 192 | cell.set_property("pixbuf", pix) 193 | else: 194 | cell.set_property("visible", False) 195 | 196 | def _get_pix(self, filename): 197 | """ 198 | Get a pix buffer from a file, resize it to 24 px, if needed 199 | @param fn: 200 | """ 201 | imgsize = 24 202 | pix = GdkPixbuf.Pixbuf.new_from_file(filename) 203 | if pix.get_height() != imgsize or pix.get_width() != imgsize: 204 | pix = pix.scale_simple(imgsize, imgsize, GdkPixbuf.INTERP_BILINEAR) 205 | return pix 206 | -------------------------------------------------------------------------------- /src/yumex/gui/views/historypackageview.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | import logging 21 | 22 | from gi.repository import Gtk 23 | import yumex.common.const as const 24 | from yumex.common import _, CONFIG, pkg_id_to_full_name 25 | 26 | logger = logging.getLogger("yumex.gui.views") 27 | 28 | 29 | class HistoryPackageView(Gtk.TreeView): 30 | """History Package View Class""" 31 | 32 | def __init__(self, base): 33 | Gtk.TreeView.__init__(self) 34 | self.model = self.setup_view() 35 | self.base = base 36 | 37 | def setup_view(self): 38 | """Create Notebook list for single page""" 39 | model = Gtk.TreeStore(str) 40 | self.set_model(model) 41 | cell = Gtk.CellRendererText() 42 | column = Gtk.TreeViewColumn(_("History Packages"), cell, markup=0) 43 | column.set_resizable(True) 44 | # column1.set_fixed_width(200) 45 | self.append_column(column) 46 | # model.set_sort_column_id(0, Gtk.SortType.ASCENDING) 47 | return model 48 | 49 | def reset(self): 50 | self.model.clear() 51 | 52 | def populate(self, data): 53 | self.model.clear() 54 | # Order by package name.arch 55 | names = {} 56 | names_pair = {} 57 | for elem in data: 58 | pkg_id, state, is_inst = elem 59 | (name, _, _, _, arch, _) = str(pkg_id).split(",") 60 | name_arch = f"{name}.{arch}" 61 | if state in const.HISTORY_UPDATE_STATES: # part of a pair 62 | if name_arch in names_pair: 63 | # this is the updating pkg 64 | if state in const.HISTORY_NEW_STATES: 65 | names_pair[name_arch].insert(0, elem) # add first in list 66 | else: 67 | names_pair[name_arch].append(elem) 68 | else: 69 | names_pair[name_arch] = [elem] 70 | else: 71 | names[name_arch] = [elem] 72 | 73 | # order by primary state 74 | states = {} 75 | # pkgs without relatives 76 | for name_arch in sorted(list(names)): 77 | pkg_list = names[name_arch] 78 | pkg_id, state, is_inst = pkg_list[ 79 | 0 80 | ] # Get first element (the primary (new) one ) 81 | if state in states: 82 | states[state].append(pkg_list) 83 | else: 84 | states[state] = [pkg_list] 85 | # pkgs with releatives 86 | for name_arch in sorted(list(names_pair)): 87 | pkg_list = names_pair[name_arch] 88 | pkg_id, state, is_inst = pkg_list[ 89 | 0 90 | ] # Get first element (the primary (new) one ) 91 | if state in states: 92 | states[state].append(pkg_list) 93 | else: 94 | states[state] = [pkg_list] 95 | # apply packages to model in right order 96 | for state in const.HISTORY_SORT_ORDER: 97 | if state in states: 98 | num = len(states[state]) 99 | cat = self.model.append( 100 | None, [f"{const.HISTORY_STATE_LABLES[state]} ({num})"] 101 | ) 102 | for pkg_list in states[state]: 103 | pkg_id, _, is_inst = pkg_list[0] 104 | if is_inst: 105 | color = CONFIG.conf.color_install 106 | fullname = pkg_id_to_full_name(pkg_id) 107 | name = f'{fullname}' 108 | else: 109 | name = pkg_id_to_full_name(pkg_id) 110 | pkg_cat = self.model.append(cat, [name]) 111 | if len(pkg_list) == 2: 112 | pkg_id, _, is_inst = pkg_list[1] 113 | name = pkg_id_to_full_name(pkg_id) 114 | self.model.append(pkg_cat, [name]) 115 | self.expand_all() 116 | -------------------------------------------------------------------------------- /src/yumex/gui/views/historyview.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | import logging 21 | 22 | from gi.repository import Gtk 23 | from yumex.common import _ 24 | from yumex.gui.views.historypackageview import HistoryPackageView 25 | 26 | logger = logging.getLogger("yumex.gui.views") 27 | 28 | 29 | class HistoryView(Gtk.TreeView): 30 | """History View Class""" 31 | 32 | def __init__(self, base): 33 | Gtk.TreeView.__init__(self) 34 | self.model = self.setup_view() 35 | self.base = base 36 | self.pkg_view = HistoryPackageView(self.base) 37 | self.connect("cursor-changed", self.on_cursor_changed) 38 | self.is_populated = False 39 | 40 | def setup_view(self): 41 | """Create Notebook list for single page""" 42 | model = Gtk.TreeStore(str, int) 43 | self.set_model(model) 44 | cell1 = Gtk.CellRendererText() 45 | column1 = Gtk.TreeViewColumn(_("History (Date/Time)"), cell1, markup=0) 46 | column1.set_resizable(False) 47 | column1.set_fixed_width(200) 48 | self.append_column(column1) 49 | model.set_sort_column_id(0, Gtk.SortType.DESCENDING) 50 | return model 51 | 52 | def reset(self): 53 | self.model.clear() 54 | self.is_populated = False 55 | self.pkg_view.reset() 56 | 57 | def populate(self, data): 58 | self.pkg_view.reset() 59 | self.model.clear() 60 | main = {} 61 | for tid, date_time in data: 62 | date, time = date_time.split("T") 63 | year, month, day = date.split("-") 64 | # year 65 | if year not in main: 66 | ycat = self.model.append(None, [year, -1]) 67 | main[year] = (ycat, {}) 68 | ycat, mdict = main[year] 69 | # month 70 | if month not in mdict: 71 | mcat = self.model.append(ycat, [month, -1]) 72 | mdict[month] = (mcat, {}) 73 | mcat, ddict = mdict[month] 74 | # day 75 | if day not in ddict: 76 | dcat = self.model.append(mcat, [day, -1]) 77 | ddict[day] = dcat 78 | dcat = ddict[day] 79 | self.model.append(dcat, [time, tid]) 80 | self.collapse_all() 81 | path = Gtk.TreePath.new_from_string("0:0:0:0") 82 | self.expand_to_path(path) 83 | self.get_selection().select_path(path) 84 | self.on_cursor_changed(self) 85 | self.is_populated = True 86 | 87 | def on_cursor_changed(self, widget): 88 | """ 89 | a new History element is selected in history view 90 | """ 91 | if widget.get_selection(): 92 | (model, iterator) = widget.get_selection().get_selected() 93 | if model is not None and iterator is not None: 94 | tid = model.get_value(iterator, 1) 95 | if tid != -1: 96 | pkgs = self.base.get_root_backend().GetHistoryPackages(tid) 97 | self.pkg_view.populate(pkgs) 98 | 99 | def get_selected(self): 100 | """Return the currently selected history tid""" 101 | if self.get_selection(): 102 | (model, iterator) = self.get_selection().get_selected() 103 | if model is not None and iterator is not None: 104 | tid = model.get_value(iterator, 1) 105 | return tid 106 | else: 107 | return 0 108 | -------------------------------------------------------------------------------- /src/yumex/gui/views/packagequeue.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | import logging 21 | 22 | import yumex.common.const as const 23 | 24 | logger = logging.getLogger("yumex.gui.views") 25 | 26 | 27 | class PackageQueue: 28 | """ 29 | A Queue class to store selected packages/groups and the pending actions 30 | """ 31 | 32 | def __init__(self): 33 | self.packages = {} 34 | self._setup_packages() 35 | self.groups = {"i": {}, "r": {}} 36 | self._name_arch_index = {} 37 | 38 | def _setup_packages(self): 39 | for key in const.QUEUE_PACKAGE_TYPES: 40 | self.packages[key] = [] 41 | 42 | def clear(self): 43 | del self.packages 44 | self.packages = {} 45 | self._setup_packages() 46 | self.groups = {"i": {}, "r": {}} 47 | self._name_arch_index = {} 48 | 49 | def get(self, action=None): 50 | if action is None: 51 | return self.packages 52 | else: 53 | return self.packages[action] 54 | 55 | def total(self): 56 | num = 0 57 | for key in const.QUEUE_PACKAGE_TYPES: 58 | num += len(self.packages[key]) 59 | num += len(self.groups["i"].keys()) 60 | num += len(self.groups["r"].keys()) 61 | return num 62 | 63 | def add(self, pkg, action=None): 64 | """Add a package to queue""" 65 | if not action: 66 | action = pkg.action 67 | name_arch = f"{pkg.name}.{pkg.arch}" 68 | if pkg not in self.packages[action] and name_arch not in self._name_arch_index: 69 | self.packages[action].append(pkg) 70 | self._name_arch_index[name_arch] = 1 71 | 72 | def remove(self, pkg, action=None): 73 | """Remove package from queue""" 74 | if not action: 75 | action = pkg.action 76 | name_arch = f"{pkg.name}.{pkg.arch}" 77 | if pkg in self.packages[action]: 78 | self.packages[action].remove(pkg) 79 | del self._name_arch_index[name_arch] 80 | 81 | def has_pkg_with_name_arch(self, pkg): 82 | name_arch = f"{pkg.name}.{pkg.arch}" 83 | return name_arch in self._name_arch_index 84 | 85 | def add_group(self, grp, action): 86 | """ 87 | 88 | @param grp: Group object 89 | @param action: 90 | """ 91 | logger.debug(f"add_group : {grp.id} - {action}") 92 | grps = self.groups[action] 93 | if grp.id not in grps: 94 | grps[grp.id] = grp 95 | grp.selected = True 96 | 97 | def remove_group(self, grp, action): 98 | """ 99 | 100 | @param grp: Group object 101 | @param action: 102 | """ 103 | logger.debug(f"remove_group : {grp.id} - {action}") 104 | grps = self.groups[action] 105 | if grp.id in grps: 106 | del grps[grp.id] 107 | grp.selected = False 108 | 109 | def remove_all_groups(self): 110 | """ 111 | remove all groups from queue 112 | """ 113 | for action in ("i", "r"): 114 | for grp in self.groups[action]: 115 | self.remove_group(grp, action) 116 | 117 | def remove_groups(self, group_names): 118 | """ 119 | remove groups from queue based on list of grp_ids 120 | """ 121 | for action in ("i", "r"): 122 | new_dict = {} 123 | grps = self.groups[action] 124 | for grp in grps.values(): 125 | if grp.name not in group_names: 126 | new_dict[grp.id] = grp # copy to new dict 127 | else: # unselect the group object 128 | grp.selected = False 129 | self.groups[action] = new_dict 130 | 131 | def has_group(self, grp_id): 132 | """check if group is in package queue""" 133 | for action in ["i", "r"]: 134 | grps = self.groups[action] 135 | if grp_id in grps: 136 | return action 137 | return None 138 | 139 | def get_groups(self): 140 | """get (grp_id, action) generator""" 141 | for action in ("i", "r"): 142 | for grp in self.groups[action].values(): 143 | yield grp.id, action 144 | -------------------------------------------------------------------------------- /src/yumex/gui/views/queueview.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | import logging 21 | 22 | from gi.repository import GObject, Gtk 23 | 24 | import yumex.common.const as const 25 | from yumex.common import _, ngettext 26 | from yumex.gui.views.packagequeue import PackageQueue 27 | 28 | logger = logging.getLogger("yumex.gui.views") 29 | 30 | 31 | class QueueView(Gtk.TreeView): 32 | __gsignals__ = { 33 | "queue-refresh": (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_INT,)) 34 | } 35 | 36 | def __init__(self, queue_menu): 37 | Gtk.TreeView.__init__(self) 38 | self.store = self._setup_model() 39 | self.queue = PackageQueue() 40 | self.queue_menu = queue_menu 41 | self.connect("button-press-event", self.on_queue_view_button_press) 42 | remove_menu = self.queue_menu.get_children()[ 43 | 0 44 | ] # get the first child (remove menu) 45 | remove_menu.connect("activate", self.delete_selected) 46 | 47 | def _setup_model(self): 48 | """ 49 | Setup the model and view 50 | """ 51 | model = Gtk.TreeStore(GObject.TYPE_STRING, GObject.TYPE_STRING) 52 | self.set_model(model) 53 | cell1 = Gtk.CellRendererText() 54 | column1 = Gtk.TreeViewColumn(_("Packages"), cell1, markup=0) 55 | column1.set_resizable(True) 56 | self.append_column(column1) 57 | 58 | cell2 = Gtk.CellRendererText() 59 | column2 = Gtk.TreeViewColumn(_("Summary"), cell2, text=1) 60 | column2.set_resizable(True) 61 | self.append_column(column2) 62 | model.set_sort_column_id(0, Gtk.SortType.ASCENDING) 63 | self.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE) 64 | return model 65 | 66 | def delete_selected(self, widget=None): 67 | rmvlist = [] 68 | model, paths = self.get_selection().get_selected_rows() 69 | for path in paths: 70 | row = model[path] 71 | if row.parent is not None: 72 | rmvlist.append(row[0]) 73 | for pkg in self.filter_pkgs_from_list(rmvlist): 74 | self.queue.remove(pkg) 75 | if pkg.queued == "do" and pkg.installed: 76 | pkg.downgrade_po.queued = None 77 | pkg.downgrade_po.set_select(not pkg.selected) 78 | pkg.action = "r" # reset action type of installed package 79 | pkg.queued = None 80 | pkg.set_select(not pkg.selected) 81 | self.queue.remove_groups(rmvlist) 82 | self.refresh() 83 | 84 | def on_queue_view_button_press(self, treeview, event): 85 | """ 86 | Mouse button clicked in package view handler 87 | :param _treeview: 88 | :param event: 89 | """ 90 | if event.button == 3: # Right Click 91 | popup = self.queue_menu 92 | popup.popup(None, None, None, None, event.button, event.time) 93 | return True 94 | 95 | def filter_pkgs_from_list(self, rlist): 96 | """ 97 | return packages in queue where str(pkg) is in a list 98 | @param rlist: 99 | """ 100 | rclist = [] 101 | for action in const.QUEUE_PACKAGE_TYPES: 102 | pkg_list = self.queue.packages[action] 103 | if pkg_list: 104 | rclist.extend([x for x in pkg_list if str(x) in rlist]) 105 | return rclist 106 | 107 | def refresh(self): 108 | """Populate view with data from queue""" 109 | self.store.clear() 110 | pkg_list = self.queue.packages["u"] + self.queue.packages["o"] 111 | text = ngettext("Package to update", "Packages to update", len(pkg_list)) 112 | label = f"{text}" 113 | if len(pkg_list) > 0: 114 | self.populate_list(label, pkg_list) 115 | pkg_list = self.queue.packages["i"] 116 | text = ngettext("Package to install", "Packages to install", len(pkg_list)) 117 | label = f"{text}" 118 | if len(pkg_list) > 0: 119 | self.populate_list(label, pkg_list) 120 | pkg_list = self.queue.packages["r"] 121 | text = ngettext("Package to remove", "Packages to remove", len(pkg_list)) 122 | label = f"{text}" 123 | if len(pkg_list) > 0: 124 | self.populate_list(label, pkg_list) 125 | pkg_list = self.queue.packages["ri"] 126 | text = ngettext("Package to reinstall", "Packages to reinstall", len(pkg_list)) 127 | label = f"{text}" 128 | if len(pkg_list) > 0: 129 | self.populate_list(label, pkg_list) 130 | pkg_list = self.queue.packages["li"] 131 | text = ngettext("RPM file to install", "RPM files to install", len(pkg_list)) 132 | label = f"{text}" 133 | if len(pkg_list) > 0: 134 | self.populate_list(label, pkg_list) 135 | grps = self.queue.groups["i"] 136 | text = ngettext("Group to install", "Groups to install", len(pkg_list)) 137 | label = f"{text}" 138 | if len(grps) > 0: 139 | self.populate_group_list(label, grps) 140 | grps = self.queue.groups["r"] 141 | text = ngettext("Group to remove", "Groups to remove", len(pkg_list)) 142 | label = f"{text}" 143 | if len(grps) > 0: 144 | self.populate_group_list(label, grps) 145 | self.populate_list_downgrade() 146 | self.expand_all() 147 | self.emit("queue-refresh", self.queue.total()) 148 | 149 | def populate_list(self, label, pkg_list): 150 | parent = self.store.append(None, [label, ""]) 151 | for pkg in pkg_list: 152 | self.store.append(parent, [str(pkg), pkg.summary]) 153 | 154 | def populate_group_list(self, label, grps): 155 | parent = self.store.append(None, [label, ""]) 156 | for grp in grps.values(): 157 | self.store.append(parent, [grp.name, grp.description]) 158 | 159 | def populate_list_downgrade(self): 160 | pkg_list = self.queue.packages["do"] 161 | text = ngettext("Package to downgrade", "Packages to downgrade", len(pkg_list)) 162 | label = f"{text}" 163 | if len(pkg_list) > 0: 164 | parent = self.store.append(None, [label, ""]) 165 | for pkg in pkg_list: 166 | item = self.store.append(parent, [str(pkg.downgrade_po), pkg.summary]) 167 | self.store.append(item, [_("Downgrade to %s ") % str(pkg), ""]) 168 | -------------------------------------------------------------------------------- /src/yumex/gui/views/repoview.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | import logging 21 | 22 | from gi.repository import Gtk 23 | from yumex.common import _ 24 | from yumex.gui.views.selectionview import SelectionView 25 | 26 | logger = logging.getLogger("yumex.gui.views") 27 | 28 | 29 | class RepoView(SelectionView): 30 | """ 31 | This class controls the repo TreeView 32 | """ 33 | 34 | def __init__(self): 35 | SelectionView.__init__(self) 36 | self.headers = [_("Repository"), _("Filename")] 37 | self.store = self.setup_view() 38 | self.state = "normal" 39 | self._last_selected = [] 40 | 41 | def on_toggled(self, widget, path): 42 | """Repo select/unselect handler""" 43 | iterator = self.store.get_iter(path) 44 | state = self.store.get_value(iterator, 0) 45 | self.store.set_value(iterator, 0, not state) 46 | 47 | def on_section_header_clicked(self, widget): 48 | """Selection column header clicked""" 49 | if self.state == "normal": # deselect all 50 | self._last_selected = self.get_selected() 51 | self.select_all(state=False) 52 | self.state = "deselected" 53 | elif self.state == "deselected": # select all 54 | self.state = "selected" 55 | self.select_all(state=True) 56 | elif self.state == "selected": # select previous selected 57 | self.state = "normal" 58 | self.select_by_keys(self._last_selected) 59 | self._last_selected = [] 60 | 61 | def setup_view(self): 62 | """Create models and columns for the Repo TextView""" 63 | store = Gtk.ListStore("gboolean", str, str, "gboolean") 64 | self.set_model(store) 65 | # Setup Selection Column 66 | col = self.create_selection_column_num( 67 | 0, tooltip=_("Click here to switch between\n" " none/all/default selected") 68 | ) 69 | col.set_clickable(True) 70 | col.connect("clicked", self.on_section_header_clicked) 71 | 72 | # Setup resent column 73 | cell2 = Gtk.CellRendererPixbuf() # gpgcheck 74 | cell2.set_property("icon-name", "dialog-password-symbolic") 75 | column2 = Gtk.TreeViewColumn("", cell2) 76 | column2.set_cell_data_func(cell2, self.new_pixbuf) 77 | column2.set_sizing(Gtk.TreeViewColumnSizing.FIXED) 78 | column2.set_fixed_width(20) 79 | column2.set_sort_column_id(-1) 80 | self.append_column(column2) 81 | 82 | # Setup reponame & repofile column's 83 | self.create_text_column_num(_("Repository"), 1) 84 | self.create_text_column_num(_("Name"), 2) 85 | self.set_search_column(1) 86 | self.set_reorderable(False) 87 | return store 88 | 89 | def populate(self, data): 90 | """Populate a repo liststore with data""" 91 | self.store.clear() 92 | for state, ident, name, gpg in data: 93 | self.store.append([state, ident, name, gpg]) 94 | 95 | def new_pixbuf(self, column, cell, model, iterator, data): 96 | gpg = model.get_value(iterator, 3) 97 | if gpg: 98 | cell.set_property("visible", True) 99 | else: 100 | cell.set_property("visible", False) 101 | 102 | def get_selected(self): 103 | selected = [] 104 | for elem in self.store: 105 | state = elem[0] 106 | name = elem[1] 107 | if state: 108 | selected.append(name) 109 | return selected 110 | 111 | def get_notselected(self): 112 | notselected = [] 113 | for elem in self.store: 114 | state = elem[0] 115 | name = elem[1] 116 | if not state: 117 | notselected.append(name) 118 | return notselected 119 | 120 | def select_by_keys(self, keys): 121 | iterator = self.store.get_iter_first() 122 | while iterator is not None: 123 | repoid = self.store.get_value(iterator, 1) 124 | if repoid in keys: 125 | self.store.set_value(iterator, 0, True) 126 | else: 127 | self.store.set_value(iterator, 0, False) 128 | iterator = self.store.iter_next(iterator) 129 | 130 | def select_all(self, state=True): 131 | """Set repo selection for all repos.""" 132 | iterator = self.store.get_iter_first() 133 | while iterator is not None: 134 | self.store.set_value(iterator, 0, state) 135 | iterator = self.store.iter_next(iterator) 136 | -------------------------------------------------------------------------------- /src/yumex/gui/views/selectionview.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | import logging 21 | from gi.repository import Gtk 22 | 23 | logger = logging.getLogger("yumex.gui.views") 24 | 25 | 26 | class SelectionView(Gtk.TreeView): 27 | """ 28 | A Base view with an selection column 29 | """ 30 | 31 | def __init__(self): 32 | """ 33 | init the view 34 | """ 35 | Gtk.TreeView.__init__(self) 36 | self.store = None 37 | 38 | def create_text_column_num(self, hdr, colno, resize=True, size=None, markup=False): 39 | """ 40 | Create a TreeViewColumn with data from a TreeStore column 41 | @param hdr: column header text 42 | @param colno: TreeStore column to get the data from 43 | @param resize: is resizable 44 | @param size: 45 | @param markup: 46 | """ 47 | cell = Gtk.CellRendererText() 48 | if markup: 49 | column = Gtk.TreeViewColumn(hdr, cell, markup=colno) 50 | else: 51 | column = Gtk.TreeViewColumn(hdr, cell, text=colno) 52 | column.set_resizable(resize) 53 | if size: 54 | column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) 55 | column.set_fixed_width(size) 56 | self.append_column(column) 57 | return column 58 | 59 | def create_text_column( 60 | self, hdr, prop, size, sortcol=None, click_handler=None, tooltip=None 61 | ): 62 | """ 63 | Create a TreeViewColumn with text and set 64 | the sorting properties and add it to the view 65 | """ 66 | cell = Gtk.CellRendererText() # Size Column 67 | column = Gtk.TreeViewColumn(hdr, cell) 68 | column.set_resizable(True) 69 | column.set_cell_data_func(cell, self.get_data_text, prop) 70 | column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) 71 | column.set_fixed_width(size) 72 | if sortcol: 73 | column.set_sort_column_id(sortcol) 74 | # column.set_sort_indicator(True) 75 | # column.set_sort_order(Gtk.Gtk.SortType.ASCENDING) 76 | else: 77 | column.set_sort_column_id(-1) 78 | self.append_column(column) 79 | if click_handler: 80 | column.set_clickable(True) 81 | label = Gtk.Label(label=hdr) 82 | label.show() 83 | column.set_widget(label) 84 | widget = column.get_button() 85 | while not isinstance(widget, Gtk.Button): 86 | widget = widget.get_parent() 87 | widget.connect("button-release-event", click_handler) 88 | if tooltip: 89 | widget.set_tooltip_text(tooltip) 90 | 91 | return column 92 | 93 | def create_selection_colunm( 94 | self, attr, click_handler=None, popup_handler=None, tooltip=None, icon=None 95 | ): 96 | """Create an selection column, there get data via property function 97 | and a key attr 98 | """ 99 | # Setup a selection column using a object attribute 100 | cell1 = Gtk.CellRendererToggle() # Selection 101 | cell1.set_property("activatable", True) 102 | column1 = Gtk.TreeViewColumn("", cell1) 103 | column1.set_cell_data_func(cell1, self.get_data_bool, attr) 104 | column1.set_sizing(Gtk.TreeViewColumnSizing.FIXED) 105 | column1.set_sort_column_id(-1) 106 | self.append_column(column1) 107 | cell1.connect("toggled", self.on_toggled) 108 | column1.set_clickable(True) 109 | if click_handler: 110 | column1.connect("clicked", click_handler) 111 | label = Gtk.Label(label="+") 112 | label.show() 113 | column1.set_widget(label) 114 | if popup_handler: 115 | widget = column1.get_widget() 116 | while not isinstance(widget, Gtk.Button): 117 | widget = widget.get_parent() 118 | widget.connect("button-release-event", popup_handler) 119 | if icon: 120 | image = Gtk.Image.new_from_icon_name(icon, Gtk.IconSize.MENU) 121 | widget.set_image(image) 122 | if tooltip: 123 | widget.set_tooltip_text(tooltip) 124 | 125 | def create_selection_column_num(self, num, data_func=None, tooltip=None): 126 | """ 127 | Create an selection column, there get data an TreeStore Column 128 | @param num: TreeStore column to get data from 129 | @param data_func: 130 | @param tooltip: 131 | """ 132 | # Setup a selection column using a column num 133 | 134 | column = Gtk.TreeViewColumn(None, None) 135 | # Selection checkbox 136 | selection = Gtk.CellRendererToggle() # Selection 137 | selection.set_property("activatable", True) 138 | column.pack_start(selection, False) 139 | if data_func: 140 | column.set_cell_data_func(selection, data_func) 141 | else: 142 | column.add_attribute(selection, "active", num) 143 | column.set_resizable(True) 144 | column.set_sort_column_id(-1) 145 | self.append_column(column) 146 | selection.connect("toggled", self.on_toggled) 147 | if tooltip: 148 | label = Gtk.Label(label="") 149 | label.show() 150 | column.set_widget(label) 151 | widget = column.get_widget() 152 | while not isinstance(widget, Gtk.Button): 153 | widget = widget.get_parent() 154 | widget.set_tooltip_text(tooltip) 155 | 156 | return column 157 | 158 | def create_selection_text_column(self, hdr, select_func, text_attr, size=200): 159 | """ 160 | Create an selection column, there get data an TreeStore Column 161 | """ 162 | # Setup a selection column using a column num 163 | 164 | column = Gtk.TreeViewColumn(hdr, None) 165 | # Selection checkbox 166 | selection = Gtk.CellRendererToggle() # Selection 167 | selection.set_property("activatable", True) 168 | selection.connect("toggled", self.on_toggled) 169 | column.pack_start(selection, False) 170 | column.set_cell_data_func(selection, select_func) 171 | text = Gtk.CellRendererText() 172 | column.pack_start(text, False) 173 | column.set_cell_data_func(text, self.get_data_text, text_attr) 174 | column.set_sizing(Gtk.TreeViewColumnSizing.FIXED) 175 | column.set_fixed_width(size) 176 | column.set_sort_column_id(-1) 177 | self.append_column(column) 178 | return column 179 | 180 | def get_data_text(self, column, cell, model, iterator, prop): 181 | """property function to get string data from a object in 182 | the TreeStore based on an attributes key 183 | """ 184 | obj = model.get_value(iterator, 0) 185 | if obj: 186 | cell.set_property("text", getattr(obj, prop)) 187 | cell.set_property("foreground-rgba", obj.color) 188 | 189 | def get_data_bool(self, column, cell, model, iterator, prop): 190 | """Property function to get boolean data from a object in 191 | the TreeStore based on an attributes key 192 | """ 193 | obj = model.get_value(iterator, 0) 194 | cell.set_property("visible", True) 195 | if obj: 196 | cell.set_property("active", getattr(obj, prop)) 197 | 198 | def on_toggled(self, widget, path): 199 | """ 200 | selection togged handler 201 | overload in child class 202 | """ 203 | raise NotImplementedError 204 | -------------------------------------------------------------------------------- /src/yumex/gui/widgets/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | -------------------------------------------------------------------------------- /src/yumex/gui/widgets/content.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | import logging 21 | from gi.repository import GObject 22 | 23 | logger = logging.getLogger("yumex.gui.widget") 24 | 25 | 26 | class Content(GObject.GObject): 27 | """Handling the content pages""" 28 | 29 | __gsignals__ = { 30 | "page-changed": (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_STRING,)) 31 | } 32 | 33 | def __init__(self, win): 34 | GObject.GObject.__init__(self) 35 | self.win = win 36 | self._stack = self.win.get_ui("main_stack") 37 | self.switcher = self.win.get_ui("main_switcher") 38 | # catch changes in active page in stack 39 | self._stack.connect("notify::visible-child", self.on_switch) 40 | 41 | def select_page(self, page): 42 | """Set the active page.""" 43 | self._stack.set_visible_child_name(page) 44 | 45 | def on_menu_select(self, widget, page): 46 | """Main menu page entry is seleceted""" 47 | self.select_page(page) 48 | 49 | def on_switch(self, widget, data): 50 | """The active page is changed.""" 51 | child = self._stack.get_visible_child_name() 52 | self.emit("page-changed", child) 53 | -------------------------------------------------------------------------------- /src/yumex/gui/widgets/filters.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | import logging 21 | import yumex.common.const as const 22 | from gi.repository import GObject, Gtk 23 | from yumex.common import CONFIG 24 | 25 | logger = logging.getLogger("yumex.gui.widget") 26 | 27 | 28 | class ExtraFilters(GObject.GObject): 29 | __gsignals__ = { 30 | "changed": ( 31 | GObject.SignalFlags.RUN_FIRST, 32 | None, 33 | ( 34 | GObject.TYPE_STRING, 35 | GObject.TYPE_PYOBJECT, 36 | ), 37 | ) 38 | } 39 | 40 | def __init__(self, win): 41 | super(ExtraFilters, self).__init__() 42 | self.win = win 43 | self.all_archs = const.PLATFORM_ARCH 44 | self.current_archs = None 45 | self._button = self.win.get_ui("button_more_filters") 46 | self._button.connect("clicked", self._on_button) 47 | self._popover = self.win.get_ui("more_filters_popover") 48 | self._arch_box = self.win.get_ui("box_archs") 49 | self._setup_archs() 50 | self.newest_only = self.win.get_ui("cb_newest_only") 51 | self.newest_only.set_active(CONFIG.conf.newest_only) 52 | self.newest_only.connect("toggled", self._on_newest) 53 | 54 | def popup(self): 55 | self._on_button(self._button) 56 | 57 | def _on_button(self, button): 58 | self._popover.show_all() 59 | 60 | def _setup_archs(self): 61 | if not CONFIG.conf.archs: 62 | CONFIG.conf.archs = list(self.all_archs) 63 | CONFIG.write() 64 | self.current_archs = set(CONFIG.conf.archs) 65 | for arch in self.all_archs: 66 | button = Gtk.CheckButton(label=arch) 67 | self._arch_box.pack_start(button, True, True, 0) 68 | if arch in CONFIG.conf.archs: 69 | button.set_active(True) 70 | else: 71 | button.set_active(False) 72 | button.show() 73 | button.connect("toggled", self._on_arch) 74 | 75 | def _on_arch(self, widget): 76 | state = widget.get_active() 77 | label = widget.get_label() 78 | if state: 79 | self.current_archs.add(label) 80 | else: 81 | self.current_archs.remove(label) 82 | CONFIG.conf.archs = list(self.current_archs) 83 | CONFIG.write() 84 | self.emit("changed", "arch", list(self.current_archs)) 85 | 86 | def _on_newest(self, widget): 87 | state = widget.get_active() 88 | self.emit("changed", "newest_only", state) 89 | 90 | 91 | class FilterSidebar(GObject.GObject): 92 | """Sidebar selector widget.""" 93 | 94 | __gsignals__ = { 95 | "sidebar-changed": (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_STRING,)) 96 | } 97 | 98 | INDEX = {0: "updates", 1: "installed", 2: "available", 3: "all"} 99 | 100 | def __init__(self, parent): 101 | GObject.GObject.__init__(self) 102 | self._lb = parent.get_ui("pkg_listbox") 103 | self._parent = parent 104 | self._current = None 105 | self._lb.unselect_all() 106 | self._lb.connect("row-selected", self.on_toggled) 107 | 108 | def on_toggled(self, widget, row): 109 | """Active filter is changed.""" 110 | if row: 111 | ndx = row.get_index() 112 | key = FilterSidebar.INDEX[ndx] 113 | if key != self._current: 114 | self.emit("sidebar_changed", key) 115 | self._current = key 116 | 117 | def set_active(self, key): 118 | """Set the active item based on key.""" 119 | if self._current == key: 120 | self.emit("sidebar_changed", key) 121 | else: 122 | row_name = "pkg_flt_row_" + key 123 | row = self._parent.get_ui(row_name) 124 | self._lb.select_row(row) 125 | 126 | 127 | class Filters(GObject.GObject): 128 | """Handling the package filter UI.""" 129 | 130 | __gsignals__ = { 131 | "filter-changed": (GObject.SignalFlags.RUN_FIRST, None, (GObject.TYPE_STRING,)) 132 | } 133 | 134 | FILTERS = ["updates", "installed", "available", "all"] 135 | 136 | def __init__(self, win): 137 | GObject.GObject.__init__(self) 138 | self.win = win 139 | self._sidebar = FilterSidebar(self.win) 140 | self.current = "updates" 141 | self._sidebar.connect("sidebar-changed", self.on_toggled) 142 | 143 | def on_toggled(self, widget, flt): 144 | """Active filter is changed.""" 145 | self.current = flt 146 | self.emit("filter-changed", flt) 147 | 148 | def set_active(self, flt): 149 | """Set the active filter.""" 150 | self._sidebar.set_active(flt) 151 | -------------------------------------------------------------------------------- /src/yumex/gui/widgets/mainnenu.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | import logging 21 | from gi.repository import Gio, GObject, Gtk, GLib 22 | from yumex.common import _ 23 | 24 | G_TRUE = GLib.Variant.new_boolean(True) 25 | G_FALSE = GLib.Variant.new_boolean(False) 26 | 27 | logger = logging.getLogger("yumex.gui.widget") 28 | 29 | 30 | class MainMenu(Gio.Menu): 31 | __gsignals__ = { 32 | "menu-changed": ( 33 | GObject.SignalFlags.RUN_FIRST, 34 | None, 35 | ( 36 | GObject.TYPE_STRING, 37 | GObject.TYPE_PYOBJECT, 38 | ), 39 | ) 40 | } 41 | 42 | def __init__(self, win): 43 | super(MainMenu, self).__init__() 44 | self.win = win 45 | self._button = self.win.get_ui("mainmenu_button") 46 | self._button.connect("clicked", self._on_button) 47 | self._popover = Gtk.Popover.new_from_model(self._button, self) 48 | gen_menu = Gio.Menu() 49 | self._add_menu(gen_menu, _("Preferences"), "pref") 50 | self._add_menu(gen_menu, _("Refresh Metadata"), "reload") 51 | self._add_menu(gen_menu, _("Quit"), "quit") 52 | self.append_section(_("Main Menu"), gen_menu) 53 | help_menu = Gio.Menu() 54 | self._add_menu(help_menu, _("About"), "about") 55 | self._add_menu(help_menu, _("Keyboard Shortcuts"), "shortcuts") 56 | self._add_menu(help_menu, _("Documentation"), "docs") 57 | self.append_section(_("Help"), help_menu) 58 | 59 | def _add_menu(self, menu, label, name): 60 | # menu 61 | menu.append(label, f"win.{name}") 62 | # action 63 | action = Gio.SimpleAction.new(name, None) 64 | self.win.add_action(action) 65 | action.connect("activate", self._on_menu, name) 66 | return action 67 | 68 | def _on_menu(self, action, state, action_name): 69 | state = action.get_state() 70 | data = None 71 | if state == G_TRUE: 72 | action.change_state(G_FALSE) 73 | data = False 74 | elif state == G_FALSE: 75 | action.change_state(G_TRUE) 76 | data = True 77 | self.emit("menu-changed", action_name, data) 78 | 79 | def _on_button(self, button): 80 | self._popover.show_all() 81 | -------------------------------------------------------------------------------- /src/yumex/gui/widgets/progress.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | from yumex.common import _ 21 | 22 | 23 | class Progress: 24 | def __init__(self, ui, base): 25 | self.base = base 26 | self.ui = ui 27 | self._is_visible = False 28 | self.infobar = ui.get_object("info_revealer") # infobar revealer 29 | self.label = ui.get_object("infobar_label") 30 | self.sublabel = ui.get_object("infobar_sublabel") 31 | self.progress = ui.get_object("infobar_progress") 32 | self.spinner = ui.get_object("info_spinner") 33 | 34 | def _show_infobar(self, show=True): 35 | """Show or hide the info bar""" 36 | if show == self._is_visible: # check if infobar already is in the wanted state 37 | return 38 | self.infobar.set_reveal_child(show) 39 | if show: 40 | self.infobar.show() 41 | self.spinner.start() 42 | self.progress.show() 43 | self.progress.set_show_text(False) 44 | self.label.show() 45 | self.sublabel.show() 46 | self.label.set_text("") 47 | self.sublabel.set_text("") 48 | self._is_visible = True 49 | else: 50 | self.spinner.stop() 51 | self.infobar.hide() 52 | self.label.hide() 53 | self.sublabel.hide() 54 | self.progress.hide() 55 | self.progress.set_show_text(False) 56 | self._is_visible = False 57 | 58 | def hide(self): 59 | self._show_infobar(False) 60 | 61 | def message(self, msg): 62 | self._show_infobar(True) 63 | self.label.set_text(msg) 64 | if hasattr(self.base, "working_splash"): 65 | self.base.working_splash.set_label(msg) 66 | self.base.working_splash.set_sublabel("") 67 | 68 | def message_sub(self, msg): 69 | self._show_infobar(True) 70 | self.sublabel.set_text(msg) 71 | if hasattr(self.base, "working_splash"): 72 | self.base.working_splash.set_sublabel(msg) 73 | 74 | def check_info(self): 75 | if self.label.get_text() == "": 76 | self.message(_("Getting Package Metadata")) 77 | 78 | # pylint: disable=unused-argument 79 | def set_progress(self, frac, label=None): 80 | if 0.0 <= frac <= 1.0: 81 | self._show_infobar() 82 | self.progress.set_fraction(frac) 83 | # make sure that the main label is shown, else the progress 84 | # looks bad. this normally happens when changlog or filelist info 85 | # is needed for a package and it will trigger the yum daemon to 86 | # download the need metadata. 87 | self.check_info() 88 | -------------------------------------------------------------------------------- /src/yumex/gui/widgets/searchbar.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Yum Exteder (yumex) - A graphic package management tool 3 | # Copyright (C) 2013 -2021 Tim Lauridsen < timlaufedoraprojectorg > 4 | # 5 | # This program is free software; you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation; either version 2 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program; if not, write to 17 | # the Free Software Foundation, Inc., 18 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 | 20 | import logging 21 | from gi.repository import GLib, GObject, Gtk 22 | from yumex.common import CONFIG 23 | 24 | logger = logging.getLogger("yumex.gui.widget") 25 | G_TRUE = GLib.Variant.new_boolean(True) 26 | G_FALSE = GLib.Variant.new_boolean(False) 27 | 28 | 29 | class SearchBar(GObject.GObject): 30 | """Handling the search UI.""" 31 | 32 | __gsignals__ = { 33 | "search": ( 34 | GObject.SignalFlags.RUN_FIRST, 35 | None, 36 | ( 37 | GObject.TYPE_STRING, 38 | GObject.TYPE_STRING, 39 | GObject.TYPE_PYOBJECT, 40 | ), 41 | ) 42 | } 43 | 44 | FIELDS = ["name", "summary", "description"] 45 | TYPES = ["prefix", "keyword", "fields"] 46 | 47 | def __init__(self, win): 48 | GObject.GObject.__init__(self) 49 | self.win = win 50 | self.search_type = CONFIG.conf.search_default 51 | self.search_fields = CONFIG.conf.search_fields 52 | self.active = False 53 | # widgets 54 | self._bar = self.win.get_ui("search_bar") 55 | # Searchbar togglebutton 56 | self._toggle = self.win.get_ui("sch_togglebutton") 57 | self._toggle.connect("toggled", self.on_toggle) 58 | # Search Entry 59 | self._entry = self.win.get_ui("search_entry") 60 | self._entry.connect("activate", self.on_entry_activate) 61 | self._entry.connect("icon-press", self.on_entry_icon) 62 | # Search Options 63 | self._options = self.win.get_ui("search-options") 64 | self._options_button = self.win.get_ui("sch_options_button") 65 | self._options_button.connect("clicked", self.on_options_button) 66 | # Search Spinner 67 | self._spinner = self.win.get_ui("search_spinner") 68 | self._spinner.stop() 69 | # setup field checkboxes 70 | for key in SearchBar.FIELDS: 71 | wid = self.win.get_ui(f"sch_fld_{key}") 72 | if key in self.search_fields: 73 | wid.set_active(True) 74 | wid.connect("toggled", self.on_fields_changed, key) 75 | # set fields sensitive if type == 'fields' 76 | self._set_fields_sensitive(self.search_type == "fields") 77 | # setup search type radiobuttons 78 | for key in SearchBar.TYPES: 79 | wid = self.win.get_ui(f"sch_opt_{key}") 80 | if key == self.search_type: 81 | wid.set_active(True) 82 | wid.connect("toggled", self.on_type_changed, key) 83 | # setup search option popover 84 | self.opt_popover = self.win.get_ui("sch_opt_popover") 85 | 86 | def show_spinner(self, state=True): 87 | """Set is spinner in searchbar is running.""" 88 | if state: 89 | self._spinner.start() 90 | else: 91 | self._spinner.stop() 92 | 93 | def toggle(self): 94 | self._toggle.set_active(not self._toggle.get_active()) 95 | 96 | def _set_fields_sensitive(self, state=True): 97 | """Set sensitivity of field checkboxes.""" 98 | for key in SearchBar.FIELDS: 99 | wid = self.win.get_ui(f"sch_fld_{key}") 100 | wid.set_sensitive(state) 101 | 102 | def _get_active_field(self): 103 | """Get the active search fields, based on checkbox states.""" 104 | active = [] 105 | for key in SearchBar.FIELDS: 106 | wid = self.win.get_ui(f"sch_fld_{key}") 107 | if wid.get_active(): 108 | active.append(key) 109 | return active 110 | 111 | def _set_focus(self): 112 | """Set focus on search entry and move cursor to end of text.""" 113 | self._entry.grab_focus() 114 | self._entry.emit("move-cursor", Gtk.MovementStep.BUFFER_ENDS, 1, False) 115 | 116 | def on_options_button(self, widget): 117 | """Search Option button is toggled.""" 118 | if self.opt_popover.get_visible(): 119 | self.opt_popover.hide() 120 | self._set_focus() 121 | else: 122 | self.opt_popover.show_all() 123 | 124 | def on_toggle(self, widget=None): 125 | """Search Toggle button is toggled.""" 126 | self._bar.set_search_mode(not self._bar.get_search_mode()) 127 | if self._bar.get_search_mode(): 128 | self._set_focus() 129 | self.active = self._bar.get_search_mode() 130 | 131 | def on_type_changed(self, widget, key): 132 | """Search type is changed.""" 133 | if widget.get_active(): 134 | self.search_type = key 135 | CONFIG.conf.search_default = key 136 | if self.search_type == "fields": 137 | self._set_fields_sensitive(True) 138 | else: 139 | self._set_fields_sensitive(False) 140 | 141 | def on_fields_changed(self, widget, key): 142 | """Search fields is changed.""" 143 | self.search_fields = self._get_active_field() 144 | CONFIG.conf.search_fields = self.search_fields 145 | 146 | def on_entry_activate(self, widget): 147 | """Seach entry is activated""" 148 | # make sure search option is hidden 149 | self.signal() 150 | 151 | def on_entry_icon(self, widget, icon_pos, event): 152 | """Search icon press callback.""" 153 | # clear icon pressed 154 | if icon_pos == Gtk.EntryIconPosition.SECONDARY: 155 | self._entry.set_text("") 156 | self._entry.emit("activate") 157 | 158 | def signal(self): 159 | """Emit a seach signal with key, search type & fields.""" 160 | txt = self._entry.get_text() 161 | if self.search_type == "fields": 162 | self.emit("search", txt, self.search_type, self.search_fields) 163 | else: 164 | self.emit("search", txt, self.search_type, []) 165 | 166 | def reset(self): 167 | self._entry.set_text("") 168 | 169 | def hide(self): 170 | if self.active: 171 | self._bar.set_search_mode(False) 172 | 173 | def show(self): 174 | if self.active: 175 | self._bar.set_search_mode(True) 176 | self._set_focus() 177 | -------------------------------------------------------------------------------- /tools/git2cl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Copyright 2008 Marcus D. Hanwell 3 | # Distributed under the terms of the GNU General Public License v2 or later 4 | 5 | 6 | import string, re, os 7 | 8 | # Execute git log with the desired command line options. 9 | fin = os.popen('git log --summary --stat --no-merges --date=short', 'r') 10 | 11 | # Create a ChangeLog file in the current directory. 12 | fout = open('ChangeLog', 'w') 13 | 14 | # Set up the loop variables in order to locate the blocks we want 15 | authorFound = False 16 | dateFound = False 17 | messageFound = False 18 | filesFound = False 19 | message = "" 20 | messageNL = False 21 | files = "" 22 | prevAuthorLine = "" 23 | date = "" 24 | author = "" 25 | 26 | # The main part of the loop 27 | for line in fin: 28 | # The commit line marks the start of a new commit object. 29 | if line.find('commit') >= 0: 30 | # Start all over again... 31 | authorFound = False 32 | dateFound = False 33 | messageFound = False 34 | messageNL = False 35 | message = "" 36 | filesFound = False 37 | files = "" 38 | continue 39 | # Match the author line and extract the part we want 40 | elif re.match('Author:', line): 41 | authorList = re.split(': ', line, 1) 42 | author = authorList[1] 43 | author = author.split("<")[0] 44 | author = author[0:len(author)-1] 45 | authorFound = True 46 | # Match the date line 47 | elif re.match('Date:', line): 48 | dateList = re.split(': ', line, 1) 49 | date = dateList[1] 50 | date = date[0:len(date)-1] 51 | dateFound = True 52 | # The svn-id lines are ignored 53 | elif re.match(' git-svn-id:', line): 54 | continue 55 | # The sign off line is ignored too 56 | elif re.search('Signed-off-by', line): 57 | continue 58 | # Extract the actual commit message for this commit 59 | elif authorFound & dateFound & messageFound == False: 60 | # Find the commit message if we can 61 | if len(line) == 1: 62 | if messageNL: 63 | messageFound = True 64 | else: 65 | messageNL = True 66 | elif len(line) == 4: 67 | messageFound = True 68 | else: 69 | if len(message) == 0: 70 | message = message + line.strip() 71 | else: 72 | message = message + " " + line.strip() 73 | # If this line is hit all of the files have been stored for this commit 74 | elif re.search('files changed', line): 75 | filesFound = True 76 | continue 77 | # Collect the files for this commit. FIXME: Still need to add +/- to files 78 | elif authorFound & dateFound & messageFound: 79 | fileList = re.split(r' \| ', line, 2) 80 | if len(fileList) > 1: 81 | if len(files) > 0: 82 | pass 83 | # files = files + ", " + fileList[0].strip() 84 | else: 85 | pass 86 | # files = fileList[0].strip() 87 | # All of the parts of the commit have been found - write out the entry 88 | if authorFound & dateFound & messageFound & filesFound: 89 | # First the author line, only outputted if it is the first for that 90 | # author on this day 91 | authorLine = date + " " + author 92 | if len(prevAuthorLine) == 0: 93 | fout.write(authorLine + "\n") 94 | elif authorLine == prevAuthorLine: 95 | pass 96 | else: 97 | fout.write("\n\n" + authorLine + "\n") 98 | 99 | # Assemble the actual commit message line(s) and limit the line length 100 | # to 80 characters. 101 | commitLine = "* " + message 102 | i = 0 103 | commit = "" 104 | while i < len(commitLine): 105 | if len(commitLine) < i + 78: 106 | commit = commit + "\n " + commitLine[i:len(commitLine)] 107 | break 108 | index = commitLine.rfind(' ', i, i+78) 109 | if index > i: 110 | commit = commit + "\n " + commitLine[i:index] 111 | i = index+1 112 | else: 113 | commit = commit + "\n " + commitLine[i:78] 114 | i = i+79 115 | 116 | # Write out the commit line 117 | fout.write(commit) 118 | 119 | #Now reset all the variables ready for a new commit block. 120 | authorFound = False 121 | dateFound = False 122 | messageFound = False 123 | messageNL = False 124 | message = "" 125 | filesFound = False 126 | files = "" 127 | prevAuthorLine = authorLine 128 | 129 | # Close the input and output lines now that we are finished. 130 | fin.close() 131 | fout.close() 132 | -------------------------------------------------------------------------------- /tools/update-translations.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | PYFILES=$(find src -name "*.py") 5 | GLADEFILES=data/ui/*.ui 6 | OTHERFILES=misc/*.in 7 | POTFILES="$PYFILES $OTHERFILES $GLADEFILES" 8 | # Generate a new yumex-dnf.pot & a LINGUAS 9 | 10 | function generate_pot() 11 | { 12 | cd po 13 | >yumex-dnf.pot 14 | for file in $POTFILES 15 | do 16 | xgettext --from-code=UTF-8 -j ../$file -o yumex-dnf.pot 17 | done 18 | >LINGUAS 19 | for po in *.po 20 | do 21 | language=${po%.po} 22 | echo $language >>LINGUAS 23 | done 24 | cd .. 25 | } 26 | 27 | generate_pot -------------------------------------------------------------------------------- /yumex-dnf.spec: -------------------------------------------------------------------------------- 1 | %global appname yumex 2 | 3 | Name: %{appname}-dnf 4 | Version: 4.5.0 5 | Release: 2%{?dist} 6 | Summary: Yum Extender graphical package management tool 7 | 8 | Group: Applications/System 9 | License: GPLv2+ 10 | URL: http://yumex.dk 11 | Source0: https://github.com/timlau/yumex-dnf/releases/download/%{name}-%{version}/%{name}-%{version}.tar.gz 12 | 13 | BuildArch: noarch 14 | BuildRequires: desktop-file-utils 15 | BuildRequires: gettext 16 | BuildRequires: intltool 17 | BuildRequires: python3-devel >= 3.8 18 | BuildRequires: meson 19 | BuildRequires: python3-libsass 20 | BuildRequires: libappstream-glib 21 | 22 | Requires: python3-dnfdaemon >= 0.3.10 23 | Requires: python3-gobject >= 3.10 24 | Requires: python3-pyxdg 25 | Requires: python3-dbus 26 | Requires: python3-cairo 27 | Requires: libnotify 28 | Requires: google-noto-sans-fonts 29 | 30 | %description 31 | Graphical package tool for maintain packages on the system 32 | 33 | 34 | %prep 35 | %setup -q 36 | 37 | 38 | %build 39 | %meson 40 | %meson_build 41 | 42 | %install 43 | %meson_install 44 | 45 | %find_lang %name 46 | 47 | %post 48 | /bin/touch --no-create %{_datadir}/icons/hicolor &>/dev/null || : 49 | update-desktop-database %{_datadir}/applications &> /dev/null || : 50 | 51 | %postun 52 | if [ $1 -eq 0 ] ; then 53 | /bin/touch --no-create %{_datadir}/icons/hicolor &>/dev/null 54 | /usr/bin/gtk-update-icon-cache -f %{_datadir}/icons/hicolor &>/dev/null || : 55 | fi 56 | update-desktop-database %{_datadir}/applications &> /dev/null || : 57 | 58 | %posttrans 59 | /usr/bin/gtk-update-icon-cache -f %{_datadir}/icons/hicolor &>/dev/null || : 60 | 61 | %files -f %{name}.lang 62 | %doc README.md COPYING 63 | %{_datadir}/%{name} 64 | %{_bindir}/%{name}* 65 | %{python3_sitelib}/%{appname}/ 66 | %{_datadir}/applications/*.desktop 67 | %{_datadir}/icons/hicolor/ 68 | %{_metainfodir}/%{name}.metainfo.xml 69 | 70 | %changelog 71 | 72 | * Sat Sep 17 2022 Tim Lauridsen 4.5.0-1 73 | - bumped release to 4.5.0 (dev) 74 | 75 | * Fri Jun 4 2021 Tim Lauridsen 4.4.0-1 76 | - bumped release to 4.4.0 (dev) 77 | 78 | * Wed May 11 2016 Tim Lauridsen 4.3.3-1 79 | - bumped release to 4.3.3 (dev) 80 | 81 | * Tue Apr 26 2016 Tim Lauridsen 4.3.2-1 82 | - bumped release to 4.3.2 (dev) 83 | 84 | * Wed Dec 9 2015 Tim Lauridsen 4.3.1-1 85 | - bumped release to 4.3.1 (dev) 86 | 87 | * Tue Dec 1 2015 Tim Lauridsen 4.3.0-1 88 | - bumped release to 4.3.0 (dev) 89 | 90 | * Wed Sep 30 2015 Tim Lauridsen 4.1.4-1 91 | - bumped release to 4.1.4 92 | - need python3-dnfdaemon >= 0.3.10 93 | 94 | * Wed May 27 2015 Tim Lauridsen 4.1.3-1 95 | - bumped release to 4.1.3 96 | - need python3-dnfdaemon >= 0.3.9 97 | 98 | * Sun Apr 26 2015 Tim Lauridsen 4.1.2-1 99 | - bumped release to 4.1.2 100 | - need python3-dnfdaemon >= 0.3.8 101 | 102 | * Sun Apr 26 2015 Tim Lauridsen 4.1.1-1 103 | - bumped release to 4.1.1 104 | 105 | * Thu Apr 16 2015 Tim Lauridsen 4.1.0-2 106 | - require python3-dnfdaemon >= 0.3.6 107 | 108 | * Sun Apr 12 2015 Tim Lauridsen 4.1.0-1 109 | - bumped release to 4.1.0 110 | 111 | * Sat Apr 11 2015 Tim Lauridsen 4.0.10-3 112 | - fixed changelog versioning 113 | 114 | * Thu Apr 9 2015 Tim Lauridsen 4.0.10-1 115 | - bumped release to 4.0.10 116 | 117 | * Tue Apr 7 2015 Tim Lauridsen 4.0.9-1 118 | - bumped release to 4.0.9 119 | 120 | * Tue Oct 21 2014 Tim Lauridsen 4.0.8-1 121 | - bumped release to 4.0.8 122 | - require python3-dnfdaemon >= 0.3.3 123 | 124 | * Sun Sep 21 2014 Tim Lauridsen 4.0.7-1 125 | - bumped release to 4.0.7 126 | 127 | * Tue Sep 02 2014 Tim Lauridsen 4.0.6-1 128 | - bumped release to 4.0.6 129 | 130 | * Fri Jun 06 2014 Tim Lauridsen 4.0.5-1 131 | - bumped release to 4.0.5 132 | - Requires: python3-dnfdaemon-client >= 0.2.2 133 | 134 | * Fri May 09 2014 Tim Lauridsen 4.0.4-1 135 | - bumped release to 4.0.4 136 | - Requires: python3-dnfdaemon-client >= 0.2.0 137 | 138 | * Sat May 03 2014 Tim Lauridsen 4.0.3-1 139 | - bumped release to 4.0.3 140 | - Requires: python3-dnfdaemon >= 0.1.5 141 | 142 | * Tue Apr 01 2014 Tim Lauridsen 4.0.2-1 143 | - bumped release to 4.0.2 144 | - Requires: python3-dnfdaemon >= 0.1.4 145 | 146 | * Sat Mar 29 2014 Tim Lauridsen 4.0.1-1 147 | - bumped release to 4.0.1 148 | 149 | * Sun Sep 15 2013 Tim Lauridsen 3.99.1-1 150 | - Initial rpm build 151 | 152 | --------------------------------------------------------------------------------