├── .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 |
18 |
--------------------------------------------------------------------------------
/data/icons/src/com.stevenbenner.pacfinder.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
115 |
--------------------------------------------------------------------------------
/data/icons/src/status-icons.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
15 |
--------------------------------------------------------------------------------
/data/resources/icon-explicit.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/data/resources/icon-installed.svg:
--------------------------------------------------------------------------------
1 |
2 |
7 |
--------------------------------------------------------------------------------
/data/resources/icon-optional.svg:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/data/resources/icon-orphan.svg:
--------------------------------------------------------------------------------
1 |
2 |
8 |
--------------------------------------------------------------------------------
/data/resources/icon-uninstalled.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
--------------------------------------------------------------------------------