├── .editorconfig ├── .gitattributes ├── .gitignore ├── COPYING ├── Makefile.am ├── NEWS ├── README.md ├── RELEASE.md ├── autogen.sh ├── configure.ac ├── data ├── .gitignore ├── Makefile.am ├── desktop │ ├── Makefile.am │ └── com.stevenbenner.pacfinder.desktop.in ├── gsettings │ ├── Makefile.am │ └── com.stevenbenner.pacfinder.gschema.xml ├── icons │ ├── COPYING │ ├── Makefile.am │ ├── scalable │ │ ├── Makefile.am │ │ └── com.stevenbenner.pacfinder.svg │ └── src │ │ ├── com.stevenbenner.pacfinder.svg │ │ └── status-icons.svg └── resources │ ├── Makefile.am │ ├── com.stevenbenner.pacfinder.gresource.xml │ ├── icon-depend.svg │ ├── icon-explicit.svg │ ├── icon-installed.svg │ ├── icon-optional.svg │ ├── icon-orphan.svg │ └── icon-uninstalled.svg ├── doc ├── Makefile.am └── pacfinder.1 ├── po ├── .gitignore ├── Makevars ├── POTFILES.in └── README.md ├── src ├── .gitignore ├── Makefile.am ├── aboutdialog.c ├── aboutdialog.h ├── database.c ├── database.h ├── interface.c ├── interface.h ├── main.c ├── main.h ├── settings.c ├── settings.h ├── util.c ├── util.h ├── window.c └── window.h └── test ├── .gitignore ├── Makefile.am ├── main.c ├── test_util.c └── test_util.h /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = tab 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | stamp-* 3 | Makefile 4 | Makefile.in 5 | Makefile.in.in 6 | /autom4te.cache/ 7 | /build-aux/ 8 | /m4/ 9 | /pacfinder-*/ 10 | /ABOUT-NLS 11 | /aclocal.m4 12 | /AUTHORS 13 | /autoscan.log 14 | /B2SUMS 15 | /ChangeLog 16 | /config.log 17 | /config.status 18 | /configure 19 | /configure.scan 20 | /INSTALL 21 | /pacfinder-*.tar* 22 | /README 23 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = \ 2 | data \ 3 | doc \ 4 | src \ 5 | test 6 | 7 | if USE_NLS 8 | SUBDIRS += po 9 | endif 10 | 11 | DIST_SUBDIRS = $(SUBDIRS) 12 | 13 | EXTRA_DIST = \ 14 | autogen.sh 15 | 16 | ACLOCAL_AMFLAGS = --install -I m4 17 | 18 | .PHONY: AUTHORS 19 | AUTHORS: 20 | $(AM_V_GEN) if test -d "$(top_srcdir)/.git"; then \ 21 | git -C "$(top_srcdir)/.git" log --format='%aN <%aE>' | sort | uniq > $@; \ 22 | fi 23 | 24 | .PHONY: ChangeLog 25 | ChangeLog: 26 | $(AM_V_GEN) if test -d "$(top_srcdir)/.git"; then \ 27 | git -C "$(top_srcdir)/.git" log --date=short --format='%cd %an:%n%x09* %s' | \ 28 | $(AWK) '/^[^\t]/{if(previous==$$0)next;previous=$$0}{print}' | \ 29 | $(SED) '/^[^\t]/s/\([^ ]*\) \(.*\)/\n\1\n\2/' > $@; \ 30 | fi 31 | 32 | dist-hook: AUTHORS ChangeLog 33 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | 2 | PacFinder Release Notes 3 | ======================= 4 | 5 | 2022-03-27 - PacFinder 1.2 6 | - Add refresh button - This button reloads all of the databases from the file- 7 | system, so any changes that you've made to your installed packages can be 8 | shown without needing to restart the program [GH-7] 9 | - Add error handling for broken pacman configurations 10 | - Fix dependency list entries in some cases linking to an uninstalled package 11 | that provides the dependency named in the list, instead of linking to the 12 | installed package satisfying the dependency, and showing an incorrect install 13 | status icon [GH-9] 14 | - Fix package list columns becoming stuck if shrunk down to zero width - now a 15 | minimum width limit of 50 pixels enforced on saved column widths [GH-3] 16 | - Fix "optional" and "optional for" dependency list entries showing incorrect 17 | names if the optional dependency description included a colon [GH-8] 18 | - Improve pacman configuration file processing, resulting in a small load time 19 | performance optimization 20 | - Log warning if pacman config file recursion limit is exceeded 21 | 22 | 2022-03-03 - PacFinder 1.1 23 | - Add handling for "Include" directives in pacman configuration files 24 | - Fix test suite failure on systems configured with locales that use a comma 25 | for the decimal mark [GH-1] 26 | 27 | 2022-02-27 - PacFinder 1.0 28 | - Initial release 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PacFinder [![Release][releasebadge]][releases] [![AUR][aurbadge]][aurpkg] 2 | 3 | **Repository & package explorer for Arch Linux.** 4 | 5 | PacFinder is a GTK 3 desktop application for browsing packages installed on your 6 | [Arch Linux][archlinux] system as well as exploring packages in the Arch Linux 7 | official package repositories. Should be compatible with any Linux distribution 8 | that uses pacman/libalpm for package management (e.g. [Manjaro][manjaro]). 9 | 10 | This software is licensed under the [Apache License, Version 2.0][license]. 11 | 12 | ![PacFinder Screenshot][screenshot] 13 | 14 | [archlinux]: https://archlinux.org/ 15 | [manjaro]: https://manjaro.org/ 16 | [license]: COPYING 17 | [screenshot]: https://stevenbenner.com/misc/pacfinder-screenshot.png 18 | [releasebadge]: https://img.shields.io/github/release/stevenbenner/pacfinder.svg 19 | [aurbadge]: https://img.shields.io/aur/version/pacfinder.svg 20 | 21 | ## Features 22 | 23 | * Comprehensive list of all packages known to local pacman 24 | * View & filter packages based on installation status 25 | * Installed 26 | * Explicitly installed 27 | * Installed as a dependency 28 | * Installed as an optional dependency 29 | * Orphan packages (dependencies not required by anything) 30 | * Explore packages contained in Arch package repositories and groups 31 | * Show installed "foreign" packages (i.e. manually installed or AUR) 32 | * Search packages by name 33 | * Display and navigate package dependency relationships 34 | * GTK desktop application, designed to obey user themes 35 | 36 | ## Installation 37 | 38 | ### Install from AUR (recommended) 39 | 40 | PacFinder is available in the Arch User Repository (AUR) as [pacfinder][aurpkg]. 41 | You can install the package using your favorite AUR helper, or by following the 42 | instructions in the [Arch User Repository][aurwiki] topic on the Arch Wiki. 43 | 44 | [aurpkg]: https://aur.archlinux.org/packages/pacfinder 45 | [aurwiki]: https://wiki.archlinux.org/title/Arch_User_Repository 46 | 47 | ### Install from tarball 48 | 49 | Download the latest distribution tarball from the [releases page][releases] for 50 | this project on GitHub. Extract the contents of that file to some temporary 51 | directory, and then run the following commands in that directory to install the 52 | software: 53 | 54 | ```shell 55 | ./configure 56 | make 57 | sudo make install 58 | ``` 59 | 60 | [releases]: https://github.com/stevenbenner/pacfinder/releases 61 | 62 | ## Building from source 63 | 64 | If you intend to do development work or make other changes to the project then 65 | you can follow these instructions. If you just want to use the program then you 66 | should instead follow the installation instructions above. 67 | 68 | ### Requirements 69 | 70 | In addition to the [base-devel][base-devel] group of packages, you need to have 71 | the following packages installed on your system to compile and run this program: 72 | 73 | * glib2 >= 2.56 74 | * gtk3 >= 3.22 75 | 76 | [base-devel]: https://archlinux.org/groups/x86_64/base-devel/ 77 | 78 | ### Clone git repo 79 | 80 | Clone the git repository to your local system: 81 | 82 | ```shell 83 | git clone https://github.com/stevenbenner/pacfinder.git 84 | cd pacfinder 85 | ``` 86 | 87 | ### Compiling 88 | 89 | Run the following commands to build this project from the source code in the git 90 | repository and produce an executable suitable for development and debugging: 91 | 92 | ```shell 93 | ./autogen.sh 94 | ./configure CFLAGS="-ggdb3 -O0" 95 | make 96 | ``` 97 | 98 | You can then run the program with the following command: 99 | 100 | ```shell 101 | GSETTINGS_SCHEMA_DIR=data/gsettings ./src/pacfinder 102 | ``` 103 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # PacFinder Release Process 2 | 3 | This file documents the PacFinder release process to ensure consistent releases. 4 | It is only of use to the maintainer. 5 | 6 | ## Release checklist 7 | 8 | Complete these steps in order. 9 | 10 | 1. Verify that `make distcheck` passes 11 | 2. Verify po files are up to date: `make -C po update-po` 12 | 3. Update NEWS file 13 | 4. Bump version number in configure.ac file (see: Versioning) 14 | 5. Make sure you're configured with optimizations: `./configure` 15 | 6. Run `make dist` 16 | 7. Sign dist file: `gpg --detach-sign pacfinder-[VERSION].tar.zst` 17 | 8. Generate checksum file: `b2sum -b pacfinder-* > B2SUMS` 18 | 9. Tag version bump commit: `git tag -s vX.Y -m 'PacFinder vX.Y release'` 19 | 10. Create GitHub release based on git tag 20 | 11. Upload the following files to the GitHub release: 21 | - pacfinder-[VERSION].tar.zst 22 | - pacfinder-[VERSION].tar.zst.sig 23 | - B2SUMS 24 | 12. Update AUR package 25 | 26 | ## Versioning 27 | 28 | Version scheme: [MAJOR].[MINOR] 29 | 30 | - **MAJOR:** Bump when something the user interacts with has functional changes 31 | from the previous version (e.g. GUI, CLI, input, output). 32 | 33 | - **MINOR:** Bump when the changes only fix bugs in user-facing elements, or 34 | the changes do not alter UI structure, behavior, or output. 35 | 36 | Reset the MINOR version number to `0` when bumping the MAJOR version. 37 | -------------------------------------------------------------------------------- /autogen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | # 3 | # Copyright 2022 Steven Benner 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o pipefail 18 | 19 | touch AUTHORS 20 | touch ChangeLog 21 | ln -sf README.md README 22 | 23 | autoreconf --force --install --verbose 24 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2022 Steven Benner 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | dnl Initialize autoconf 17 | AC_COPYRIGHT([Copyright 2022 Steven Benner]) 18 | AC_PREREQ([2.71]) 19 | AC_INIT( 20 | [PacFinder], 21 | [1.2], 22 | [https://github.com/stevenbenner/pacfinder/issues], 23 | [pacfinder], 24 | [https://github.com/stevenbenner/pacfinder] 25 | ) 26 | AC_CONFIG_SRCDIR([src/main.c]) 27 | AC_CONFIG_HEADERS([src/config.h]) 28 | AC_CONFIG_MACRO_DIRS([m4]) 29 | AC_CONFIG_AUX_DIR([build-aux]) 30 | AC_CONFIG_FILES([ 31 | data/desktop/Makefile 32 | data/gsettings/Makefile 33 | data/icons/scalable/Makefile 34 | data/icons/Makefile 35 | data/resources/Makefile 36 | data/Makefile 37 | doc/Makefile 38 | po/Makefile.in 39 | src/Makefile 40 | test/Makefile 41 | Makefile 42 | ]) 43 | 44 | dnl Check programs 45 | AC_PROG_AWK 46 | AC_PROG_CC 47 | AC_PROG_INSTALL 48 | AC_PROG_LN_S 49 | AC_PROG_SED 50 | 51 | dnl Check types 52 | AC_TYPE_OFF_T 53 | AC_TYPE_SIZE_T 54 | 55 | dnl Check headers 56 | AC_CHECK_HEADERS([unistd.h]) 57 | 58 | dnl Check functions 59 | AC_CHECK_FUNCS([setlocale]) 60 | 61 | dnl Check libraries 62 | AC_CHECK_LIB([alpm], [alpm_version], , AC_MSG_ERROR([pacman is required to compile pacfinder])) 63 | 64 | dnl Check modules 65 | PKG_CHECK_MODULES([GLIB],[glib-2.0 >= 2.56]) 66 | PKG_CHECK_MODULES([GTK], [gtk+-3.0 >= 3.22.0]) 67 | PKG_CHECK_MODULES([LIBALPM], [libalpm >= 13.0.0]) 68 | 69 | dnl Test suite 70 | AC_REQUIRE_AUX_FILE([tap-driver.sh]) 71 | 72 | dnl Initialize automake 73 | AM_INIT_AUTOMAKE([dist-zstd no-dist-gzip subdir-objects]) 74 | AM_SILENT_RULES([yes]) 75 | 76 | dnl GSettings 77 | AX_REQUIRE_DEFINED([GLIB_GSETTINGS]) 78 | GLIB_GSETTINGS 79 | 80 | dnl GResource 81 | GLIB_COMPILE_RESOURCES=`$PKG_CONFIG --variable glib_compile_resources gio-2.0` 82 | AC_SUBST(GLIB_COMPILE_RESOURCES) 83 | 84 | dnl Generate LINGUAS file 85 | rm -f $srcdir/po/LINGUAS 86 | for po_file in `ls $srcdir/po/*.po | sort`; do 87 | lang=`echo "$po_file" | ${SED} "s|.*/po/\(.*\)\.po|\1|g"` 88 | echo $lang >> $srcdir/po/LINGUAS 89 | done 90 | 91 | dnl Internationalization 92 | AM_GNU_GETTEXT([external], [need-ngettext]) 93 | AM_GNU_GETTEXT_VERSION([0.21]) 94 | AM_CONDITIONAL([USE_NLS], [test "x${USE_NLS}" = "xyes"]) 95 | AC_SUBST([GETTEXT_PACKAGE], [$PACKAGE]) 96 | AC_DEFINE_UNQUOTED([GETTEXT_PACKAGE], ["$PACKAGE"], [Gettext package.]) 97 | 98 | dnl Generate output 99 | AC_OUTPUT 100 | 101 | dnl Configuration summary 102 | echo " 103 | ${PACKAGE_NAME} - version ${PACKAGE_VERSION} 104 | 105 | Build information: 106 | source code dir : ${srcdir} 107 | prefix : ${prefix} 108 | use NLS : ${USE_NLS} 109 | compiler : ${CC} 110 | compiler flags : ${CFLAGS} 111 | 112 | Install paths: 113 | binaries : $(eval echo $(eval echo ${bindir})) 114 | data : $(eval echo $(eval echo ${datadir})) 115 | desktop : $(eval echo $(eval echo ${datadir}))/applications 116 | schemas : $(eval echo $(eval echo ${gsettingsschemadir})) 117 | icons : $(eval echo $(eval echo ${datadir}))/icons/hicolor 118 | locales : $(eval echo $(eval echo ${localedir})) 119 | man pages : $(eval echo $(eval echo ${mandir})) 120 | " 121 | -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | /desktop/*.desktop 2 | /gsettings/*.compiled 3 | /gsettings/*.valid 4 | -------------------------------------------------------------------------------- /data/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = \ 2 | desktop \ 3 | gsettings \ 4 | icons \ 5 | resources 6 | -------------------------------------------------------------------------------- /data/desktop/Makefile.am: -------------------------------------------------------------------------------- 1 | desktopdir = $(datadir)/applications 2 | 3 | desktop_in_files = \ 4 | com.stevenbenner.pacfinder.desktop.in 5 | 6 | desktop_DATA = $(desktop_in_files:.desktop.in=.desktop) 7 | 8 | $(desktop_DATA): $(desktop_in_files) 9 | if USE_NLS 10 | $(AM_V_GEN) $(MSGFMT) --desktop --template $< -d $(top_srcdir)/po -o $@ 11 | else 12 | $(AM_V_GEN) cp $< $@ 13 | endif 14 | 15 | EXTRA_DIST = \ 16 | $(desktop_in_files) 17 | 18 | CLEANFILES = \ 19 | $(desktop_DATA) 20 | -------------------------------------------------------------------------------- /data/desktop/com.stevenbenner.pacfinder.desktop.in: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Version=1.0 4 | # l10n: program name used in OS menus 5 | Name=PacFinder 6 | # l10n: program description used in OS menus - prefer short text 7 | Comment=Package & repository explorer 8 | Icon=com.stevenbenner.pacfinder 9 | Exec=pacfinder 10 | Terminal=false 11 | Categories=GTK;System; 12 | # l10n: keywords used to search for the program - leave semicolons unlocalized, list must end in semicolon 13 | Keywords=pacman; 14 | StartupNotify=true 15 | -------------------------------------------------------------------------------- /data/gsettings/Makefile.am: -------------------------------------------------------------------------------- 1 | gsettings_SCHEMAS = com.stevenbenner.pacfinder.gschema.xml 2 | 3 | @GSETTINGS_RULES@ 4 | 5 | EXTRA_DIST = \ 6 | $(gsettings_SCHEMAS) 7 | 8 | CLEANFILES = \ 9 | gschemas.compiled 10 | 11 | gschemas.compiled: $(gsettings_SCHEMAS) Makefile 12 | $(AM_V_GEN) $(GLIB_COMPILE_SCHEMAS) $(builddir) 13 | 14 | all-local: gschemas.compiled 15 | -------------------------------------------------------------------------------- /data/gsettings/com.stevenbenner.pacfinder.gschema.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | (-1, -1) 6 | Window size 7 | Window size (width and height). 8 | 9 | 10 | (-1, -1) 11 | Window position 12 | Window position (x and y). 13 | 14 | 15 | false 16 | Window maximized 17 | Window maximized state. 18 | 19 | 20 | 21 | 200 22 | Left paned width 23 | Width of the left paned in pixels. 24 | 25 | 26 | 27 | 350 28 | Right paned height 29 | Height of the right paned in pixels. 30 | 31 | 32 | (150, 150, 150, 150) 33 | Package list column widths 34 | Width of package list columns in pixels. 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /data/icons/COPYING: -------------------------------------------------------------------------------- 1 | The icons here are licensed under the Creative Commons Attribution Share 2 | Alike 4.0 International. You can find the full text of the license at: 3 | 4 | https://creativecommons.org/licenses/by-sa/4.0/ 5 | 6 | See git commits for authors and contributors to individual icons. 7 | -------------------------------------------------------------------------------- /data/icons/Makefile.am: -------------------------------------------------------------------------------- 1 | SUBDIRS = \ 2 | scalable 3 | 4 | gtk_update_icon_cache = gtk-update-icon-cache -f -t $(datadir)/icons/hicolor 5 | 6 | .PHONY: update-icon-cache 7 | update-icon-cache: 8 | @-if test -z "$(DESTDIR)"; then \ 9 | echo "Updating Gtk icon cache."; \ 10 | $(gtk_update_icon_cache); \ 11 | fi 12 | 13 | install-data-hook: update-icon-cache 14 | 15 | uninstall-hook: update-icon-cache 16 | -------------------------------------------------------------------------------- /data/icons/scalable/Makefile.am: -------------------------------------------------------------------------------- 1 | iconsdir = $(datadir)/icons/hicolor/scalable 2 | icons_appsdir = $(iconsdir)/apps 3 | 4 | dist_icons_apps_DATA = \ 5 | com.stevenbenner.pacfinder.svg 6 | -------------------------------------------------------------------------------- /data/icons/scalable/com.stevenbenner.pacfinder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /data/icons/src/com.stevenbenner.pacfinder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 43 | 47 | 48 | 50 | 53 | 57 | 61 | 62 | 65 | 69 | 73 | 74 | 84 | 93 | 94 | 98 | 100 | 105 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /data/icons/src/status-icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 38 | 41 | 42 | 44 | 52 | 57 | 58 | 66 | 68 | 72 | 76 | 80 | 81 | 82 | 90 | 95 | 96 | 104 | 109 | 110 | 118 | 121 | 125 | 129 | 133 | 137 | 141 | 145 | 146 | 147 | 155 | 160 | 161 | 169 | 174 | 175 | 183 | 188 | 189 | 197 | 202 | 203 | 211 | 216 | 217 | 219 | Right and Down Arrow 221 | 225 | 226 | 227 | 232 | 239 | 246 | 253 | 260 | 261 | 266 | 272 | 273 | 278 | 287 | 293 | 299 | 308 | 312 | 319 | 326 | 327 | 328 | -------------------------------------------------------------------------------- /data/resources/Makefile.am: -------------------------------------------------------------------------------- 1 | resource_icons = \ 2 | icon-depend.svg \ 3 | icon-explicit.svg \ 4 | icon-installed.svg \ 5 | icon-optional.svg \ 6 | icon-orphan.svg \ 7 | icon-uninstalled.svg 8 | 9 | EXTRA_DIST = \ 10 | com.stevenbenner.pacfinder.gresource.xml \ 11 | $(resource_icons) 12 | -------------------------------------------------------------------------------- /data/resources/com.stevenbenner.pacfinder.gresource.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | icon-depend.svg 5 | icon-explicit.svg 6 | icon-installed.svg 7 | icon-optional.svg 8 | icon-orphan.svg 9 | icon-uninstalled.svg 10 | 11 | 12 | -------------------------------------------------------------------------------- /data/resources/icon-depend.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Right and Down Arrow 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /data/resources/icon-explicit.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /data/resources/icon-installed.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /data/resources/icon-optional.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Right and Down Arrow 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /data/resources/icon-orphan.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /data/resources/icon-uninstalled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /doc/Makefile.am: -------------------------------------------------------------------------------- 1 | dist_man_MANS = pacfinder.1 2 | -------------------------------------------------------------------------------- /doc/pacfinder.1: -------------------------------------------------------------------------------- 1 | .TH PACFINDER "1" "Feb 2022" "PacFinder" "User Commands" 2 | .SH NAME 3 | PacFinder \- Repository & package explorer for Arch Linux. 4 | .SH SYNOPSIS 5 | .B pacfinder 6 | [\fIoptions\fR] 7 | .SH DESCRIPTION 8 | \fBPacFinder\fP is a GTK 3 desktop application for browsing packages installed on your 9 | Arch Linux system as well as exploring packages in the Arch Linux official 10 | package repositories. 11 | .SH OPTIONS 12 | .TP 13 | \fB\-h\fR, \fB\-\-help\fR 14 | Show help options 15 | .TP 16 | \fB\-v\fR, \fB\-\-version\fR 17 | Show version information 18 | .TP 19 | \fB\-\-display\fR=\fIDISPLAY\fR 20 | X display to use 21 | .TP 22 | \fB\-\-help-all\fR 23 | Show all help options 24 | .TP 25 | \fB\-\-help-gapplication\fR 26 | Show GApplication options 27 | .TP 28 | \fB\-\-help-gtk\fR 29 | Show GTK+ Options 30 | .SH AUTHOR 31 | Written by Steven Benner. 32 | .SH BUGS 33 | Please report bugs to 34 | https://github.com/stevenbenner/pacfinder/issues 35 | -------------------------------------------------------------------------------- /po/.gitignore: -------------------------------------------------------------------------------- 1 | *.gmo 2 | *.header 3 | *.mo 4 | *.pot 5 | *.sed 6 | *.sin 7 | LINGUAS 8 | Makevars.template 9 | POTFILES 10 | Rules-quot 11 | -------------------------------------------------------------------------------- /po/Makevars: -------------------------------------------------------------------------------- 1 | DOMAIN = $(PACKAGE) 2 | 3 | subdir = po 4 | top_builddir = .. 5 | 6 | XGETTEXT_OPTIONS = --from-code=UTF-8 --keyword=_ --keyword=Q_:1g --keyword=N_ --keyword=C_:1c,2 --keyword=NC_:1c,2 --keyword=ngettext:1,2 --add-comments=l10n 7 | COPYRIGHT_HOLDER = Steven Benner 8 | PACKAGE_GNU = no 9 | MSGID_BUGS_ADDRESS = https://github.com/stevenbenner/pacfinder/issues 10 | EXTRA_LOCALE_CATEGORIES = 11 | USE_MSGCTXT = no 12 | MSGMERGE_OPTIONS = 13 | MSGINIT_OPTIONS = 14 | PO_DEPENDS_ON_POT = yes 15 | DIST_DEPENDS_ON_UPDATE_PO = yes 16 | -------------------------------------------------------------------------------- /po/POTFILES.in: -------------------------------------------------------------------------------- 1 | data/desktop/com.stevenbenner.pacfinder.desktop.in 2 | data/gsettings/com.stevenbenner.pacfinder.gschema.xml 3 | src/aboutdialog.c 4 | src/database.c 5 | src/interface.c 6 | src/main.c 7 | src/settings.c 8 | src/util.c 9 | src/window.c 10 | -------------------------------------------------------------------------------- /po/README.md: -------------------------------------------------------------------------------- 1 | # Translating PacFinder 2 | 3 | If you would like to localize PacFinder to a new language or change existing 4 | translations, then please follow the instructions below. 5 | 6 | The PacFinder project uses [GNU gettext][gettext] for text localization. This is 7 | the standard tool for text localization on Linux, so you may already be familiar 8 | with the process. If not, then this document provides step-by-step guidance for 9 | non-developers who may be new to this process. 10 | 11 | [gettext]: https://www.gnu.org/software/gettext/ 12 | 13 | 14 | ## Forking the project 15 | 16 | Before you get started, you will want to get a fork of this project on your 17 | local computer that is suitable for making changes and testing. 18 | 19 | ### Install git 20 | 21 | First, you will need to have `git` installed on your computer. 22 | 23 | ```shell 24 | sudo pacman -Syu git 25 | ``` 26 | 27 | Then you will need to configure git. At the bare minimum you will need to set 28 | your name and email: 29 | 30 | ```shell 31 | git config --global user.name 'Your Name' 32 | git config --global user.email 'you@example.com' 33 | ``` 34 | 35 | This information is public. 36 | 37 | ### Set up your dev environment 38 | 39 | You should then fork the project on GitHub. You will need a GitHub account, then 40 | you can click the Fork button in the [PacFinder GitHub project][gh-project]. 41 | Once you have a fork you can clone that fork to your local machine with git: 42 | 43 | ```shell 44 | git clone https://github.com/[YOUR_USERNAME]/pacfinder.git 45 | cd pacfinder 46 | ``` 47 | 48 | I recommend that you make your changes in a new git branch. Create a new branch 49 | with the following command: 50 | 51 | ```shell 52 | git checkout -b update-loc 53 | ``` 54 | 55 | In that example, "update-loc" is the name of the new git branch. This branch 56 | name can be basically anything you like, but you will need to remember it for 57 | submitting your changes. 58 | 59 | Then you should run the following commands to set up the build system and 60 | perform the first build: 61 | 62 | ```shell 63 | ./autogen.sh 64 | ./configure 65 | make 66 | ``` 67 | 68 | Also, testing the localizations require installing PacFinder from source. So, if 69 | it is already installed on your system then you will need to uninstall it. 70 | 71 | ```shell 72 | sudo pacman -R pacfinder 73 | ``` 74 | 75 | [gh-project]: https://github.com/stevenbenner/pacfinder 76 | 77 | 78 | ## Making your changes 79 | 80 | ### Using an editor 81 | 82 | The most convenient way to make and edit po files is to use an editor built for 83 | that purpose. 84 | 85 | There are many po file editors to choose from, but if you need a recommendation 86 | then the [poedit][poedit] program is a good choice. It is also available in the 87 | Arch Linux official repos: 88 | 89 | ```shell 90 | sudo pacman -S poedit 91 | ``` 92 | 93 | Please refer to the documentation for your preferred editor for instructions on 94 | creating and editing translations. 95 | 96 | [poedit]: https://poedit.net/ 97 | 98 | ### Using gettext tools directly in CLI 99 | 100 | If you prefer not to use a po file editor then you can use the gettext tools and 101 | a basic text editor. For full documentation on the GNU gettext tools please 102 | refer to the official [gettext manual][gettext-manual]. 103 | 104 | Note: Do not attempt to create a LINGUAS file. This is generated automatically 105 | by the build system. 106 | 107 | [gettext-manual]: https://www.gnu.org/software/gettext/manual/gettext.html 108 | 109 | 110 | ## Testing your localization 111 | 112 | Once you have a po file with the translated text that you want, you should then 113 | test the program to make sure that the new localization is working. This is done 114 | by installing the program and running it. 115 | 116 | Run the following commands from the project's root folder to update, build, and 117 | install the program: 118 | 119 | ```shell 120 | ./configure 121 | make 122 | sudo make install 123 | ``` 124 | 125 | You can then run the program. If your system is not set to use the language that 126 | you are adding translations for then you can run the program with the following 127 | command to override the system-default language: 128 | 129 | ```shell 130 | LANG=ja_JP.utf8 pacfinder 131 | ``` 132 | 133 | After you have tested your changes you should uninstall the program. 134 | 135 | ```shell 136 | sudo make uninstall 137 | ``` 138 | 139 | Note that the language must be enabled in the operating system. If the language 140 | you want to test doesn't appear in `locale -a` then you will need to enable the 141 | language in your `/etc/locale.gen` file and run the `local-gen` command. For 142 | full instructions, please see the [Locale][wikilocale] topic in the Arch Wiki. 143 | 144 | [wikilocale]: https://wiki.archlinux.org/title/Locale 145 | 146 | 147 | ## Submitting your changes 148 | 149 | ### Commit to git 150 | 151 | Once you have entered your translations and are satisfied that they are working, 152 | you will then need to commit them to the git repository and push the changes to 153 | your GitHub fork. Use the following commands: 154 | 155 | ```shell 156 | git add po/ja_JP.po 157 | git commit 158 | ``` 159 | 160 | The first command instructs `git` to stage the ja_JP.po file from the po folder 161 | for commit, and the second command asks git to commit the changes to the 162 | repository. You will be prompted for a commit message. I recommend that you keep 163 | this message simple. Something like this: 164 | 165 | > Create ja_JP localization 166 | 167 | or 168 | 169 | > Update ja_JP localization 170 | 171 | Once you have committed your changes you should then push them to GitHub: 172 | 173 | ```shell 174 | git push origin -u update-loc 175 | ``` 176 | 177 | ### Open pull request 178 | 179 | The final step is to create a pull request on the PacFinder GitHub project. Open 180 | the [pull requests][gh-pr] page and create a new pull request based on your fork 181 | for the git branch you created ("update-loc" if you followed the example above). 182 | 183 | [gh-pr]: https://github.com/stevenbenner/pacfinder/pulls 184 | 185 | 186 | ## Questions 187 | 188 | If you have any questions or run into any problems while translating, please 189 | open a new discussion on GitHub. I will do my best to assist. 190 | -------------------------------------------------------------------------------- /src/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | /.deps/ 3 | /.dirstamp 4 | /config.h 5 | /config.h.in 6 | /pacfinder 7 | /pacfinder-resources.c 8 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | bin_PROGRAMS = pacfinder 2 | 3 | BUILT_SOURCES = \ 4 | pacfinder-resources.c 5 | 6 | pacfinder_SOURCES = \ 7 | aboutdialog.c \ 8 | aboutdialog.h \ 9 | database.c \ 10 | database.h \ 11 | interface.c \ 12 | interface.h \ 13 | main.c \ 14 | main.h \ 15 | settings.c \ 16 | settings.h \ 17 | util.c \ 18 | util.h \ 19 | window.c \ 20 | window.h \ 21 | $(BUILT_SOURCES) 22 | 23 | pacfinder_CFLAGS = \ 24 | -Wall \ 25 | -Wextra \ 26 | -Wno-unused-parameter \ 27 | -DLOCALEDIR=\"$(localedir)\" \ 28 | @GTK_CFLAGS@ 29 | 30 | pacfinder_LDADD = \ 31 | @GTK_LIBS@ 32 | 33 | resource_dir = ${abs_top_builddir}/data/resources 34 | 35 | pacfinder-resources.c: 36 | $(GLIB_COMPILE_RESOURCES) \ 37 | --sourcedir=$(resource_dir) \ 38 | --generate-source \ 39 | --target=$@ \ 40 | $(resource_dir)/com.stevenbenner.pacfinder.gresource.xml 41 | 42 | clean-local: 43 | $(RM) -f $(BUILT_SOURCES) 44 | -------------------------------------------------------------------------------- /src/aboutdialog.c: -------------------------------------------------------------------------------- 1 | /* aboutdialog.c - PacFinder about dialog 2 | * 3 | * Copyright 2022 Steven Benner 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* build config */ 19 | #include "config.h" 20 | 21 | /* file header */ 22 | #include "aboutdialog.h" 23 | 24 | /* system libraries */ 25 | #include 26 | #include 27 | 28 | /* pacfinder */ 29 | #include "main.h" 30 | 31 | static const gchar *authors[] = { 32 | "Steven Benner", 33 | NULL 34 | }; 35 | 36 | void show_about_dialog(GtkWindow *parent) 37 | { 38 | /* l10n: program version shown in about dialog - %s is version number (e.g. 1.2) */ 39 | gchar *version_str = g_strdup_printf(_("Version %s"), VERSION); 40 | /* l10n: program copyright shown in about dialog - %s is year (e.g. 2022, or 2022-2023) */ 41 | gchar *copyright_str = g_strdup_printf(_("Copyright %s Steven Benner"), COPYRIGHT_YEAR); 42 | 43 | gtk_show_about_dialog( 44 | parent, 45 | /* l10n: program name shown in about dialog */ 46 | "program-name", _("PacFinder"), 47 | "icon-name", APPLICATION_ID, 48 | "logo-icon-name", APPLICATION_ID, 49 | "version", version_str, 50 | /* l10n: program description shown in about dialog */ 51 | "comments", _("Repository & package explorer for Arch Linux."), 52 | /* l10n: program web site link label shown in about dialog */ 53 | "website-label", _("PacFinder on GitHub"), 54 | "website", PACKAGE_URL, 55 | "copyright", copyright_str, 56 | "authors", authors, 57 | /* l10n: translator names shown in the about dialog: Your Name */ 58 | "translator-credits", _("translator-credits"), 59 | "license-type", GTK_LICENSE_APACHE_2_0, 60 | NULL 61 | ); 62 | 63 | g_free(copyright_str); 64 | g_free(version_str); 65 | } 66 | -------------------------------------------------------------------------------- /src/aboutdialog.h: -------------------------------------------------------------------------------- 1 | /* aboutdialog.h 2 | * 3 | * Copyright 2022 Steven Benner 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef PF_ABOUTDIALOG_H 19 | #define PF_ABOUTDIALOG_H 20 | 21 | #include 22 | 23 | void show_about_dialog(GtkWindow *parent); 24 | 25 | #endif /* PF_ABOUTDIALOG_H */ 26 | -------------------------------------------------------------------------------- /src/database.c: -------------------------------------------------------------------------------- 1 | /* database.c - PacFinder alpm data access layer state and utility functions 2 | * 3 | * Copyright 2022 Steven Benner 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* build config */ 19 | #include "config.h" 20 | 21 | /* file header */ 22 | #include "database.h" 23 | 24 | /* system libraries */ 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | /* pacfinder */ 32 | #include "util.h" 33 | 34 | #define FS_ROOT_PATH "/" 35 | #define PACMAN_CONFIG_PATH "/etc/pacman.conf" 36 | #define PACMAN_DB_PATH "/var/lib/pacman/" 37 | #define MAX_CONFIG_DEPTH 5 38 | 39 | alpm_list_t *foreign_pkg_list = NULL; 40 | 41 | static alpm_handle_t *handle = NULL; 42 | static alpm_db_t *db_local = NULL; 43 | static alpm_list_t *all_packages_list = NULL; 44 | 45 | static gboolean register_syncs(const gchar *file_path, const gint depth) 46 | { 47 | static GList *processed_files = NULL; 48 | 49 | gboolean ret; 50 | gchar *contents = NULL; 51 | gchar **lines = NULL; 52 | guint i; 53 | 54 | /* prevent recursion loops by limiting max depth */ 55 | if (depth >= MAX_CONFIG_DEPTH) { 56 | /* l10n: warning message shown in cli or log - %s is file path of skipped file */ 57 | g_warning(_("Maximum pacman config recursion level exceeded. File skipped: %s"), file_path); 58 | return TRUE; 59 | } 60 | 61 | /* prevent repeated processing of files included multiple times */ 62 | if (g_list_find_custom(processed_files, file_path, (GCompareFunc)g_strcmp0) == NULL) { 63 | processed_files = g_list_append(processed_files, g_strdup(file_path)); 64 | } else { 65 | return TRUE; 66 | } 67 | 68 | ret = g_file_get_contents(file_path, &contents, NULL, NULL); 69 | 70 | if (ret) { 71 | lines = g_strsplit(contents, "\n", -1); 72 | 73 | for (i = 0; lines[i] != NULL; i++) { 74 | gchar *section = NULL; 75 | alpm_db_t *db; 76 | gchar **pair = NULL; 77 | glob_t globstruct; 78 | size_t x; 79 | 80 | g_strstrip(lines[i]); 81 | 82 | if (g_str_has_prefix(lines[i], "[") && g_str_has_suffix(lines[i], "]")) { 83 | /* handle sections: sections other than "options" are dbs */ 84 | section = g_strndup(&lines[i][1], strlen(lines[i]) - 2); 85 | if (g_strcmp0(section, "options") != 0) { 86 | db = alpm_register_syncdb(handle, section, ALPM_SIG_USE_DEFAULT); 87 | if (db) { 88 | alpm_db_set_usage(db, ALPM_DB_USAGE_ALL); 89 | } else { 90 | g_warning( 91 | /* l10n: error message - first %s is db name, second is error message */ 92 | _("Failed to register '%s' database: %s"), 93 | section, 94 | alpm_strerror(alpm_errno(handle)) 95 | ); 96 | ret = FALSE; 97 | } 98 | } 99 | g_free(section); 100 | } else { 101 | /* handle key=val pairs: find "Include" directives and recurse conf files */ 102 | pair = g_strsplit(lines[i], "=", 2); 103 | if (pair && g_strv_length(pair) == 2) { 104 | g_strstrip(pair[0]); 105 | if (g_strcmp0(pair[0], "Include") == 0) { 106 | g_strstrip(pair[1]); 107 | if (glob(pair[1], GLOB_ERR, NULL, &globstruct) == 0) { 108 | for (x = 0; x < globstruct.gl_pathc; x++) { 109 | ret = register_syncs(globstruct.gl_pathv[x], depth + 1); 110 | /* break on failure */ 111 | if (ret == FALSE) { 112 | break; 113 | } 114 | } 115 | } 116 | globfree(&globstruct); 117 | } 118 | } 119 | g_strfreev(pair); 120 | } 121 | 122 | /* break on failure */ 123 | if (ret == FALSE) { 124 | break; 125 | } 126 | } 127 | g_strfreev(lines); 128 | } else { 129 | /* l10n: error message shown in cli or log */ 130 | g_warning(_("Failed to read pacman config file: %s"), file_path); 131 | } 132 | 133 | if (depth == 0) { 134 | g_list_free_full(g_steal_pointer(&processed_files), g_free); 135 | } 136 | g_free(contents); 137 | 138 | return ret; 139 | } 140 | 141 | static void initialize_alpm(void) 142 | { 143 | alpm_errno_t err; 144 | 145 | /* set up libalpm */ 146 | handle = alpm_initialize(FS_ROOT_PATH, PACMAN_DB_PATH, &err); 147 | if (!handle) { 148 | /* l10n: error message shown in cli or log */ 149 | g_error(_("Failed to initialize libalpm: %s"), alpm_strerror(err)); 150 | } 151 | 152 | /* process pacman conf files and register dbs */ 153 | if (register_syncs(PACMAN_CONFIG_PATH, 0) == FALSE) { 154 | /* l10n: error message shown in cli or log */ 155 | g_error(_("Failed to register pacman sync databases")); 156 | } 157 | } 158 | 159 | alpm_handle_t *get_alpm_handle(void) 160 | { 161 | if (handle == NULL) { 162 | initialize_alpm(); 163 | } 164 | return handle; 165 | } 166 | 167 | alpm_db_t *get_local_db(void) 168 | { 169 | if (db_local == NULL) { 170 | db_local = alpm_get_localdb(get_alpm_handle()); 171 | } 172 | return db_local; 173 | } 174 | 175 | alpm_list_t *get_all_packages(void) 176 | { 177 | alpm_list_t *i; 178 | 179 | if (all_packages_list == NULL) { 180 | /* collect all packages from the syncdbs */ 181 | for (i = alpm_get_syncdbs(get_alpm_handle()); i; i = i->next) { 182 | alpm_db_t *db = i->data; 183 | alpm_list_t *db_package_list = alpm_db_get_pkgcache(db); 184 | 185 | if (db_package_list != NULL) { 186 | all_packages_list = alpm_list_join( 187 | all_packages_list, 188 | alpm_list_copy(db_package_list) 189 | ); 190 | } 191 | } 192 | 193 | /* iterate the localdb packages and find any that are not listed in the 194 | * syncdbs - when found, add it to the "all packages" list as well as keep 195 | * track of them in the "foreign" packages list */ 196 | for (i = alpm_db_get_pkgcache(get_local_db()); i; i = i->next) { 197 | alpm_pkg_t *pkg = i->data; 198 | 199 | if (!alpm_list_find(all_packages_list, pkg, package_cmp)) { 200 | foreign_pkg_list = alpm_list_add(foreign_pkg_list, pkg); 201 | all_packages_list = alpm_list_add(all_packages_list, pkg); 202 | } 203 | } 204 | 205 | /* sort the final list */ 206 | all_packages_list = alpm_list_msort( 207 | all_packages_list, 208 | alpm_list_count(all_packages_list), 209 | package_cmp 210 | ); 211 | } 212 | 213 | return all_packages_list; 214 | } 215 | 216 | alpm_pkg_t *find_package(const gchar *pkg_name) 217 | { 218 | return alpm_pkg_find(all_packages_list, pkg_name); 219 | } 220 | 221 | alpm_pkg_t *find_satisfier(const gchar *dep_str) 222 | { 223 | alpm_pkg_t *ret; 224 | 225 | /* prefer installed packages */ 226 | ret = alpm_find_satisfier(alpm_db_get_pkgcache(get_local_db()), dep_str); 227 | 228 | if (ret) { 229 | /* return sync db version of the package, for full dependency relations list */ 230 | ret = find_package(alpm_pkg_get_name(ret)); 231 | } else { 232 | /* if no installed packages satisfy, then search all known packages */ 233 | ret = alpm_find_satisfier(all_packages_list, dep_str); 234 | } 235 | 236 | return ret; 237 | } 238 | 239 | alpm_depend_t *find_pkg_optdep(alpm_pkg_t *pkg, alpm_pkg_t *optpkg) 240 | { 241 | const gchar *pkgname = alpm_pkg_get_name(pkg); 242 | alpm_depend_t *ret = NULL; 243 | alpm_list_t *optdeps; 244 | 245 | for (optdeps = alpm_pkg_get_optdepends(optpkg); optdeps; optdeps = alpm_list_next(optdeps)) { 246 | alpm_depend_t *opt = optdeps->data; 247 | gchar *opt_name = opt->name; 248 | alpm_list_t *provides; 249 | 250 | /* package name comparison */ 251 | if (g_strcmp0(opt_name, pkgname) == 0) { 252 | ret = opt; 253 | break; 254 | } 255 | 256 | /* check all provides for a match */ 257 | for (provides = alpm_pkg_get_provides(pkg); provides; provides = alpm_list_next(provides)) { 258 | const alpm_depend_t *prov = provides->data; 259 | if (g_strcmp0(opt_name, prov->name) == 0) { 260 | ret = opt; 261 | break; 262 | } 263 | } 264 | 265 | /* if we found a match in the provides then we're done */ 266 | if (ret != NULL) { 267 | break; 268 | } 269 | } 270 | 271 | return ret; 272 | } 273 | 274 | install_reason_t get_pkg_status(alpm_pkg_t *pkg) 275 | { 276 | static const install_reason_t reason_map[] = { 277 | [ALPM_PKG_REASON_EXPLICIT] = PKG_REASON_EXPLICIT, 278 | [ALPM_PKG_REASON_DEPEND] = PKG_REASON_DEPEND 279 | }; 280 | 281 | install_reason_t ret; 282 | alpm_pkg_t *local_pkg; 283 | alpm_pkgreason_t install_reason; 284 | alpm_list_t *required_by, *optional_for; 285 | 286 | ret = PKG_REASON_NOT_INSTALLED; 287 | 288 | local_pkg = alpm_db_get_pkg(get_local_db(), alpm_pkg_get_name(pkg)); 289 | 290 | if (local_pkg != NULL) { 291 | install_reason = alpm_pkg_get_reason(local_pkg); 292 | required_by = alpm_pkg_compute_requiredby(local_pkg); 293 | 294 | if (install_reason == ALPM_PKG_REASON_DEPEND && alpm_list_count(required_by) == 0) { 295 | optional_for = alpm_pkg_compute_optionalfor(local_pkg); 296 | 297 | if (alpm_list_count(optional_for) == 0) { 298 | ret = PKG_REASON_ORPHAN; 299 | } else { 300 | ret = PKG_REASON_OPTIONAL; 301 | } 302 | 303 | alpm_list_free_inner(optional_for, g_free); 304 | alpm_list_free(optional_for); 305 | } else { 306 | ret = reason_map[install_reason]; 307 | } 308 | 309 | alpm_list_free_inner(required_by, g_free); 310 | alpm_list_free(required_by); 311 | } 312 | 313 | return ret; 314 | } 315 | 316 | void database_free(void) 317 | { 318 | if (handle) { 319 | alpm_list_free(all_packages_list); 320 | alpm_list_free(foreign_pkg_list); 321 | all_packages_list = NULL; 322 | foreign_pkg_list = NULL; 323 | if (alpm_release(handle) == -1) { 324 | /* l10n: error message shown in cli or log */ 325 | g_error(_("Failed to release libalpm.")); 326 | } 327 | db_local = NULL; 328 | handle = NULL; 329 | } 330 | } 331 | -------------------------------------------------------------------------------- /src/database.h: -------------------------------------------------------------------------------- 1 | /* database.h 2 | * 3 | * Copyright 2022 Steven Benner 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef PF_DATABASE_H 19 | #define PF_DATABASE_H 20 | 21 | #include 22 | #include 23 | 24 | typedef enum { 25 | PKG_REASON_NOT_INSTALLED = 0, 26 | PKG_REASON_EXPLICIT, 27 | PKG_REASON_DEPEND, 28 | PKG_REASON_OPTIONAL, 29 | PKG_REASON_ORPHAN 30 | } install_reason_t; 31 | 32 | extern alpm_list_t *foreign_pkg_list; 33 | 34 | alpm_handle_t *get_alpm_handle(void); 35 | alpm_db_t *get_local_db(void); 36 | alpm_list_t *get_all_packages(void); 37 | alpm_pkg_t *find_package(const gchar *pkg_name); 38 | alpm_pkg_t *find_satisfier(const gchar *dep_str); 39 | alpm_depend_t *find_pkg_optdep(alpm_pkg_t *pkg, alpm_pkg_t *optpkg); 40 | install_reason_t get_pkg_status(alpm_pkg_t *pkg); 41 | void database_free(void); 42 | 43 | #endif /* PF_DATABASE_H */ 44 | -------------------------------------------------------------------------------- /src/interface.c: -------------------------------------------------------------------------------- 1 | /* interface.c - PacFinder graphical user interface layout and initialization 2 | * 3 | * Copyright 2022 Steven Benner 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* build config */ 19 | #include "config.h" 20 | 21 | /* file header */ 22 | #include "interface.h" 23 | 24 | /* system libraries */ 25 | #include 26 | #include 27 | 28 | /* pacfinder */ 29 | #include "database.h" 30 | 31 | struct main_window_gui_t main_window_gui; 32 | 33 | static GtkWidget *create_header_bar(void) 34 | { 35 | GtkWidget *header_bar, *menu_image; 36 | 37 | header_bar = gtk_header_bar_new(); 38 | /* l10n: main window title */ 39 | gtk_header_bar_set_title(GTK_HEADER_BAR(header_bar), _("PacFinder")); 40 | gtk_header_bar_set_show_close_button(GTK_HEADER_BAR(header_bar), TRUE); 41 | 42 | main_window_gui.search_entry = gtk_search_entry_new(); 43 | gtk_header_bar_pack_start(GTK_HEADER_BAR(header_bar), main_window_gui.search_entry); 44 | 45 | main_window_gui.menu_button = gtk_menu_button_new(); 46 | menu_image = gtk_image_new_from_icon_name("open-menu-symbolic", GTK_ICON_SIZE_BUTTON); 47 | gtk_button_set_image(GTK_BUTTON(main_window_gui.menu_button), menu_image); 48 | gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), main_window_gui.menu_button); 49 | 50 | main_window_gui.refresh_button = gtk_button_new_from_icon_name("view-refresh", GTK_ICON_SIZE_SMALL_TOOLBAR); 51 | /* l10n: refresh button tooltip */ 52 | gtk_widget_set_tooltip_text(main_window_gui.refresh_button, _("Refresh all data")); 53 | gtk_header_bar_pack_end(GTK_HEADER_BAR(header_bar), main_window_gui.refresh_button); 54 | 55 | return header_bar; 56 | } 57 | 58 | static GtkWidget *create_repo_tree(void) 59 | { 60 | GtkTreeViewColumn *column; 61 | GtkCellRenderer *renderer; 62 | GtkWidget *scrolled_window; 63 | 64 | main_window_gui.repo_tree_store = gtk_tree_store_new( 65 | FILTERS_NUM_COLS, 66 | GDK_TYPE_PIXBUF, /* icon */ 67 | G_TYPE_STRING, /* item name */ 68 | G_TYPE_INT, /* filters */ 69 | G_TYPE_POINTER, /* database */ 70 | G_TYPE_POINTER /* group */ 71 | ); 72 | main_window_gui.repo_treeview = GTK_TREE_VIEW( 73 | gtk_tree_view_new_with_model(GTK_TREE_MODEL(main_window_gui.repo_tree_store)) 74 | ); 75 | 76 | column = gtk_tree_view_column_new(); 77 | /* l10n: categories list tree view heading */ 78 | gtk_tree_view_column_set_title(column, _("Categories")); 79 | gtk_tree_view_append_column(main_window_gui.repo_treeview, column); 80 | 81 | renderer = gtk_cell_renderer_pixbuf_new(); 82 | gtk_tree_view_column_pack_start(column, renderer, FALSE); 83 | gtk_tree_view_column_set_attributes(column, renderer, "pixbuf", FILTERS_COL_ICON, NULL); 84 | 85 | renderer = gtk_cell_renderer_text_new(); 86 | gtk_tree_view_column_pack_start(column, renderer, TRUE); 87 | gtk_tree_view_column_set_attributes(column, renderer, "text", FILTERS_COL_TITLE, NULL); 88 | 89 | scrolled_window = gtk_scrolled_window_new(NULL, NULL); 90 | gtk_container_add(GTK_CONTAINER(scrolled_window), GTK_WIDGET(main_window_gui.repo_treeview)); 91 | 92 | return scrolled_window; 93 | } 94 | 95 | static void reason_cell_data_fn(GtkTreeViewColumn *column, GtkCellRenderer *renderer, 96 | GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data) 97 | { 98 | static const gchar *reason_map[] = { 99 | [PKG_REASON_NOT_INSTALLED] = "", 100 | /* l10n: package install reasons - shown in package list */ 101 | [PKG_REASON_EXPLICIT] = N_("Explicit"), 102 | [PKG_REASON_DEPEND] = N_("Depend"), 103 | [PKG_REASON_OPTIONAL] = N_("Optional"), 104 | [PKG_REASON_ORPHAN] = N_("Orphan") 105 | }; 106 | 107 | install_reason_t reason; 108 | 109 | gtk_tree_model_get(model, iter, PACKAGES_COL_STATUS, &reason, -1); 110 | 111 | /* only set localized string for installed packages to 112 | * prevent sending an empty string to gettext */ 113 | if (reason == PKG_REASON_NOT_INSTALLED) { 114 | g_object_set(renderer, "text", "", NULL); 115 | } else { 116 | g_object_set(renderer, "text", _(reason_map[reason]), NULL); 117 | } 118 | } 119 | 120 | static GtkWidget *create_package_list(void) 121 | { 122 | /* l10n: package list column names */ 123 | const gchar *column_titles[] = { N_("Name"), N_("Version"), N_("Reason"), N_("Repository") }; 124 | 125 | GtkWidget *scrolled_window; 126 | gint i; 127 | 128 | main_window_gui.package_list_store = gtk_list_store_new( 129 | PACKAGES_NUM_COLS, 130 | G_TYPE_STRING, /* name */ 131 | G_TYPE_STRING, /* version */ 132 | G_TYPE_INT, /* reason */ 133 | G_TYPE_STRING, /* repository */ 134 | G_TYPE_POINTER /* alpm_pkg_t */ 135 | ); 136 | 137 | main_window_gui.package_list_model = GTK_TREE_MODEL_FILTER( 138 | gtk_tree_model_filter_new(GTK_TREE_MODEL(main_window_gui.package_list_store), NULL) 139 | ); 140 | main_window_gui.package_treeview = GTK_TREE_VIEW( 141 | gtk_tree_view_new_with_model(GTK_TREE_MODEL(main_window_gui.package_list_model)) 142 | ); 143 | 144 | for (i = 0; i < PACKAGES_NUM_COLS - 1; i++) { /* COLS-1 for the non-visible pointer column */ 145 | GtkCellRenderer *renderer; 146 | GtkTreeViewColumn *column; 147 | 148 | renderer = gtk_cell_renderer_text_new(); 149 | column = gtk_tree_view_column_new_with_attributes( 150 | _(column_titles[i]), 151 | renderer, 152 | "text", 153 | i, 154 | NULL 155 | ); 156 | 157 | if (i == PACKAGES_COL_NAME) { 158 | g_object_set(renderer, "weight", PANGO_WEIGHT_BOLD, NULL); 159 | } 160 | 161 | if (i == PACKAGES_COL_STATUS) { 162 | gtk_tree_view_column_set_cell_data_func(column, renderer, reason_cell_data_fn, NULL, NULL); 163 | } 164 | 165 | gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_FIXED); 166 | gtk_tree_view_column_set_resizable(column, TRUE); 167 | gtk_tree_view_append_column(main_window_gui.package_treeview, column); 168 | } 169 | 170 | scrolled_window = gtk_scrolled_window_new(NULL, NULL); 171 | gtk_container_add(GTK_CONTAINER(scrolled_window), GTK_WIDGET(main_window_gui.package_treeview)); 172 | 173 | return scrolled_window; 174 | } 175 | 176 | static GtkWidget *create_package_overview(void) 177 | { 178 | GtkWidget *aside_hbox, *middle_vbox, *required_by_heading, *optional_for_heading, 179 | *dependencies_heading, *right_vbox, *hbox, *scrolled_window; 180 | 181 | main_window_gui.details_overview.left_label = GTK_LABEL(gtk_label_new(NULL)); 182 | main_window_gui.details_overview.middle_label = GTK_LABEL(gtk_label_new(NULL)); 183 | main_window_gui.details_overview.right_label = GTK_LABEL(gtk_label_new(NULL)); 184 | 185 | aside_hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); 186 | gtk_box_pack_start(GTK_BOX(aside_hbox), GTK_WIDGET(main_window_gui.details_overview.left_label), TRUE, FALSE, 0); 187 | gtk_box_pack_start(GTK_BOX(aside_hbox), GTK_WIDGET(main_window_gui.details_overview.middle_label), TRUE, FALSE, 0); 188 | gtk_box_pack_start(GTK_BOX(aside_hbox), GTK_WIDGET(main_window_gui.details_overview.right_label), TRUE, FALSE, 0); 189 | 190 | main_window_gui.details_overview.heading_label = GTK_LABEL(gtk_label_new(NULL)); 191 | gtk_label_set_justify(main_window_gui.details_overview.heading_label, GTK_JUSTIFY_LEFT); 192 | gtk_label_set_xalign(main_window_gui.details_overview.heading_label, 0); 193 | gtk_widget_set_halign(GTK_WIDGET(main_window_gui.details_overview.heading_label), GTK_ALIGN_START); 194 | gtk_widget_set_margin_bottom(GTK_WIDGET(main_window_gui.details_overview.heading_label), 20); 195 | 196 | main_window_gui.details_overview.desc_label = GTK_LABEL(gtk_label_new(NULL)); 197 | gtk_label_set_justify(main_window_gui.details_overview.desc_label, GTK_JUSTIFY_LEFT); 198 | gtk_label_set_xalign(main_window_gui.details_overview.desc_label, 0); 199 | gtk_label_set_line_wrap(main_window_gui.details_overview.desc_label, TRUE); 200 | gtk_widget_set_halign(GTK_WIDGET(main_window_gui.details_overview.desc_label), GTK_ALIGN_START); 201 | 202 | main_window_gui.details_overview.link_label = GTK_LABEL(gtk_label_new(NULL)); 203 | gtk_label_set_justify(main_window_gui.details_overview.link_label, GTK_JUSTIFY_LEFT); 204 | gtk_label_set_xalign(main_window_gui.details_overview.link_label, 0); 205 | gtk_label_set_line_wrap(main_window_gui.details_overview.link_label, TRUE); 206 | gtk_widget_set_halign(GTK_WIDGET(main_window_gui.details_overview.link_label), GTK_ALIGN_START); 207 | gtk_widget_set_margin_bottom(GTK_WIDGET(main_window_gui.details_overview.link_label), 20); 208 | 209 | middle_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); 210 | gtk_box_pack_start(GTK_BOX(middle_vbox), GTK_WIDGET(main_window_gui.details_overview.heading_label), FALSE, FALSE, 0); 211 | gtk_box_pack_start(GTK_BOX(middle_vbox), GTK_WIDGET(main_window_gui.details_overview.desc_label), FALSE, FALSE, 0); 212 | gtk_box_pack_start(GTK_BOX(middle_vbox), GTK_WIDGET(main_window_gui.details_overview.link_label), FALSE, FALSE, 0); 213 | gtk_box_pack_start(GTK_BOX(middle_vbox), aside_hbox, FALSE, FALSE, 0); 214 | 215 | required_by_heading = gtk_label_new(NULL); 216 | gtk_label_set_xalign(GTK_LABEL(required_by_heading), 0); 217 | /* l10n: package overview tab right column labels */ 218 | gtk_label_set_markup(GTK_LABEL(required_by_heading), _("Required by:")); 219 | main_window_gui.details_overview.required_by_label = GTK_LABEL(gtk_label_new(NULL)); 220 | gtk_label_set_xalign(main_window_gui.details_overview.required_by_label, 0); 221 | gtk_widget_set_margin_bottom(GTK_WIDGET(main_window_gui.details_overview.required_by_label), 20); 222 | optional_for_heading = gtk_label_new(NULL); 223 | gtk_label_set_xalign(GTK_LABEL(optional_for_heading), 0); 224 | gtk_label_set_markup(GTK_LABEL(optional_for_heading), _("Optional for:")); 225 | main_window_gui.details_overview.optional_for_label = GTK_LABEL(gtk_label_new(NULL)); 226 | gtk_label_set_xalign(main_window_gui.details_overview.optional_for_label, 0); 227 | gtk_widget_set_margin_bottom(GTK_WIDGET(main_window_gui.details_overview.optional_for_label), 20); 228 | dependencies_heading = gtk_label_new(NULL); 229 | gtk_label_set_xalign(GTK_LABEL(dependencies_heading), 0); 230 | gtk_label_set_markup(GTK_LABEL(dependencies_heading), _("Dependencies:")); 231 | main_window_gui.details_overview.dependencies_label = GTK_LABEL(gtk_label_new(NULL)); 232 | gtk_label_set_xalign(main_window_gui.details_overview.dependencies_label, 0); 233 | gtk_widget_set_margin_bottom(GTK_WIDGET(main_window_gui.details_overview.dependencies_label), 20); 234 | 235 | right_vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); 236 | gtk_widget_set_margin_start(right_vbox, 20); 237 | gtk_widget_set_margin_end(right_vbox, 20); 238 | gtk_box_pack_start(GTK_BOX(right_vbox), required_by_heading, FALSE, FALSE, 0); 239 | gtk_box_pack_start(GTK_BOX(right_vbox), GTK_WIDGET(main_window_gui.details_overview.required_by_label), FALSE, FALSE, 0); 240 | gtk_box_pack_start(GTK_BOX(right_vbox), optional_for_heading, FALSE, FALSE, 0); 241 | gtk_box_pack_start(GTK_BOX(right_vbox), GTK_WIDGET(main_window_gui.details_overview.optional_for_label), FALSE, FALSE, 0); 242 | gtk_box_pack_start(GTK_BOX(right_vbox), dependencies_heading, FALSE, FALSE, 0); 243 | gtk_box_pack_start(GTK_BOX(right_vbox), GTK_WIDGET(main_window_gui.details_overview.dependencies_label), FALSE, FALSE, 0); 244 | 245 | main_window_gui.details_overview.status_image = gtk_image_new(); 246 | gtk_widget_set_halign(main_window_gui.details_overview.status_image, GTK_ALIGN_END); 247 | gtk_widget_set_valign(main_window_gui.details_overview.status_image, GTK_ALIGN_START); 248 | gtk_widget_set_margin_end(main_window_gui.details_overview.status_image, 10); 249 | 250 | hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); 251 | gtk_widget_set_margin_top(hbox, 10); 252 | gtk_widget_set_margin_start(hbox, 10); 253 | gtk_widget_set_margin_end(hbox, 10); 254 | gtk_box_pack_start(GTK_BOX(hbox), main_window_gui.details_overview.status_image, FALSE, FALSE, 0); 255 | gtk_box_pack_start(GTK_BOX(hbox), middle_vbox, TRUE, TRUE, 0); 256 | gtk_box_pack_start(GTK_BOX(hbox), right_vbox, FALSE, FALSE, 0); 257 | 258 | scrolled_window = gtk_scrolled_window_new(NULL, NULL); 259 | gtk_container_add(GTK_CONTAINER(scrolled_window), hbox); 260 | 261 | return scrolled_window; 262 | } 263 | 264 | static GtkWidget *create_package_dependencies(void) 265 | { 266 | GtkWidget *requires_label, *optional_label, *grid, *scrolled_window; 267 | 268 | main_window_gui.package_details_deps_box = GTK_FLOW_BOX(gtk_flow_box_new()); 269 | gtk_flow_box_set_selection_mode(main_window_gui.package_details_deps_box, GTK_SELECTION_NONE); 270 | gtk_widget_set_valign(GTK_WIDGET(main_window_gui.package_details_deps_box), GTK_ALIGN_START); 271 | gtk_widget_set_hexpand(GTK_WIDGET(main_window_gui.package_details_deps_box), TRUE); 272 | 273 | /* l10n: labels in package dependencies tab */ 274 | requires_label = gtk_label_new(_("Requires:")); 275 | gtk_widget_set_valign(requires_label, GTK_ALIGN_START); 276 | gtk_widget_set_margin_top(requires_label, 5); 277 | gtk_widget_set_margin_start(requires_label, 5); 278 | gtk_widget_set_margin_end(requires_label, 5); 279 | 280 | optional_label = gtk_label_new(_("Optional:")); 281 | gtk_widget_set_valign(optional_label, GTK_ALIGN_START); 282 | gtk_widget_set_margin_top(optional_label, 5); 283 | gtk_widget_set_margin_start(optional_label, 5); 284 | gtk_widget_set_margin_end(optional_label, 5); 285 | 286 | main_window_gui.package_details_opts_grid = GTK_GRID(gtk_grid_new()); 287 | gtk_grid_set_column_spacing(main_window_gui.package_details_opts_grid, 4); 288 | 289 | grid = gtk_grid_new(); 290 | gtk_grid_attach(GTK_GRID(grid), requires_label, 0, 0, 1, 1); 291 | gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(main_window_gui.package_details_deps_box), 1, 0, 1, 1); 292 | gtk_grid_attach(GTK_GRID(grid), optional_label, 0, 1, 1, 1); 293 | gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(main_window_gui.package_details_opts_grid), 1, 1, 1, 1); 294 | 295 | scrolled_window = gtk_scrolled_window_new(NULL, NULL); 296 | gtk_container_add(GTK_CONTAINER(scrolled_window), grid); 297 | 298 | return scrolled_window; 299 | } 300 | 301 | static GtkWidget *create_package_dependents(void) 302 | { 303 | GtkWidget *requires_label, *optional_label, *grid, *scrolled_window; 304 | 305 | main_window_gui.package_details_depsfor_box = GTK_FLOW_BOX(gtk_flow_box_new()); 306 | gtk_flow_box_set_selection_mode(main_window_gui.package_details_depsfor_box, GTK_SELECTION_NONE); 307 | gtk_widget_set_valign(GTK_WIDGET(main_window_gui.package_details_depsfor_box), GTK_ALIGN_START); 308 | gtk_widget_set_hexpand(GTK_WIDGET(main_window_gui.package_details_depsfor_box), TRUE); 309 | 310 | /* l10n: labels in package dependents tab */ 311 | requires_label = gtk_label_new(_("Required By:")); 312 | gtk_widget_set_valign(requires_label, GTK_ALIGN_START); 313 | gtk_widget_set_margin_top(requires_label, 5); 314 | gtk_widget_set_margin_start(requires_label, 5); 315 | gtk_widget_set_margin_end(requires_label, 5); 316 | 317 | optional_label = gtk_label_new(_("Optional For:")); 318 | gtk_widget_set_valign(optional_label, GTK_ALIGN_START); 319 | gtk_widget_set_margin_top(optional_label, 5); 320 | gtk_widget_set_margin_start(optional_label, 5); 321 | gtk_widget_set_margin_end(optional_label, 5); 322 | 323 | main_window_gui.package_details_optsfor_grid = GTK_GRID(gtk_grid_new()); 324 | gtk_grid_set_column_spacing(main_window_gui.package_details_optsfor_grid, 4); 325 | 326 | grid = gtk_grid_new(); 327 | gtk_grid_attach(GTK_GRID(grid), requires_label, 0, 0, 1, 1); 328 | gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(main_window_gui.package_details_depsfor_box), 1, 0, 1, 1); 329 | gtk_grid_attach(GTK_GRID(grid), optional_label, 0, 1, 1, 1); 330 | gtk_grid_attach(GTK_GRID(grid), GTK_WIDGET(main_window_gui.package_details_optsfor_grid), 1, 1, 1, 1); 331 | 332 | scrolled_window = gtk_scrolled_window_new(NULL, NULL); 333 | gtk_container_add(GTK_CONTAINER(scrolled_window), grid); 334 | 335 | return scrolled_window; 336 | } 337 | 338 | static GtkWidget *create_package_details(void) 339 | { 340 | GtkWidget *scrolled_window, *package_details_treeview; 341 | gint i; 342 | 343 | main_window_gui.package_details_list_store = gtk_list_store_new( 344 | DETAILS_NUM_COLS, 345 | G_TYPE_STRING, /* name */ 346 | G_TYPE_STRING /* value */ 347 | ); 348 | package_details_treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(main_window_gui.package_details_list_store)); 349 | gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(package_details_treeview), FALSE); 350 | gtk_tree_selection_set_mode(gtk_tree_view_get_selection(GTK_TREE_VIEW(package_details_treeview)), GTK_SELECTION_NONE); 351 | 352 | for (i = 0; i < DETAILS_NUM_COLS; i++) { 353 | GtkCellRenderer *renderer; 354 | GtkTreeViewColumn *column; 355 | 356 | renderer = gtk_cell_renderer_text_new(); 357 | column = gtk_tree_view_column_new_with_attributes("", renderer, "text", i, NULL); 358 | 359 | if (i == DETAILS_COL_NAME) { 360 | g_object_set(renderer, "weight", PANGO_WEIGHT_BOLD, NULL); 361 | } 362 | 363 | gtk_tree_view_append_column(GTK_TREE_VIEW(package_details_treeview), column); 364 | } 365 | 366 | scrolled_window = gtk_scrolled_window_new(NULL, NULL); 367 | gtk_container_add(GTK_CONTAINER(scrolled_window), package_details_treeview); 368 | 369 | return scrolled_window; 370 | } 371 | 372 | static GtkWidget *create_package_info(void) 373 | { 374 | main_window_gui.details_notebook = GTK_NOTEBOOK(gtk_notebook_new()); 375 | 376 | gtk_notebook_append_page_menu( 377 | main_window_gui.details_notebook, 378 | create_package_overview(), 379 | /* l10n: package overview tab name */ 380 | gtk_label_new(_("Overview")), 381 | NULL 382 | ); 383 | 384 | gtk_notebook_append_page_menu( 385 | main_window_gui.details_notebook, 386 | create_package_dependencies(), 387 | /* l10n: package dependencies tab name */ 388 | gtk_label_new(_("Dependencies")), 389 | NULL 390 | ); 391 | 392 | gtk_notebook_append_page_menu( 393 | main_window_gui.details_notebook, 394 | create_package_dependents(), 395 | /* l10n: package dependents tab name */ 396 | gtk_label_new(_("Dependents")), 397 | NULL 398 | ); 399 | 400 | gtk_notebook_append_page_menu( 401 | main_window_gui.details_notebook, 402 | create_package_details(), 403 | /* l10n: package details tab name */ 404 | gtk_label_new(_("Details")), 405 | NULL 406 | ); 407 | 408 | return GTK_WIDGET(main_window_gui.details_notebook); 409 | } 410 | 411 | static void create_user_interface(GtkWindow *window) 412 | { 413 | gtk_window_set_titlebar(window, create_header_bar()); 414 | 415 | main_window_gui.vpaned = GTK_PANED(gtk_paned_new(GTK_ORIENTATION_VERTICAL)); 416 | gtk_paned_add1(main_window_gui.vpaned, create_package_list()); 417 | gtk_paned_add2(main_window_gui.vpaned, create_package_info()); 418 | 419 | main_window_gui.hpaned = GTK_PANED(gtk_paned_new(GTK_ORIENTATION_HORIZONTAL)); 420 | gtk_paned_add1(main_window_gui.hpaned, create_repo_tree()); 421 | gtk_paned_add2(main_window_gui.hpaned, GTK_WIDGET(main_window_gui.vpaned)); 422 | 423 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(main_window_gui.hpaned)); 424 | } 425 | 426 | void create_app_window(GtkApplication *app) 427 | { 428 | main_window_gui.window = GTK_WINDOW(gtk_application_window_new(app)); 429 | gtk_window_set_title(main_window_gui.window, _("PacFinder")); 430 | create_user_interface(main_window_gui.window); 431 | } 432 | -------------------------------------------------------------------------------- /src/interface.h: -------------------------------------------------------------------------------- 1 | /* interface.h 2 | * 3 | * Copyright 2022 Steven Benner 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef PF_INTERFACE_H 19 | #define PF_INTERFACE_H 20 | 21 | #include 22 | 23 | enum { 24 | FILTERS_COL_ICON = 0, 25 | FILTERS_COL_TITLE, 26 | FILTERS_COL_MASK, 27 | FILTERS_COL_DB, 28 | FILTERS_COL_GROUP, 29 | FILTERS_NUM_COLS 30 | }; 31 | 32 | enum { 33 | PACKAGES_COL_NAME = 0, 34 | PACKAGES_COL_VERSION, 35 | PACKAGES_COL_STATUS, 36 | PACAKGES_COL_REPO, 37 | PACKAGES_COL_PKG, 38 | PACKAGES_NUM_COLS 39 | }; 40 | 41 | enum { 42 | DETAILS_COL_NAME = 0, 43 | DETAILS_COL_VALUE, 44 | DETAILS_NUM_COLS 45 | }; 46 | 47 | struct details_overview_t { 48 | GtkWidget *status_image; 49 | GtkLabel *heading_label; 50 | GtkLabel *desc_label; 51 | GtkLabel *link_label; 52 | GtkLabel *left_label; 53 | GtkLabel *middle_label; 54 | GtkLabel *right_label; 55 | GtkLabel *required_by_label; 56 | GtkLabel *optional_for_label; 57 | GtkLabel *dependencies_label; 58 | }; 59 | 60 | struct main_window_gui_t { 61 | GtkWindow *window; 62 | GtkWidget *search_entry; 63 | GtkWidget *refresh_button; 64 | GtkWidget *menu_button; 65 | GtkPaned *hpaned; 66 | GtkPaned *vpaned; 67 | GtkTreeView *repo_treeview; 68 | GtkTreeView *package_treeview; 69 | GtkTreeStore *repo_tree_store; 70 | GtkListStore *package_list_store; 71 | GtkTreeModelFilter *package_list_model; 72 | GtkNotebook *details_notebook; 73 | struct details_overview_t details_overview; 74 | GtkFlowBox *package_details_deps_box; 75 | GtkGrid *package_details_opts_grid; 76 | GtkFlowBox *package_details_depsfor_box; 77 | GtkGrid *package_details_optsfor_grid; 78 | GtkListStore *package_details_list_store; 79 | }; 80 | 81 | extern struct main_window_gui_t main_window_gui; 82 | 83 | void create_app_window(GtkApplication *app); 84 | 85 | #endif /* PF_INTERFACE_H */ 86 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | /* main.c - PacFinder main application entry point and supporting code 2 | * 3 | * Copyright 2022 Steven Benner 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* build config */ 19 | #include "config.h" 20 | 21 | /* file header */ 22 | #include "main.h" 23 | 24 | /* system libraries */ 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | /* pacfinder */ 32 | #include "interface.h" 33 | #include "settings.h" 34 | #include "window.h" 35 | 36 | static struct app_options_t { 37 | gboolean show_version; 38 | } app_options; 39 | 40 | static void init_i18n(void) 41 | { 42 | #ifdef ENABLE_NLS 43 | setlocale(LC_ALL, ""); 44 | bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR); 45 | bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"); 46 | textdomain(GETTEXT_PACKAGE); 47 | #endif 48 | } 49 | 50 | static void init_cli_options(GApplication *app, struct app_options_t *options) 51 | { 52 | const GOptionEntry cli_options[] = { 53 | { 54 | .long_name = "version", 55 | .short_name = 'v', 56 | .flags = G_OPTION_FLAG_NONE, 57 | .arg = G_OPTION_ARG_NONE, 58 | .arg_data = &(options->show_version), 59 | /* l10n: cli option descriptions */ 60 | .description = N_("Show version information"), 61 | .arg_description = NULL 62 | }, 63 | { NULL } 64 | }; 65 | 66 | g_application_add_main_option_entries(app, cli_options); 67 | } 68 | 69 | static void on_activate_app(GtkApplication *app, struct app_options_t *options) 70 | { 71 | if (options->show_version == TRUE) { 72 | /* l10n: cli --version output */ 73 | g_print(_("PacFinder - version %s"), VERSION); 74 | g_print("\n\n"); 75 | g_print(_("Copyright %s Steven Benner"), COPYRIGHT_YEAR); 76 | g_print("\n\n"); 77 | g_print(_("Report bugs to <%s>."), PACKAGE_BUGREPORT); 78 | g_print("\n"); 79 | return; 80 | } 81 | 82 | /* add icons from gresource */ 83 | gtk_icon_theme_add_resource_path( 84 | gtk_icon_theme_get_default(), 85 | "/com/stevenbenner/pacfinder/icons" 86 | ); 87 | 88 | create_app_window(app); 89 | initialize_main_window(); 90 | } 91 | 92 | int main(int argc, char **argv) 93 | { 94 | const gchar *current_desktop = g_getenv("XDG_CURRENT_DESKTOP"); 95 | GtkApplication *app; 96 | int status; 97 | 98 | /* set up internationalization */ 99 | init_i18n(); 100 | 101 | /* log informational message to help with diagnostics */ 102 | g_message( 103 | "PacFinder %s (libalpm %s, GTK %d.%d.%d, %s)", 104 | PACKAGE_VERSION, 105 | alpm_version(), 106 | gtk_get_major_version(), 107 | gtk_get_minor_version(), 108 | gtk_get_micro_version(), 109 | current_desktop ? current_desktop : "n/a" 110 | ); 111 | 112 | /* prevent users from running as root */ 113 | if (geteuid() == 0) { 114 | /* l10n: error message shown in cli or log */ 115 | g_error(_("Do not run this program as root.")); 116 | } 117 | 118 | /* initialize settings system */ 119 | initialize_settings(); 120 | 121 | /* launch gtk application */ 122 | app = gtk_application_new(APPLICATION_ID, G_APPLICATION_FLAGS_NONE); 123 | g_signal_connect(app, "activate", G_CALLBACK(on_activate_app), &app_options); 124 | init_cli_options(G_APPLICATION(app), &app_options); 125 | status = g_application_run(G_APPLICATION(app), argc, argv); 126 | g_object_unref(app); 127 | 128 | return status; 129 | } 130 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | /* main.h 2 | * 3 | * Copyright 2022 Steven Benner 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef PF_MAIN_H 19 | #define PF_MAIN_H 20 | 21 | #define APPLICATION_ID "com.stevenbenner.pacfinder" 22 | #define COPYRIGHT_YEAR "2022" 23 | 24 | #endif /* PF_MAIN_H */ 25 | -------------------------------------------------------------------------------- /src/settings.c: -------------------------------------------------------------------------------- 1 | /* settings.c - PacFinder global settings state and interaction 2 | * 3 | * Copyright 2022 Steven Benner 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* file header */ 19 | #include "settings.h" 20 | 21 | /* system libraries */ 22 | #include 23 | #include 24 | 25 | /* pacfinder */ 26 | #include "main.h" 27 | 28 | #define LEFT_PANED_MIN 50 29 | #define LEFT_PANED_MAX 1000 30 | #define RIGHT_PANED_MIN 50 31 | #define RIGHT_PANED_MAX 1000 32 | #define WINDOW_MIN_WIDTH 600 33 | #define WINDOW_MIN_HEIGHT 450 34 | #define COLUMN_WIDTH_MIN 50 35 | 36 | static GSettings *settings = NULL; 37 | 38 | void initialize_settings(void) 39 | { 40 | if (settings == NULL) { 41 | settings = g_settings_new(APPLICATION_ID); 42 | } 43 | } 44 | 45 | void settings_free(void) 46 | { 47 | if (settings != NULL) { 48 | g_settings_sync(); 49 | g_clear_object(&settings); 50 | } 51 | } 52 | 53 | GdkRectangle get_saved_window_geometry(void) 54 | { 55 | GdkRectangle geometry = { 0 }; 56 | 57 | g_settings_get(settings, "window-position", "(ii)", &geometry.x, &geometry.y); 58 | g_settings_get(settings, "window-size", "(ii)", &geometry.width, &geometry.height); 59 | 60 | geometry.width = MAX(geometry.width, WINDOW_MIN_WIDTH); 61 | geometry.height = MAX(geometry.height, WINDOW_MIN_HEIGHT); 62 | 63 | return geometry; 64 | } 65 | 66 | void set_saved_window_geometry(GdkRectangle geometry) 67 | { 68 | geometry.width = MAX(geometry.width, WINDOW_MIN_WIDTH); 69 | geometry.height = MAX(geometry.height, WINDOW_MIN_HEIGHT); 70 | 71 | g_settings_set(settings, "window-size", "(ii)", geometry.width, geometry.height); 72 | g_settings_set(settings, "window-position", "(ii)", geometry.x, geometry.y); 73 | } 74 | 75 | gboolean get_saved_window_state(void) 76 | { 77 | gboolean maximized = FALSE; 78 | 79 | g_settings_get(settings, "window-maximized", "b", &maximized); 80 | 81 | return maximized; 82 | } 83 | 84 | void set_saved_window_state(const gboolean maximized) 85 | { 86 | g_settings_set_boolean(settings, "window-maximized", maximized); 87 | } 88 | 89 | gint get_saved_left_width(void) 90 | { 91 | return g_settings_get_int(settings, "left-width"); 92 | } 93 | 94 | void set_saved_left_width(gint width) 95 | { 96 | width = MIN(width, LEFT_PANED_MAX); 97 | width = MAX(width, LEFT_PANED_MIN); 98 | 99 | g_settings_set_int(settings, "left-width", width); 100 | } 101 | 102 | gint get_saved_right_height(void) 103 | { 104 | return g_settings_get_int(settings, "right-height"); 105 | } 106 | 107 | void set_saved_right_height(gint height) 108 | { 109 | height = MIN(height, RIGHT_PANED_MAX); 110 | height = MAX(height, RIGHT_PANED_MIN); 111 | 112 | g_settings_set_int(settings, "right-height", height); 113 | } 114 | 115 | void get_saved_package_column_widths(gint *w1, gint *w2, gint *w3, gint *w4) 116 | { 117 | g_settings_get(settings, "package-list-column-widths", "(iiii)", w1, w2, w3, w4); 118 | } 119 | 120 | void set_saved_package_column_widths(const gint w1, const gint w2, const gint w3, const gint w4) 121 | { 122 | g_settings_set( 123 | settings, 124 | "package-list-column-widths", 125 | "(iiii)", 126 | MAX(w1, COLUMN_WIDTH_MIN), 127 | MAX(w2, COLUMN_WIDTH_MIN), 128 | MAX(w3, COLUMN_WIDTH_MIN), 129 | MAX(w4, COLUMN_WIDTH_MIN) 130 | ); 131 | } 132 | -------------------------------------------------------------------------------- /src/settings.h: -------------------------------------------------------------------------------- 1 | /* settings.h 2 | * 3 | * Copyright 2022 Steven Benner 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef PF_SETTINGS_H 19 | #define PF_SETTINGS_H 20 | 21 | #include 22 | 23 | void initialize_settings(void); 24 | void settings_free(void); 25 | GdkRectangle get_saved_window_geometry(void); 26 | void set_saved_window_geometry(GdkRectangle geometry); 27 | gboolean get_saved_window_state(void); 28 | void set_saved_window_state(const gboolean maximized); 29 | gint get_saved_left_width(void); 30 | void set_saved_left_width(gint width); 31 | gint get_saved_right_height(void); 32 | void set_saved_right_height(gint height); 33 | void get_saved_package_column_widths(gint *w1, gint *w2, gint *w3, gint *w4); 34 | void set_saved_package_column_widths(const gint w1, const gint w2, const gint w3, const gint w4); 35 | 36 | #endif /* PF_SETTINGS_H */ 37 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | /* util.c - PacFinder general utility functions 2 | * 3 | * Copyright 2022 Steven Benner 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* build config */ 19 | #include "config.h" 20 | 21 | /* file header */ 22 | #include "util.h" 23 | 24 | /* system libraries */ 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | static GPtrArray *list_to_ptrarray(const alpm_list_t *list) 31 | { 32 | const alpm_list_t *i = list; 33 | GPtrArray *arr = g_ptr_array_new_with_free_func(g_free); 34 | 35 | while (i) { 36 | g_ptr_array_add(arr, g_strdup(i->data)); 37 | i = alpm_list_next(i); 38 | } 39 | g_ptr_array_add(arr, NULL); 40 | 41 | return arr; 42 | } 43 | 44 | gchar *list_to_string(const alpm_list_t *list) 45 | { 46 | GPtrArray *arr = list_to_ptrarray(list); 47 | gchar *str = g_strjoinv(", ", (gchar **)(arr->pdata)); 48 | g_ptr_array_free(arr, TRUE); 49 | 50 | return str; 51 | } 52 | 53 | gchar *deplist_to_string(const alpm_list_t *list) 54 | { 55 | const alpm_list_t *i = list; 56 | alpm_list_t *string_list = NULL; 57 | gchar *str; 58 | 59 | while (i) { 60 | gchar *dep_str = alpm_dep_compute_string(i->data); 61 | string_list = alpm_list_add(string_list, dep_str); 62 | i = alpm_list_next(i); 63 | } 64 | 65 | str = list_to_string(string_list); 66 | 67 | alpm_list_free_inner(string_list, g_free); 68 | alpm_list_free(string_list); 69 | 70 | return str; 71 | } 72 | 73 | gchar *human_readable_size(const off_t size) 74 | { 75 | /* l10n: file size units */ 76 | static const gchar *sizes[] = { N_("B"), N_("KiB"), N_("MiB"), N_("GiB"), N_("TiB") }; 77 | guint size_index = 0; 78 | gfloat file_size = size; 79 | 80 | g_return_val_if_fail(size >= 0, NULL); 81 | 82 | while (file_size >= 1024 && size_index < 4) { 83 | size_index++; 84 | file_size = file_size / 1024; 85 | } 86 | 87 | if (size >= 1024) { 88 | /* l10n: file size patterns - %.2f or %ld is the numeric value (e.g. 8.88 or 8) 89 | * and %s is the localized unit (e.g. "MiB"), the order must remain the same */ 90 | return g_strdup_printf(_("%.2f %s"), file_size, _(sizes[size_index])); 91 | } else { 92 | return g_strdup_printf(_("%ld %s"), size, _(sizes[size_index])); 93 | } 94 | } 95 | 96 | gchar *strtrunc_dep_desc(const gchar *str) 97 | { 98 | gchar *name, *desc; 99 | 100 | g_return_val_if_fail(str != NULL, NULL); 101 | 102 | name = g_strdup(str); 103 | desc = strstr(name, ": "); 104 | 105 | if (desc != NULL) { 106 | name[desc - name] = '\0'; 107 | } 108 | 109 | return name; 110 | } 111 | 112 | int package_cmp(const void *p1, const void *p2) { 113 | alpm_pkg_t *pkg1 = (alpm_pkg_t *)p1; 114 | alpm_pkg_t *pkg2 = (alpm_pkg_t *)p2; 115 | 116 | return g_strcmp0(alpm_pkg_get_name(pkg1), alpm_pkg_get_name(pkg2)); 117 | } 118 | 119 | int group_cmp(const void *p1, const void *p2) { 120 | const alpm_group_t *grp1 = p1; 121 | const alpm_group_t *grp2 = p2; 122 | 123 | return g_strcmp0(grp1->name, grp2->name); 124 | } 125 | 126 | int group_cmp_find(const void *p1, const void *p2) { 127 | const alpm_group_t *grp1 = (alpm_group_t *)&p1; 128 | const alpm_group_t *grp2 = p2; 129 | 130 | return g_strcmp0(grp1->name, grp2->name); 131 | } 132 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | /* util.h 2 | * 3 | * Copyright 2022 Steven Benner 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef PF_UTIL_H 19 | #define PF_UTIL_H 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | gchar *list_to_string(const alpm_list_t *list); 26 | gchar *deplist_to_string(const alpm_list_t *list); 27 | gchar *human_readable_size(const off_t size); 28 | gchar *strtrunc_dep_desc(const gchar *str); 29 | int package_cmp(const void *p1, const void *p2); 30 | int group_cmp(const void *p1, const void *p2); 31 | int group_cmp_find(const void *p1, const void *p2); 32 | 33 | #endif /* PF_UTIL_H */ 34 | -------------------------------------------------------------------------------- /src/window.c: -------------------------------------------------------------------------------- 1 | /* window.c - PacFinder main window business logic 2 | * 3 | * Copyright 2022 Steven Benner 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | /* build config */ 19 | #include "config.h" 20 | 21 | /* file header */ 22 | #include "window.h" 23 | 24 | /* system libraries */ 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | /* pacfinder */ 33 | #include "aboutdialog.h" 34 | #include "database.h" 35 | #include "interface.h" 36 | #include "main.h" 37 | #include "settings.h" 38 | #include "util.h" 39 | 40 | /* package list filtering */ 41 | enum { 42 | HIDE_NONE = 0, 43 | HIDE_INSTALLED = (1 << 0), 44 | HIDE_UNINSTALLED = (1 << 1), 45 | HIDE_EXPLICIT = (1 << 2), 46 | HIDE_DEPEND = (1 << 3), 47 | HIDE_OPTION = (1 << 4), 48 | HIDE_ORPHAN = (1 << 5), 49 | HIDE_NATIVE = (1 << 6), 50 | HIDE_FOREIGN = (1 << 7) 51 | }; 52 | 53 | /* package list filters */ 54 | static struct { 55 | guint status_filter; 56 | alpm_db_t *db; 57 | alpm_group_t *group; 58 | gchar *search_string; 59 | } package_filters; 60 | 61 | /* local variables */ 62 | static gulong repo_selchange_handler_id; 63 | static gulong pkg_selchange_handler_id; 64 | static gulong search_changed_handler_id; 65 | 66 | static void show_package(alpm_pkg_t *pkg); 67 | 68 | static void show_package_list(GtkListStore *package_list_store) 69 | { 70 | alpm_list_t *i; 71 | gint icount; 72 | GtkTreeIter iter; 73 | 74 | for (i = get_all_packages(), icount = 0; i; i = alpm_list_next(i), icount++) { 75 | alpm_pkg_t *pkg = i->data; 76 | 77 | gtk_list_store_append(package_list_store, &iter); 78 | gtk_list_store_set( 79 | package_list_store, 80 | &iter, 81 | PACKAGES_COL_NAME, alpm_pkg_get_name(pkg), 82 | PACKAGES_COL_VERSION, alpm_pkg_get_version(pkg), 83 | PACKAGES_COL_STATUS, get_pkg_status(pkg), 84 | PACAKGES_COL_REPO, alpm_db_get_name(alpm_pkg_get_db(pkg)), 85 | PACKAGES_COL_PKG, pkg, 86 | -1 87 | ); 88 | 89 | /* keep gtk moving while we're working - this is needed to ensure that the refresh 90 | * button gets disabled when clicked, preventing double-refresh on double-click */ 91 | if (icount % 10 == 0) { 92 | while (gtk_events_pending()) { 93 | gtk_main_iteration(); 94 | } 95 | } 96 | } 97 | } 98 | 99 | static void show_package_overview(alpm_pkg_t *pkg) 100 | { 101 | gchar *str; 102 | alpm_list_t *dep_list; 103 | size_t count; 104 | 105 | /* set icon */ 106 | switch (get_pkg_status(pkg)) { 107 | case PKG_REASON_EXPLICIT: 108 | gtk_image_set_from_icon_name( 109 | GTK_IMAGE(main_window_gui.details_overview.status_image), 110 | "icon-explicit", 111 | GTK_ICON_SIZE_DIALOG 112 | ); 113 | break; 114 | case PKG_REASON_DEPEND: 115 | gtk_image_set_from_icon_name( 116 | GTK_IMAGE(main_window_gui.details_overview.status_image), 117 | "icon-depend", 118 | GTK_ICON_SIZE_DIALOG 119 | ); 120 | break; 121 | case PKG_REASON_OPTIONAL: 122 | gtk_image_set_from_icon_name( 123 | GTK_IMAGE(main_window_gui.details_overview.status_image), 124 | "icon-optional", 125 | GTK_ICON_SIZE_DIALOG 126 | ); 127 | break; 128 | case PKG_REASON_ORPHAN: 129 | gtk_image_set_from_icon_name( 130 | GTK_IMAGE(main_window_gui.details_overview.status_image), 131 | "icon-orphan", 132 | GTK_ICON_SIZE_DIALOG 133 | ); 134 | break; 135 | case PKG_REASON_NOT_INSTALLED: 136 | gtk_image_set_from_icon_name( 137 | GTK_IMAGE(main_window_gui.details_overview.status_image), 138 | "icon-uninstalled", 139 | GTK_ICON_SIZE_DIALOG 140 | ); 141 | break; 142 | } 143 | 144 | /* set labels */ 145 | str = g_strdup_printf( 146 | "%s\n" 147 | "version %s", 148 | alpm_pkg_get_name(pkg), 149 | alpm_pkg_get_version(pkg) 150 | ); 151 | gtk_label_set_markup(main_window_gui.details_overview.heading_label, str); 152 | g_free(str); 153 | 154 | gtk_label_set_label(main_window_gui.details_overview.desc_label, alpm_pkg_get_desc(pkg)); 155 | 156 | str = g_strdup_printf("%s", alpm_pkg_get_url(pkg), alpm_pkg_get_url(pkg)); 157 | gtk_label_set_markup(main_window_gui.details_overview.link_label, str); 158 | g_free(str); 159 | 160 | str = human_readable_size(alpm_pkg_get_isize(pkg)); 161 | gtk_label_set_label(main_window_gui.details_overview.left_label, str); 162 | g_free(str); 163 | gtk_label_set_label(main_window_gui.details_overview.middle_label, alpm_pkg_get_arch(pkg)); 164 | gtk_label_set_label(main_window_gui.details_overview.right_label, alpm_db_get_name(alpm_pkg_get_db(pkg))); 165 | 166 | dep_list = alpm_pkg_compute_requiredby(pkg); 167 | count = alpm_list_count(dep_list); 168 | /* l10n: package dependency counts - %ld will be a number (zero or more) */ 169 | str = g_strdup_printf(ngettext("%ld package", "%ld packages", count), count); 170 | gtk_label_set_markup(main_window_gui.details_overview.required_by_label, str); 171 | g_free(str); 172 | alpm_list_free_inner(dep_list, g_free); 173 | alpm_list_free(dep_list); 174 | 175 | dep_list = alpm_pkg_compute_optionalfor(pkg); 176 | count = alpm_list_count(dep_list); 177 | str = g_strdup_printf(ngettext("%ld package", "%ld packages", count), count); 178 | gtk_label_set_markup(main_window_gui.details_overview.optional_for_label, str); 179 | g_free(str); 180 | alpm_list_free_inner(dep_list, g_free); 181 | alpm_list_free(dep_list); 182 | 183 | dep_list = alpm_pkg_get_depends(pkg); 184 | count = alpm_list_count(dep_list); 185 | str = g_strdup_printf(ngettext("%ld package", "%ld packages", count), count); 186 | gtk_label_set_markup(main_window_gui.details_overview.dependencies_label, str); 187 | g_free(str); 188 | } 189 | 190 | static void on_deppkg_clicked(GtkButton* self, alpm_pkg_t *pkg) 191 | { 192 | if (pkg != NULL) { 193 | show_package(pkg); 194 | gtk_notebook_set_current_page(main_window_gui.details_notebook, 0); 195 | } 196 | } 197 | 198 | static GtkWidget *create_dep_button(alpm_pkg_t *pkg, const gchar *label) 199 | { 200 | gchar *name; 201 | GtkWidget *button, *image; 202 | 203 | name = strtrunc_dep_desc(label); 204 | button = gtk_button_new_with_label(name); 205 | 206 | switch (get_pkg_status(pkg)) { 207 | case PKG_REASON_EXPLICIT: 208 | image = gtk_image_new_from_icon_name("icon-explicit", GTK_ICON_SIZE_BUTTON); 209 | break; 210 | case PKG_REASON_DEPEND: 211 | image = gtk_image_new_from_icon_name("icon-depend", GTK_ICON_SIZE_BUTTON); 212 | break; 213 | case PKG_REASON_OPTIONAL: 214 | image = gtk_image_new_from_icon_name("icon-optional", GTK_ICON_SIZE_BUTTON); 215 | break; 216 | case PKG_REASON_ORPHAN: 217 | image = gtk_image_new_from_icon_name("icon-orphan", GTK_ICON_SIZE_BUTTON); 218 | break; 219 | case PKG_REASON_NOT_INSTALLED: 220 | default: 221 | image = gtk_image_new_from_icon_name("icon-uninstalled", GTK_ICON_SIZE_BUTTON); 222 | } 223 | 224 | gtk_button_set_image(GTK_BUTTON(button), image); 225 | gtk_button_set_always_show_image(GTK_BUTTON(button), TRUE); 226 | 227 | g_signal_connect(button, "clicked", G_CALLBACK(on_deppkg_clicked), pkg); 228 | 229 | g_free(name); 230 | 231 | return button; 232 | } 233 | 234 | static void show_package_deps(alpm_pkg_t *pkg) 235 | { 236 | alpm_list_t *i; 237 | gint row; 238 | 239 | /* empty the dependencies boxes of any previous children */ 240 | gtk_container_foreach( 241 | GTK_CONTAINER(main_window_gui.package_details_deps_box), 242 | (void *)gtk_widget_destroy, 243 | NULL 244 | ); 245 | gtk_container_foreach( 246 | GTK_CONTAINER(main_window_gui.package_details_opts_grid), 247 | (void *)gtk_widget_destroy, 248 | NULL 249 | ); 250 | 251 | /* append required dependencies */ 252 | for (i = alpm_pkg_get_depends(pkg); i; i = alpm_list_next(i)) { 253 | gchar *dep_str; 254 | alpm_pkg_t *dep_pkg; 255 | GtkWidget *button; 256 | 257 | dep_str = alpm_dep_compute_string(i->data); 258 | dep_pkg = find_satisfier(dep_str); 259 | button = create_dep_button(dep_pkg, dep_str); 260 | 261 | gtk_flow_box_insert(main_window_gui.package_details_deps_box, button, -1); 262 | 263 | g_free(dep_str); 264 | } 265 | 266 | /* append optional dependencies */ 267 | for (i = alpm_pkg_get_optdepends(pkg), row = 0; i; i = alpm_list_next(i), row++) { 268 | const alpm_depend_t *dep_obj; 269 | gchar *dep_str; 270 | alpm_pkg_t *dep_pkg; 271 | GtkWidget *button, *label; 272 | 273 | dep_obj = i->data; 274 | dep_str = alpm_dep_compute_string(dep_obj); 275 | dep_pkg = find_satisfier(dep_str); 276 | 277 | button = create_dep_button(dep_pkg, dep_str); 278 | gtk_widget_set_margin_start(button, 5); 279 | gtk_widget_set_margin_top(button, 5); 280 | gtk_widget_set_margin_bottom(button, 5); 281 | 282 | label = gtk_label_new(dep_obj->desc); 283 | gtk_widget_set_halign(label, GTK_ALIGN_START); 284 | gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); 285 | gtk_label_set_xalign(GTK_LABEL(label), 0); 286 | 287 | gtk_grid_attach(main_window_gui.package_details_opts_grid, button, 0, row, 1, 1); 288 | gtk_grid_attach(main_window_gui.package_details_opts_grid, label, 1, row, 1, 1); 289 | 290 | g_free(dep_str); 291 | } 292 | 293 | gtk_widget_show_all(GTK_WIDGET(main_window_gui.package_details_deps_box)); 294 | gtk_widget_show_all(GTK_WIDGET(main_window_gui.package_details_opts_grid)); 295 | } 296 | 297 | static void show_package_depsfor(alpm_pkg_t *pkg) 298 | { 299 | alpm_list_t *required_by, *optional_for, *i; 300 | gint row; 301 | 302 | /* empty the dependents boxes of any previous children */ 303 | gtk_container_foreach( 304 | GTK_CONTAINER(main_window_gui.package_details_depsfor_box), 305 | (void *)gtk_widget_destroy, 306 | NULL 307 | ); 308 | gtk_container_foreach( 309 | GTK_CONTAINER(main_window_gui.package_details_optsfor_grid), 310 | (void *)gtk_widget_destroy, 311 | NULL 312 | ); 313 | 314 | /* append required by dependents */ 315 | required_by = alpm_pkg_compute_requiredby(pkg); 316 | for (i = required_by; i; i = alpm_list_next(i)) { 317 | alpm_pkg_t *dep; 318 | GtkWidget *button; 319 | 320 | dep = find_package(i->data); 321 | button = create_dep_button(dep, alpm_pkg_get_name(dep)); 322 | 323 | gtk_flow_box_insert(main_window_gui.package_details_depsfor_box, button, -1); 324 | } 325 | alpm_list_free_inner(required_by, g_free); 326 | alpm_list_free(required_by); 327 | 328 | /* append optional for dependents */ 329 | optional_for = alpm_pkg_compute_optionalfor(pkg); 330 | for (i = optional_for, row = 0; i; i = alpm_list_next(i), row++) { 331 | alpm_pkg_t *dep; 332 | alpm_depend_t *optdep; 333 | GtkWidget *button, *label; 334 | 335 | dep = find_package(i->data); 336 | optdep = find_pkg_optdep(pkg, dep); 337 | 338 | button = create_dep_button(dep, alpm_pkg_get_name(dep)); 339 | gtk_widget_set_margin_start(button, 5); 340 | gtk_widget_set_margin_top(button, 5); 341 | gtk_widget_set_margin_bottom(button, 5); 342 | 343 | label = gtk_label_new(optdep ? optdep->desc : NULL); 344 | gtk_widget_set_halign(label, GTK_ALIGN_START); 345 | gtk_label_set_line_wrap(GTK_LABEL(label), TRUE); 346 | gtk_label_set_xalign(GTK_LABEL(label), 0); 347 | 348 | gtk_grid_attach(main_window_gui.package_details_optsfor_grid, button, 0, row, 1, 1); 349 | gtk_grid_attach(main_window_gui.package_details_optsfor_grid, label, 1, row, 1, 1); 350 | } 351 | alpm_list_free_inner(optional_for, g_free); 352 | alpm_list_free(optional_for); 353 | 354 | gtk_widget_show_all(GTK_WIDGET(main_window_gui.package_details_depsfor_box)); 355 | gtk_widget_show_all(GTK_WIDGET(main_window_gui.package_details_optsfor_grid)); 356 | } 357 | 358 | static void append_details_row(GtkTreeIter *iter, const gchar *name, const gchar *value) 359 | { 360 | gtk_list_store_append(main_window_gui.package_details_list_store, iter); 361 | gtk_list_store_set( 362 | main_window_gui.package_details_list_store, 363 | iter, 364 | DETAILS_COL_NAME, name, 365 | DETAILS_COL_VALUE, value, 366 | -1 367 | ); 368 | } 369 | 370 | static void show_package_details(alpm_pkg_t *pkg) 371 | { 372 | alpm_pkg_t *local_pkg; 373 | gchar *licenses_str, *groups_str, *provides_str, *dependson_str, *optionals_str, 374 | *requiredby_str, *optionalfor_str, *conflicts_str, *replaces_str, *fsize_str, *isize_str, 375 | *bdate_str, *idate_str; 376 | alpm_list_t *requiredby, *optionalfor; 377 | GDateTime *bdate, *idate; 378 | GtkTreeIter iter; 379 | 380 | /* grab local package, if it exists */ 381 | local_pkg = alpm_db_get_pkg(get_local_db(), alpm_pkg_get_name(pkg)); 382 | 383 | /* compute dependency lists */ 384 | requiredby = alpm_pkg_compute_requiredby(pkg); 385 | optionalfor = alpm_pkg_compute_optionalfor(pkg); 386 | 387 | /* gather dates */ 388 | bdate = g_date_time_new_from_unix_local(alpm_pkg_get_builddate(pkg)); 389 | if (local_pkg != NULL) { 390 | idate = g_date_time_new_from_unix_local(alpm_pkg_get_installdate(local_pkg)); 391 | } else { 392 | idate = NULL; 393 | } 394 | 395 | /* build display strings */ 396 | licenses_str = list_to_string(alpm_pkg_get_licenses(pkg)); 397 | groups_str = list_to_string(alpm_pkg_get_groups(pkg)); 398 | provides_str = deplist_to_string(alpm_pkg_get_provides(pkg)); 399 | dependson_str = deplist_to_string(alpm_pkg_get_depends(pkg)); 400 | optionals_str = deplist_to_string(alpm_pkg_get_optdepends(pkg)); 401 | requiredby_str = list_to_string(requiredby); 402 | optionalfor_str = list_to_string(optionalfor); 403 | conflicts_str = deplist_to_string(alpm_pkg_get_conflicts(pkg)); 404 | replaces_str = deplist_to_string(alpm_pkg_get_replaces(pkg)); 405 | fsize_str = human_readable_size(alpm_pkg_get_size(pkg)); 406 | isize_str = human_readable_size(alpm_pkg_get_isize(pkg)); 407 | bdate_str = g_date_time_format_iso8601(bdate); 408 | if (idate != NULL) { 409 | idate_str = g_date_time_format_iso8601(idate); 410 | } else { 411 | idate_str = ""; 412 | } 413 | 414 | /* clean up dates */ 415 | g_date_time_unref(bdate); 416 | if (idate != NULL) g_date_time_unref(idate); 417 | 418 | /* clean up dependency lists */ 419 | alpm_list_free_inner(requiredby, g_free); 420 | alpm_list_free_inner(optionalfor, g_free); 421 | alpm_list_free(requiredby); 422 | alpm_list_free(optionalfor); 423 | 424 | /* empty list from any previously selected package */ 425 | gtk_list_store_clear(main_window_gui.package_details_list_store); 426 | 427 | /* add detail rows */ 428 | /* l10n: package details tab row labels */ 429 | append_details_row(&iter, _("Name:"), alpm_pkg_get_name(pkg)); 430 | append_details_row(&iter, _("Version:"), alpm_pkg_get_version(pkg)); 431 | append_details_row(&iter, _("Description:"), alpm_pkg_get_desc(pkg)); 432 | append_details_row(&iter, _("Architecture:"), alpm_pkg_get_arch(pkg)); 433 | append_details_row(&iter, _("URL:"), alpm_pkg_get_url(pkg)); 434 | append_details_row(&iter, _("Licenses:"), licenses_str); 435 | append_details_row(&iter, _("Groups:"), groups_str); 436 | append_details_row(&iter, _("Provides:"), provides_str); 437 | append_details_row(&iter, _("Depends On:"), dependson_str); 438 | append_details_row(&iter, _("Optional:"), optionals_str); 439 | append_details_row(&iter, _("Required By:"), requiredby_str); 440 | append_details_row(&iter, _("Optional For:"), optionalfor_str); 441 | append_details_row(&iter, _("Conflicts:"), conflicts_str); 442 | append_details_row(&iter, _("Replaces:"), replaces_str); 443 | append_details_row(&iter, _("File Size:"), fsize_str); 444 | append_details_row(&iter, _("Install Size:"), isize_str); 445 | append_details_row(&iter, _("Packager:"), alpm_pkg_get_packager(pkg)); 446 | append_details_row(&iter, _("Build Date:"), bdate_str); 447 | append_details_row(&iter, _("Install Date:"), idate_str); 448 | 449 | /* clean up display strings */ 450 | g_free(licenses_str); 451 | g_free(groups_str); 452 | g_free(provides_str); 453 | g_free(dependson_str); 454 | g_free(optionals_str); 455 | g_free(requiredby_str); 456 | g_free(optionalfor_str); 457 | g_free(conflicts_str); 458 | g_free(replaces_str); 459 | g_free(fsize_str); 460 | g_free(isize_str); 461 | g_free(bdate_str); 462 | if (idate != NULL) g_free(idate_str); 463 | } 464 | 465 | static void show_package(alpm_pkg_t *pkg) 466 | { 467 | if (pkg == NULL) { 468 | gtk_widget_hide(GTK_WIDGET(main_window_gui.details_notebook)); 469 | return; 470 | } 471 | 472 | show_package_overview(pkg); 473 | show_package_deps(pkg); 474 | show_package_depsfor(pkg); 475 | show_package_details(pkg); 476 | 477 | gtk_widget_show(GTK_WIDGET(main_window_gui.details_notebook)); 478 | } 479 | 480 | static void package_row_selected(GtkTreeSelection *selection, gpointer user_data) 481 | { 482 | GtkTreeModel *model; 483 | GtkTreeIter iter; 484 | alpm_pkg_t *pkg; 485 | 486 | if (gtk_tree_selection_get_selected(selection, &model, &iter)) { 487 | gtk_tree_model_get(model, &iter, PACKAGES_COL_PKG, &pkg, -1); 488 | show_package(pkg); 489 | } 490 | } 491 | 492 | static void populate_db_tree_view(GtkTreeStore *repo_tree_store) 493 | { 494 | GtkIconTheme *icon_theme; 495 | GdkPixbuf *icon; 496 | GtkTreeIter toplevel, child; 497 | alpm_list_t *i; 498 | 499 | icon_theme = gtk_icon_theme_get_default(); 500 | 501 | /* add standard filter lists */ 502 | icon = gtk_icon_theme_load_icon(icon_theme, "go-home", 16, GTK_ICON_LOOKUP_USE_BUILTIN, NULL); 503 | gtk_tree_store_append(repo_tree_store, &toplevel, NULL); 504 | gtk_tree_store_set( 505 | repo_tree_store, 506 | &toplevel, 507 | FILTERS_COL_ICON, icon, 508 | /* l10n: filter names shown in main filter list */ 509 | FILTERS_COL_TITLE, _("All Packages"), 510 | FILTERS_COL_MASK, HIDE_NONE, 511 | -1 512 | ); 513 | g_object_unref(icon); 514 | 515 | icon = gtk_icon_theme_load_icon(icon_theme, "icon-installed", 16, GTK_ICON_LOOKUP_USE_BUILTIN, NULL); 516 | gtk_tree_store_append(repo_tree_store, &toplevel, NULL); 517 | gtk_tree_store_set( 518 | repo_tree_store, 519 | &toplevel, 520 | FILTERS_COL_ICON, icon, 521 | FILTERS_COL_TITLE, _("Installed"), 522 | FILTERS_COL_MASK, HIDE_UNINSTALLED, 523 | -1 524 | ); 525 | g_object_unref(icon); 526 | 527 | icon = gtk_icon_theme_load_icon(icon_theme, "icon-explicit", 16, GTK_ICON_LOOKUP_USE_BUILTIN, NULL); 528 | gtk_tree_store_append(repo_tree_store, &toplevel, NULL); 529 | gtk_tree_store_set( 530 | repo_tree_store, 531 | &toplevel, 532 | FILTERS_COL_ICON, icon, 533 | FILTERS_COL_TITLE, _("Explicit"), 534 | FILTERS_COL_MASK, HIDE_UNINSTALLED | HIDE_DEPEND | HIDE_OPTION | HIDE_ORPHAN, 535 | -1 536 | ); 537 | g_object_unref(icon); 538 | 539 | icon = gtk_icon_theme_load_icon(icon_theme, "icon-depend", 16, GTK_ICON_LOOKUP_USE_BUILTIN, NULL); 540 | gtk_tree_store_append(repo_tree_store, &toplevel, NULL); 541 | gtk_tree_store_set( 542 | repo_tree_store, 543 | &toplevel, 544 | FILTERS_COL_ICON, icon, 545 | FILTERS_COL_TITLE, _("Dependency"), 546 | FILTERS_COL_MASK, HIDE_UNINSTALLED | HIDE_EXPLICIT | HIDE_OPTION | HIDE_ORPHAN, 547 | -1 548 | ); 549 | g_object_unref(icon); 550 | 551 | icon = gtk_icon_theme_load_icon(icon_theme, "icon-optional", 16, GTK_ICON_LOOKUP_USE_BUILTIN, NULL); 552 | gtk_tree_store_append(repo_tree_store, &toplevel, NULL); 553 | gtk_tree_store_set( 554 | repo_tree_store, 555 | &toplevel, 556 | FILTERS_COL_ICON, icon, 557 | FILTERS_COL_TITLE, _("Optional"), 558 | FILTERS_COL_MASK, HIDE_UNINSTALLED | HIDE_EXPLICIT | HIDE_DEPEND | HIDE_ORPHAN, 559 | -1 560 | ); 561 | g_object_unref(icon); 562 | 563 | icon = gtk_icon_theme_load_icon(icon_theme, "icon-orphan", 16, GTK_ICON_LOOKUP_USE_BUILTIN, NULL); 564 | gtk_tree_store_append(repo_tree_store, &toplevel, NULL); 565 | gtk_tree_store_set( 566 | repo_tree_store, 567 | &toplevel, 568 | FILTERS_COL_ICON, icon, 569 | FILTERS_COL_TITLE, _("Orphan"), 570 | FILTERS_COL_MASK, HIDE_UNINSTALLED | HIDE_EXPLICIT | HIDE_DEPEND | HIDE_OPTION, 571 | -1 572 | ); 573 | g_object_unref(icon); 574 | 575 | /* add known databases */ 576 | for (i = alpm_get_syncdbs(get_alpm_handle()); i; i = i->next) { 577 | alpm_db_t *db; 578 | alpm_list_t *group_list; 579 | 580 | db = i->data; 581 | 582 | icon = gtk_icon_theme_load_icon(icon_theme, "folder", 16, GTK_ICON_LOOKUP_USE_BUILTIN, NULL); 583 | gtk_tree_store_append(repo_tree_store, &toplevel, NULL); 584 | gtk_tree_store_set( 585 | repo_tree_store, 586 | &toplevel, 587 | FILTERS_COL_ICON, icon, 588 | FILTERS_COL_TITLE, alpm_db_get_name(db), 589 | FILTERS_COL_MASK, HIDE_NONE, 590 | FILTERS_COL_DB, db, 591 | -1 592 | ); 593 | g_object_unref(icon); 594 | 595 | /* add any groups found for this database */ 596 | group_list = alpm_list_copy(alpm_db_get_groupcache(db)); 597 | group_list = alpm_list_msort(group_list, alpm_list_count(group_list), group_cmp); 598 | for (; group_list; group_list = group_list->next) { 599 | alpm_group_t *group = group_list->data; 600 | icon = gtk_icon_theme_load_icon(icon_theme, "text-x-generic", 16, GTK_ICON_LOOKUP_USE_BUILTIN, NULL); 601 | gtk_tree_store_append(repo_tree_store, &child, &toplevel); 602 | gtk_tree_store_set( 603 | repo_tree_store, 604 | &child, 605 | FILTERS_COL_ICON, icon, 606 | FILTERS_COL_TITLE, group->name, 607 | FILTERS_COL_MASK, HIDE_NONE, 608 | FILTERS_COL_DB, db, 609 | FILTERS_COL_GROUP, group, 610 | -1 611 | ); 612 | g_object_unref(icon); 613 | } 614 | alpm_list_free(group_list); 615 | } 616 | 617 | icon = gtk_icon_theme_load_icon(icon_theme, "drive-harddisk", 16, GTK_ICON_LOOKUP_USE_BUILTIN, NULL); 618 | gtk_tree_store_append(repo_tree_store, &toplevel, NULL); 619 | gtk_tree_store_set( 620 | repo_tree_store, 621 | &toplevel, 622 | FILTERS_COL_ICON, icon, 623 | FILTERS_COL_TITLE, _("Foreign"), 624 | FILTERS_COL_MASK, HIDE_NATIVE, 625 | -1 626 | ); 627 | g_object_unref(icon); 628 | } 629 | 630 | static void unselect_package(void) 631 | { 632 | GtkTreePath *path; 633 | GtkTreeSelection *selection; 634 | 635 | /* reset tree view cursor to top */ 636 | path = gtk_tree_path_new_from_indices(0, -1); 637 | gtk_tree_view_set_cursor( 638 | main_window_gui.package_treeview, 639 | path, 640 | NULL, 641 | FALSE 642 | ); 643 | gtk_tree_path_free(path); 644 | 645 | /* if any package list row is selected then unselect it */ 646 | selection = gtk_tree_view_get_selection(main_window_gui.package_treeview); 647 | gtk_tree_selection_unselect_all(selection); 648 | 649 | /* clear any open package details */ 650 | show_package(NULL); 651 | } 652 | 653 | static void block_signal_repo_treeview_selection(gboolean block) 654 | { 655 | GtkTreeSelection *selection = gtk_tree_view_get_selection(main_window_gui.repo_treeview); 656 | 657 | if (block) { 658 | g_signal_handler_block(selection, repo_selchange_handler_id); 659 | } else { 660 | g_signal_handler_unblock(selection, repo_selchange_handler_id); 661 | } 662 | } 663 | 664 | static void block_signal_package_treeview_selection(gboolean block) 665 | { 666 | GtkTreeSelection *selection = gtk_tree_view_get_selection(main_window_gui.package_treeview); 667 | 668 | if (block) { 669 | g_signal_handler_block(selection, pkg_selchange_handler_id); 670 | } else { 671 | g_signal_handler_unblock(selection, pkg_selchange_handler_id); 672 | } 673 | } 674 | 675 | static void block_signal_search_changed(gboolean block) 676 | { 677 | if (block) { 678 | g_signal_handler_block(main_window_gui.search_entry, search_changed_handler_id); 679 | } else { 680 | g_signal_handler_unblock(main_window_gui.search_entry, search_changed_handler_id); 681 | } 682 | } 683 | 684 | static void load_data(void) 685 | { 686 | /* block interactions that need data */ 687 | block_signal_repo_treeview_selection(TRUE); 688 | block_signal_package_treeview_selection(TRUE); 689 | block_signal_search_changed(TRUE); 690 | gtk_widget_set_sensitive(main_window_gui.refresh_button, FALSE); 691 | 692 | /* reset filters */ 693 | package_filters.status_filter = HIDE_NONE; 694 | package_filters.group = NULL; 695 | package_filters.db = NULL; 696 | package_filters.search_string = NULL; 697 | 698 | /* reset search entry */ 699 | gtk_entry_set_text(GTK_ENTRY(main_window_gui.search_entry), ""); 700 | 701 | /* close package view */ 702 | show_package(NULL); 703 | 704 | /* reset database */ 705 | database_free(); 706 | 707 | /* load repo tree */ 708 | gtk_tree_store_clear(main_window_gui.repo_tree_store); 709 | populate_db_tree_view(main_window_gui.repo_tree_store); 710 | 711 | /* load package list */ 712 | gtk_tree_view_set_model(main_window_gui.package_treeview, NULL); 713 | gtk_list_store_clear(main_window_gui.package_list_store); 714 | show_package_list(main_window_gui.package_list_store); 715 | gtk_tree_view_set_model( 716 | main_window_gui.package_treeview, 717 | GTK_TREE_MODEL(main_window_gui.package_list_model) 718 | ); 719 | 720 | /* unblock interactions */ 721 | gtk_widget_set_sensitive(main_window_gui.refresh_button, TRUE); 722 | block_signal_search_changed(FALSE); 723 | block_signal_repo_treeview_selection(FALSE); 724 | block_signal_package_treeview_selection(FALSE); 725 | } 726 | 727 | static void repo_row_selected(GtkTreeSelection *selection, gpointer user_data) 728 | { 729 | GtkTreeModel *repo_model; 730 | GtkTreeIter repo_iter; 731 | guint filters; 732 | alpm_db_t *db = NULL; 733 | alpm_group_t *group = NULL; 734 | 735 | if (gtk_tree_selection_get_selected(selection, &repo_model, &repo_iter)) { 736 | /* prevent selecting a different repo row while we're filtering */ 737 | block_signal_package_treeview_selection(TRUE); 738 | 739 | /* get row data from model */ 740 | gtk_tree_model_get( 741 | repo_model, 742 | &repo_iter, 743 | FILTERS_COL_MASK, &filters, 744 | FILTERS_COL_DB, &db, 745 | FILTERS_COL_GROUP, &group, 746 | -1 747 | ); 748 | 749 | /* set filters */ 750 | package_filters.status_filter = filters; 751 | package_filters.group = group; 752 | package_filters.db = db; 753 | 754 | /* trigger refilter of package list */ 755 | gtk_tree_model_filter_refilter(main_window_gui.package_list_model); 756 | 757 | /* release selection blocking */ 758 | block_signal_package_treeview_selection(FALSE); 759 | 760 | /* if any package list row is selected then unselect it */ 761 | unselect_package(); 762 | } 763 | } 764 | 765 | static gboolean is_package_filtered(const install_reason_t reason, const gchar *db_name, alpm_pkg_t *pkg) 766 | { 767 | if (package_filters.status_filter & HIDE_INSTALLED) { 768 | if (reason != PKG_REASON_NOT_INSTALLED) { 769 | return TRUE; 770 | } 771 | } 772 | 773 | if (package_filters.status_filter & HIDE_UNINSTALLED) { 774 | if (reason == PKG_REASON_NOT_INSTALLED) { 775 | return TRUE; 776 | } 777 | } 778 | 779 | if (package_filters.status_filter & HIDE_EXPLICIT) { 780 | if (reason == PKG_REASON_EXPLICIT) { 781 | return TRUE; 782 | } 783 | } 784 | 785 | if (package_filters.status_filter & HIDE_DEPEND) { 786 | if (reason == PKG_REASON_DEPEND) { 787 | return TRUE; 788 | } 789 | } 790 | 791 | if (package_filters.status_filter & HIDE_OPTION) { 792 | if (reason == PKG_REASON_OPTIONAL) { 793 | return TRUE; 794 | } 795 | } 796 | 797 | if (package_filters.status_filter & HIDE_ORPHAN) { 798 | if (reason == PKG_REASON_ORPHAN) { 799 | return TRUE; 800 | } 801 | } 802 | 803 | if (package_filters.status_filter & HIDE_NATIVE) { 804 | if (g_strcmp0(db_name, "local") != 0) { 805 | return TRUE; 806 | } 807 | } 808 | 809 | if (package_filters.status_filter & HIDE_FOREIGN) { 810 | if (g_strcmp0(db_name, "local") == 0) { 811 | return TRUE; 812 | } 813 | } 814 | 815 | if (package_filters.db != NULL) { 816 | if (g_strcmp0(db_name, alpm_db_get_name(package_filters.db)) != 0) { 817 | return TRUE; 818 | } 819 | } 820 | 821 | if (package_filters.group != NULL) { 822 | alpm_list_t *pkg_groups = alpm_pkg_get_groups(pkg); 823 | if (alpm_list_count(pkg_groups) == 0) { 824 | return TRUE; 825 | } else if (!alpm_list_find(pkg_groups, package_filters.group, group_cmp_find)) { 826 | return TRUE; 827 | } 828 | } 829 | 830 | if (package_filters.search_string != NULL) { 831 | if (g_strrstr(alpm_pkg_get_name(pkg), package_filters.search_string) == NULL) { 832 | return TRUE; 833 | } 834 | } 835 | 836 | /* no matches, row not filtered */ 837 | return FALSE; 838 | } 839 | 840 | static gboolean row_visible(GtkTreeModel *model, GtkTreeIter *iter, gpointer data) 841 | { 842 | install_reason_t reason; 843 | gchar *db_name; 844 | alpm_pkg_t *pkg; 845 | gboolean ret; 846 | 847 | /* get row data from model */ 848 | gtk_tree_model_get( 849 | model, 850 | iter, 851 | PACKAGES_COL_STATUS, &reason, 852 | PACAKGES_COL_REPO, &db_name, 853 | PACKAGES_COL_PKG, &pkg, 854 | -1 855 | ); 856 | 857 | /* check if the filters exclude this row */ 858 | ret = !is_package_filtered(reason, db_name, pkg); 859 | 860 | g_free(db_name); 861 | 862 | return ret; 863 | } 864 | 865 | static void on_search_changed(GtkSearchEntry *entry, gpointer user_data) 866 | { 867 | /* block search events to prevent repeated search invocations */ 868 | block_signal_search_changed(TRUE); 869 | 870 | /* prevent selecting a different repo row while we're filtering */ 871 | block_signal_package_treeview_selection(TRUE); 872 | 873 | /* set filters */ 874 | package_filters.status_filter = HIDE_NONE; 875 | package_filters.group = NULL; 876 | package_filters.db = NULL; 877 | package_filters.search_string = g_ascii_strdown(gtk_entry_get_text(GTK_ENTRY(entry)), -1); 878 | 879 | /* trigger refilter of package list */ 880 | gtk_tree_model_filter_refilter(main_window_gui.package_list_model); 881 | 882 | /* clean up */ 883 | g_free(package_filters.search_string); 884 | package_filters.search_string = NULL; 885 | 886 | /* if any package list row is selected then deselect it */ 887 | unselect_package(); 888 | 889 | /* release selection blocking */ 890 | block_signal_package_treeview_selection(FALSE); 891 | 892 | /* release search block */ 893 | block_signal_search_changed(FALSE); 894 | } 895 | 896 | static void activate_about(GSimpleAction *simple, GVariant *parameter, gpointer user_data) 897 | { 898 | show_about_dialog(main_window_gui.window); 899 | } 900 | 901 | static void activate_quit(GSimpleAction *simple, GVariant *parameter, gpointer user_data) 902 | { 903 | exit(EXIT_SUCCESS); 904 | } 905 | 906 | static GActionGroup *create_action_group(void) 907 | { 908 | const GActionEntry entries[] = { 909 | { "about", activate_about, NULL, NULL, NULL, { 0, 0, 0 } }, 910 | { "quit", activate_quit, NULL, NULL, NULL, { 0, 0, 0 } } 911 | }; 912 | 913 | GSimpleActionGroup *group; 914 | 915 | group = g_simple_action_group_new(); 916 | g_action_map_add_action_entries(G_ACTION_MAP(group), entries, G_N_ELEMENTS(entries), NULL); 917 | 918 | return G_ACTION_GROUP(group); 919 | } 920 | 921 | static GMenuModel *create_app_menu(void) 922 | { 923 | GMenu *section, *menu; 924 | 925 | menu = g_menu_new(); 926 | 927 | section = g_menu_new(); 928 | /* l10n: header menu items */ 929 | g_menu_insert(section, 0, _("About PacFinder"), "app.about"); 930 | g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); 931 | g_object_unref(section); 932 | 933 | section = g_menu_new(); 934 | g_menu_insert(section, 0, _("Quit"), "app.quit"); 935 | g_menu_append_section(menu, NULL, G_MENU_MODEL(section)); 936 | g_object_unref(section); 937 | 938 | return G_MENU_MODEL(menu); 939 | } 940 | 941 | static void create_main_menu(GtkWidget *menu_button) 942 | { 943 | gtk_menu_button_set_menu_model(GTK_MENU_BUTTON(menu_button), create_app_menu()); 944 | gtk_widget_insert_action_group(menu_button, "app", create_action_group()); 945 | } 946 | 947 | static void on_window_realize(GtkWindow *window) 948 | { 949 | GdkRectangle geometry; 950 | gint width1, width2, width3, width4; 951 | GtkTreeViewColumn *column; 952 | 953 | /* set window geometry */ 954 | geometry = get_saved_window_geometry(); 955 | gtk_window_set_default_size(window, geometry.width, geometry.height); 956 | if (geometry.x > -1 && geometry.y > -1) { 957 | gtk_window_move(window, geometry.x, geometry.y); 958 | } 959 | if (get_saved_window_state()) { 960 | gtk_window_maximize(window); 961 | } 962 | 963 | /* set paned positions */ 964 | gtk_paned_set_position(main_window_gui.hpaned, get_saved_left_width()); 965 | gtk_paned_set_position(main_window_gui.vpaned, get_saved_right_height()); 966 | 967 | /* set column widths */ 968 | get_saved_package_column_widths(&width1, &width2, &width3, &width4); 969 | column = gtk_tree_view_get_column(main_window_gui.package_treeview, PACKAGES_COL_NAME); 970 | gtk_tree_view_column_set_fixed_width(column, width1); 971 | column = gtk_tree_view_get_column(main_window_gui.package_treeview, PACKAGES_COL_VERSION); 972 | gtk_tree_view_column_set_fixed_width(column, width2); 973 | column = gtk_tree_view_get_column(main_window_gui.package_treeview, PACKAGES_COL_STATUS); 974 | gtk_tree_view_column_set_fixed_width(column, width3); 975 | column = gtk_tree_view_get_column(main_window_gui.package_treeview, PACAKGES_COL_REPO); 976 | gtk_tree_view_column_set_fixed_width(column, width4); 977 | } 978 | 979 | static gboolean on_window_configure(GtkWindow *window, GdkEventConfigure *event) 980 | { 981 | GdkRectangle geometry = { 0 }; 982 | gboolean maximized; 983 | 984 | gtk_window_get_size(window, &geometry.width, &geometry.height); 985 | gtk_window_get_position(window, &geometry.x, &geometry.y); 986 | maximized = gtk_window_is_maximized(window); 987 | 988 | if (!maximized) { 989 | set_saved_window_geometry(geometry); 990 | } 991 | set_saved_window_state(maximized); 992 | 993 | return GDK_EVENT_PROPAGATE; 994 | } 995 | 996 | static void on_window_destroy(GtkWindow *window) 997 | { 998 | settings_free(); 999 | database_free(); 1000 | } 1001 | 1002 | static void bind_events_to_window(GtkWindow *window) 1003 | { 1004 | g_signal_connect(window, "realize", G_CALLBACK(on_window_realize), NULL); 1005 | g_signal_connect(window, "configure-event", G_CALLBACK(on_window_configure), NULL); 1006 | g_signal_connect(window, "destroy", G_CALLBACK(on_window_destroy), NULL); 1007 | } 1008 | 1009 | static void save_package_column_widths(GtkTreeView *package_treeview) 1010 | { 1011 | GtkTreeViewColumn *column; 1012 | gint width1, width2, width3, width4; 1013 | 1014 | column = gtk_tree_view_get_column(package_treeview, PACKAGES_COL_NAME); 1015 | width1 = gtk_tree_view_column_get_width(column); 1016 | 1017 | column = gtk_tree_view_get_column(package_treeview, PACKAGES_COL_VERSION); 1018 | width2 = gtk_tree_view_column_get_width(column); 1019 | 1020 | column = gtk_tree_view_get_column(package_treeview, PACKAGES_COL_STATUS); 1021 | width3 = gtk_tree_view_column_get_width(column); 1022 | 1023 | column = gtk_tree_view_get_column(package_treeview, PACAKGES_COL_REPO); 1024 | width4 = gtk_tree_view_column_get_width(column); 1025 | 1026 | set_saved_package_column_widths(width1, width2, width3, width4); 1027 | } 1028 | 1029 | static gboolean on_paned_reposition(GtkWidget *widget, GdkEvent *event, gpointer user_data) 1030 | { 1031 | set_saved_left_width(gtk_paned_get_position(main_window_gui.hpaned)); 1032 | set_saved_right_height(gtk_paned_get_position(main_window_gui.vpaned)); 1033 | save_package_column_widths(main_window_gui.package_treeview); 1034 | 1035 | return FALSE; 1036 | } 1037 | 1038 | static void on_refresh_click(GtkWidget *widget, GdkEvent *event, gpointer user_data) 1039 | { 1040 | load_data(); 1041 | } 1042 | 1043 | static void bind_events_to_widgets(void) 1044 | { 1045 | GtkTreeSelection *selection; 1046 | 1047 | /* filter list item selected */ 1048 | selection = gtk_tree_view_get_selection(main_window_gui.repo_treeview); 1049 | repo_selchange_handler_id = g_signal_connect( 1050 | G_OBJECT(selection), 1051 | "changed", 1052 | G_CALLBACK(repo_row_selected), 1053 | NULL 1054 | ); 1055 | 1056 | /* package list item selected */ 1057 | selection = gtk_tree_view_get_selection(main_window_gui.package_treeview); 1058 | pkg_selchange_handler_id = g_signal_connect( 1059 | G_OBJECT(selection), 1060 | "changed", 1061 | G_CALLBACK(package_row_selected), 1062 | NULL 1063 | ); 1064 | 1065 | /* search entry changed */ 1066 | search_changed_handler_id = g_signal_connect( 1067 | main_window_gui.search_entry, 1068 | "search-changed", 1069 | G_CALLBACK(on_search_changed), 1070 | NULL 1071 | ); 1072 | 1073 | /* refresh button click */ 1074 | g_signal_connect( 1075 | main_window_gui.refresh_button, 1076 | "clicked", 1077 | G_CALLBACK(on_refresh_click), 1078 | NULL 1079 | ); 1080 | 1081 | /* paned position change */ 1082 | g_signal_connect( 1083 | main_window_gui.hpaned, 1084 | "accept-position", 1085 | G_CALLBACK(on_paned_reposition), 1086 | NULL 1087 | ); 1088 | g_signal_connect( 1089 | main_window_gui.hpaned, 1090 | "button-release-event", 1091 | G_CALLBACK(on_paned_reposition), 1092 | NULL 1093 | ); 1094 | 1095 | } 1096 | 1097 | void initialize_main_window(void) 1098 | { 1099 | create_main_menu(main_window_gui.menu_button); 1100 | 1101 | bind_events_to_window(main_window_gui.window); 1102 | bind_events_to_widgets(); 1103 | gtk_tree_model_filter_set_visible_func( 1104 | main_window_gui.package_list_model, 1105 | (GtkTreeModelFilterVisibleFunc)row_visible, 1106 | NULL, 1107 | NULL 1108 | ); 1109 | 1110 | load_data(); 1111 | 1112 | gtk_window_set_icon_name(main_window_gui.window, APPLICATION_ID); 1113 | gtk_widget_show_all(GTK_WIDGET(main_window_gui.window)); 1114 | } 1115 | -------------------------------------------------------------------------------- /src/window.h: -------------------------------------------------------------------------------- 1 | /* window.h 2 | * 3 | * Copyright 2022 Steven Benner 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef PF_WINDOW_H 19 | #define PF_WINDOW_H 20 | 21 | void initialize_main_window(void); 22 | 23 | #endif /* PF_WINDOW_H */ 24 | -------------------------------------------------------------------------------- /test/.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | /.deps/ 3 | /*.log 4 | /*.trs 5 | /test_suite 6 | -------------------------------------------------------------------------------- /test/Makefile.am: -------------------------------------------------------------------------------- 1 | check_PROGRAMS = test_suite 2 | 3 | test_suite_SOURCES = \ 4 | $(top_srcdir)/src/util.c \ 5 | $(top_srcdir)/src/util.h \ 6 | main.c \ 7 | test_util.c \ 8 | test_util.h 9 | 10 | test_suite_CPPFLAGS = \ 11 | -I$(top_srcdir)/src 12 | 13 | test_suite_CFLAGS = \ 14 | -Wall \ 15 | -Wextra \ 16 | -Wno-unused-parameter \ 17 | -DLOCALEDIR=\"$(localedir)\" \ 18 | @GTK_CFLAGS@ 19 | 20 | test_suite_LDADD = \ 21 | @GTK_LIBS@ 22 | 23 | LOG_DRIVER = env AM_TAP_AWK='$(AWK)' $(SHELL) $(top_srcdir)/build-aux/tap-driver.sh 24 | 25 | TESTS = test_suite 26 | -------------------------------------------------------------------------------- /test/main.c: -------------------------------------------------------------------------------- 1 | /* main.c 2 | * 3 | * Copyright 2022 Steven Benner 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include 19 | #include 20 | 21 | #include "test_util.h" 22 | 23 | int main(int argc, char *argv[]) 24 | { 25 | setlocale(LC_ALL, "C"); 26 | 27 | g_test_init(&argc, &argv, NULL); 28 | g_test_set_nonfatal_assertions(); 29 | 30 | test_util(); 31 | 32 | return g_test_run(); 33 | } 34 | -------------------------------------------------------------------------------- /test/test_util.c: -------------------------------------------------------------------------------- 1 | /* test_util.c 2 | * 3 | * Copyright 2022 Steven Benner 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #include "test_util.h" 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "util.h" 25 | 26 | static void test_list_to_string(void) 27 | { 28 | alpm_list_t *empty_list = NULL; 29 | alpm_list_t *list_of_one = NULL; 30 | alpm_list_t *list_of_three = NULL; 31 | 32 | list_of_one = alpm_list_add(list_of_one, "item 1"); 33 | 34 | list_of_three = alpm_list_add(list_of_three, "item 1"); 35 | list_of_three = alpm_list_add(list_of_three, "item 2"); 36 | list_of_three = alpm_list_add(list_of_three, "item 3"); 37 | 38 | gchar *null_items = list_to_string(empty_list); 39 | gchar *one_item = list_to_string(list_of_one); 40 | gchar *three_items = list_to_string(list_of_three); 41 | 42 | g_assert_cmpstr(null_items, ==, ""); 43 | g_assert_cmpstr(one_item, ==, "item 1"); 44 | g_assert_cmpstr(three_items, ==, "item 1, item 2, item 3"); 45 | 46 | g_free(null_items); 47 | g_free(one_item); 48 | g_free(three_items); 49 | alpm_list_free(empty_list); 50 | alpm_list_free(list_of_one); 51 | alpm_list_free(list_of_three); 52 | } 53 | 54 | static void test_human_readable_size(void) 55 | { 56 | gchar *zero = human_readable_size(0); 57 | gchar *ten_b = human_readable_size(10); 58 | gchar *max_b = human_readable_size(1023); 59 | gchar *one_k = human_readable_size(1024); 60 | gchar *one_point_five_k = human_readable_size(1024 * 1.5); 61 | gchar *one_m = human_readable_size(pow(1024, 2)); 62 | gchar *one_point_nine_nine_m = human_readable_size((pow(1024, 2) * 2) - (1024 * 10)); 63 | gchar *one_g = human_readable_size(pow(1024, 3)); 64 | gchar *one_t = human_readable_size(pow(1024, 4)); 65 | gchar *one_p = human_readable_size(pow(1024, 5)); 66 | 67 | g_assert_cmpstr(zero, ==, "0 B"); 68 | g_assert_cmpstr(ten_b, ==, "10 B"); 69 | g_assert_cmpstr(max_b, ==, "1023 B"); 70 | g_assert_cmpstr(one_k, ==, "1.00 KiB"); 71 | g_assert_cmpstr(one_point_five_k, ==, "1.50 KiB"); 72 | g_assert_cmpstr(one_m, ==, "1.00 MiB"); 73 | g_assert_cmpstr(one_point_nine_nine_m, ==, "1.99 MiB"); 74 | g_assert_cmpstr(one_g, ==, "1.00 GiB"); 75 | g_assert_cmpstr(one_t, ==, "1.00 TiB"); 76 | g_assert_cmpstr(one_p, ==, "1024.00 TiB"); 77 | 78 | g_free(zero); 79 | g_free(ten_b); 80 | g_free(max_b); 81 | g_free(one_k); 82 | g_free(one_point_five_k); 83 | g_free(one_m); 84 | g_free(one_point_nine_nine_m); 85 | g_free(one_g); 86 | g_free(one_t); 87 | g_free(one_p); 88 | } 89 | 90 | static void test_strtrunc_dep_desc(void) 91 | { 92 | gchar *pkg_empty = strtrunc_dep_desc(""); 93 | gchar *pkg_simple = strtrunc_dep_desc("pacfinder"); 94 | gchar *pkg_version = strtrunc_dep_desc("pacfinder>=1.1"); 95 | gchar *pkg_verepoch = strtrunc_dep_desc("pacfinder=1:1.1"); 96 | gchar *pkg_desc = strtrunc_dep_desc("pacfinder: Package explorer"); 97 | gchar *pkg_desc_colon = strtrunc_dep_desc("pacfinder: Package explorer: Arch"); 98 | gchar *pkg_desc_epoch = strtrunc_dep_desc("pacfinder=1:1.1: Package explorer"); 99 | gchar *pkg_desc_epoch_colon = strtrunc_dep_desc("pacfinder=1:1.1: Package explorer: Arch"); 100 | gchar *prov_version = strtrunc_dep_desc("libalpm.so>=13"); 101 | 102 | g_assert_cmpstr(pkg_empty, ==, ""); 103 | g_assert_cmpstr(pkg_simple, ==, "pacfinder"); 104 | g_assert_cmpstr(pkg_version, ==, "pacfinder>=1.1"); 105 | g_assert_cmpstr(pkg_verepoch, ==, "pacfinder=1:1.1"); 106 | g_assert_cmpstr(pkg_desc, ==, "pacfinder"); 107 | g_assert_cmpstr(pkg_desc_colon, ==, "pacfinder"); 108 | g_assert_cmpstr(pkg_desc_epoch, ==, "pacfinder=1:1.1"); 109 | g_assert_cmpstr(pkg_desc_epoch_colon, ==, "pacfinder=1:1.1"); 110 | g_assert_cmpstr(prov_version, ==, "libalpm.so>=13"); 111 | 112 | g_free(pkg_empty); 113 | g_free(pkg_simple); 114 | g_free(pkg_version); 115 | g_free(pkg_verepoch); 116 | g_free(pkg_desc); 117 | g_free(pkg_desc_colon); 118 | g_free(pkg_desc_epoch); 119 | g_free(pkg_desc_epoch_colon); 120 | g_free(prov_version); 121 | } 122 | 123 | void test_util(void) 124 | { 125 | g_test_add_func("/util/list_to_string", test_list_to_string); 126 | g_test_add_func("/util/human_readable_size", test_human_readable_size); 127 | g_test_add_func("/util/strtrunc_dep_desc", test_strtrunc_dep_desc); 128 | } 129 | -------------------------------------------------------------------------------- /test/test_util.h: -------------------------------------------------------------------------------- 1 | /* test_util.h 2 | * 3 | * Copyright 2022 Steven Benner 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #ifndef PF_TEST_UTIL_H 19 | #define PF_TEST_UTIL_H 20 | 21 | void test_util(void); 22 | 23 | #endif /* PF_TEST_UTIL_H */ 24 | --------------------------------------------------------------------------------