A => .gitignore +144 -0
@@ 1,144 @@
+### Python template
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+/.idea
+/build
+/himitsu_gtk/keyring.gresource
+*.glade~<
\ No newline at end of file
A => LICENSE +165 -0
@@ 1,165 @@
+ GNU LESSER GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+ This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+ 0. Additional Definitions.
+
+ As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+ "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+ An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+ A "Combined Work" is a work produced by combining or linking an
+Application with the Library. The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+ The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+ The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+ 1. Exception to Section 3 of the GNU GPL.
+
+ You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+ 2. Conveying Modified Versions.
+
+ If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+ a) under this License, provided that you make a good faith effort to
+ ensure that, in the event an Application does not supply the
+ function or data, the facility still operates, and performs
+ whatever part of its purpose remains meaningful, or
+
+ b) under the GNU GPL, with none of the additional permissions of
+ this License applicable to that copy.
+
+ 3. Object Code Incorporating Material from Library Header Files.
+
+ The object code form of an Application may incorporate material from
+a header file that is part of the Library. You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+ a) Give prominent notice with each copy of the object code that the
+ Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the object code with a copy of the GNU GPL and this license
+ document.
+
+ 4. Combined Works.
+
+ You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+ a) Give prominent notice with each copy of the Combined Work that
+ the Library is used in it and that the Library and its use are
+ covered by this License.
+
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
+ document.
+
+ c) For a Combined Work that displays copyright notices during
+ execution, include the copyright notice for the Library among
+ these notices, as well as a reference directing the user to the
+ copies of the GNU GPL and this license document.
+
+ d) Do one of the following:
+
+ 0) Convey the Minimal Corresponding Source under the terms of this
+ License, and the Corresponding Application Code in a form
+ suitable for, and under terms that permit, the user to
+ recombine or relink the Application with a modified version of
+ the Linked Version to produce a modified Combined Work, in the
+ manner specified by section 6 of the GNU GPL for conveying
+ Corresponding Source.
+
+ 1) Use a suitable shared library mechanism for linking with the
+ Library. A suitable mechanism is one that (a) uses at run time
+ a copy of the Library already present on the user's computer
+ system, and (b) will operate properly with a modified version
+ of the Library that is interface-compatible with the Linked
+ Version.
+
+ e) Provide Installation Information, but only if you would otherwise
+ be required to provide such information under section 6 of the
+ GNU GPL, and only to the extent that such information is
+ necessary to install and execute a modified version of the
+ Combined Work produced by recombining or relinking the
+ Application with a modified version of the Linked Version. (If
+ you use option 4d0, the Installation Information must accompany
+ the Minimal Corresponding Source and Corresponding Application
+ Code. If you use option 4d1, you must provide the Installation
+ Information in the manner specified by section 6 of the GNU GPL
+ for conveying Corresponding Source.)
+
+ 5. Combined Libraries.
+
+ You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+ a) Accompany the combined library with a copy of the same work based
+ on the Library, uncombined with any other library facilities,
+ conveyed under the terms of this License.
+
+ b) Give prominent notice with the combined library that part of it
+ is a work based on the Library, and explaining where to find the
+ accompanying uncombined form of the same work.
+
+ 6. Revised Versions of the GNU Lesser General Public License.
+
+ The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+ If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
A => README.md +16 -0
@@ 1,16 @@
+Keyring
+=======
+
+A graphical frontend for managing a [Himitsu](https://himitsustore.org) key store.
+
+![Screenshot of the control application](https://brixitcdn.net/metainfo/keyring.png)
+
+
+Installing
+----------
+
+```shell-session
+$ meson build
+$ meson compile -C build
+$ sudo meson install -C build
+```
A => build-aux/meson/postinstall.py +18 -0
@@ 1,18 @@
+#!/usr/bin/env python3
+
+from os import environ, path
+from subprocess import call
+
+prefix = environ.get('MESON_INSTALL_PREFIX', '/usr/local')
+datadir = path.join(prefix, 'share')
+destdir = environ.get('DESTDIR', '')
+
+if not destdir:
+ print('Updating icon cache...')
+ call(['gtk-update-icon-cache', '-qtf', path.join(datadir, 'icons', 'hicolor')])
+
+ print('Updating desktop database')
+ call(['update-desktop-database', '-q', path.join(datadir, 'applications')])
+
+ print('Compiling GSettings schemas...')
+ call(['glib-compile-schemas', path.join(datadir, 'glib-2.0', 'schemas')])
A => data/meson.build +10 -0
@@ 1,10 @@
+install_data('nl.brixit.Keyring.desktop',
+ install_dir: join_paths(get_option('datadir'), 'applications'),
+)
+
+install_data(['nl.brixit.Keyring.appdata.xml'],
+ install_dir : get_option('datadir') / 'metainfo')
+
+install_data('nl.brixit.Keyring.svg',
+ install_dir: join_paths(get_option('datadir'), 'icons/hicolor/scalable/apps')
+)
A => data/nl.brixit.Keyring.appdata.xml +33 -0
@@ 1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 2021 Martijn Braam -->
+<component type="desktop-application">
+ <id>nl.brixit.Keyring</id>
+ <metadata_license>CC0-1.0</metadata_license>
+ <project_license>GPL-3.0</project_license>
+ <name>Keyring</name>
+ <summary>Keyring frontend for the Himitsu keystore</summary>
+ <description>
+ Graphical frontend to manage the keys in your Himitsu keystore
+ </description>
+ <launchable type="desktop-id">nl.brixit.Keyring.desktop</launchable>
+ <provides>
+ <binary>himitsu-keyring</binary>
+ </provides>
+ <screenshots>
+ <screenshot type="default">
+ <image>http://brixitcdn.net/metainfo/keyring.png</image>
+ </screenshot>
+ </screenshots>
+ <url type="homepage">https://sr.ht/~martijnbraam/keyring</url>
+ <url type="bugtracker">https://todo.sr.ht/~martijnbraam/keyring</url>
+ <content_rating type="oars-1.1"/>
+ <releases>
+ <release version="0.1.0" date="2022-06-13">
+ <description>
+ <p>
+ Initial keyring implementation
+ </p>
+ </description>
+ </release>
+ </releases>
+</component>
A => data/nl.brixit.Keyring.desktop +13 -0
@@ 1,13 @@
+[Desktop Entry]
+Name=Keyring
+Encoding=UTF-8
+Version=1.0
+Type=Application
+Terminal=false
+Exec=himitsu-keyring
+GenericName=Password manager
+Icon=nl.brixit.Keyring
+Categories=GTK;GNOME;Utility
+X-DBUS-ServiceName=nl.brixit.Keyring
+StartupNotify=true
+X-Purism-FormFactor=Workstation;Mobile;
A => data/nl.brixit.Keyring.svg +340 -0
@@ 1,340 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+ viewBox="0 0 128 128"
+ style="display:inline;enable-background:new"
+ version="1.0"
+ id="svg11300"
+ height="128"
+ width="128"
+ sodipodi:docname="nl.brixit.Keyring.svg"
+ inkscape:version="1.2 (dc2aedaf03, 2022-05-15)"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <sodipodi:namedview
+ id="namedview61"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ inkscape:showpageshadow="2"
+ inkscape:pageopacity="0.0"
+ inkscape:pagecheckerboard="0"
+ inkscape:deskcolor="#d1d1d1"
+ showgrid="false"
+ inkscape:zoom="1.8216808"
+ inkscape:cx="193.50262"
+ inkscape:cy="116.92499"
+ inkscape:window-width="1920"
+ inkscape:window-height="1043"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer9" />
+ <title
+ id="title4162">Adwaita Icon Template</title>
+ <defs
+ id="defs3">
+ <linearGradient
+ id="linearGradient1296">
+ <stop
+ id="stop1292"
+ offset="0"
+ style="stop-color:#77767b;stop-opacity:1;" />
+ <stop
+ style="stop-color:#c0bfbc;stop-opacity:1"
+ offset="0.17589436"
+ id="stop1300" />
+ <stop
+ id="stop1302"
+ offset="0.4092612"
+ style="stop-color:#77767b;stop-opacity:1" />
+ <stop
+ id="stop1294"
+ offset="1"
+ style="stop-color:#3d3846;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient969">
+ <stop
+ style="stop-color:#f6f5f4;stop-opacity:1"
+ offset="0"
+ id="stop963" />
+ <stop
+ id="stop965"
+ offset="0.25731823"
+ style="stop-color:#ffffff;stop-opacity:1" />
+ <stop
+ style="stop-color:#c0bfbc;stop-opacity:1"
+ offset="0.5999999"
+ id="stop1085" />
+ <stop
+ id="stop1087"
+ offset="0.70312482"
+ style="stop-color:#f6f5f4;stop-opacity:1" />
+ <stop
+ style="stop-color:#f6f5f4;stop-opacity:1"
+ offset="1"
+ id="stop967" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient1040">
+ <stop
+ style="stop-color:#c0bfbc;stop-opacity:1"
+ offset="0"
+ id="stop1036" />
+ <stop
+ style="stop-color:#f6f5f4;stop-opacity:1"
+ offset="1"
+ id="stop1038" />
+ </linearGradient>
+ <linearGradient
+ y2="249.87819"
+ x2="67.121834"
+ y1="238.30762"
+ x1="78.692398"
+ gradientTransform="translate(55.100502,0.07106726)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient1986"
+ xlink:href="#linearGradient969" />
+ <linearGradient
+ y2="70.300697"
+ x2="85.886963"
+ y1="67.679771"
+ x1="88.507896"
+ gradientTransform="translate(55.769701,171.28412)"
+ gradientUnits="userSpaceOnUse"
+ id="linearGradient1988"
+ xlink:href="#linearGradient1040" />
+ <linearGradient
+ gradientUnits="userSpaceOnUse"
+ y2="268"
+ x2="198"
+ y1="268"
+ x1="142"
+ id="linearGradient1039"
+ xlink:href="#linearGradient1296" />
+ </defs>
+ <metadata
+ id="metadata4">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>GNOME Design Team</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:source />
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
+ <dc:title>Adwaita Icon Template</dc:title>
+ <dc:subject>
+ <rdf:Bag />
+ </dc:subject>
+ <dc:date />
+ <dc:rights>
+ <cc:Agent>
+ <dc:title />
+ </cc:Agent>
+ </dc:rights>
+ <dc:publisher>
+ <cc:Agent>
+ <dc:title />
+ </cc:Agent>
+ </dc:publisher>
+ <dc:identifier />
+ <dc:relation />
+ <dc:language />
+ <dc:coverage />
+ <dc:description />
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title />
+ </cc:Agent>
+ </dc:contributor>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Reproduction" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#Distribution" />
+ <cc:requires
+ rdf:resource="http://creativecommons.org/ns#Notice" />
+ <cc:requires
+ rdf:resource="http://creativecommons.org/ns#Attribution" />
+ <cc:permits
+ rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://creativecommons.org/ns#ShareAlike" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ transform="translate(0,-172)"
+ style="display:inline"
+ id="layer1">
+ <g
+ style="display:inline"
+ id="layer9">
+ <rect
+ style="display:inline;opacity:1;fill:#009900;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;enable-background:new"
+ id="rect1027"
+ width="112"
+ height="63.999977"
+ x="8"
+ y="224"
+ rx="8"
+ ry="8" />
+ <g
+ style="display:inline;opacity:1;stroke:#a2ffa5;stroke-width:0.93541437;enable-background:new;fill:#8ff0a4;stroke-opacity:1;fill-opacity:1"
+ id="g1256"
+ transform="matrix(1,0,0,1.1428571,-4.8522949e-8,-22.857143)">
+ <path
+ style="fill:#8ff0a4;fill-rule:evenodd;stroke:#a2ffa5;stroke-width:1.87082875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+ d="m 27,230 v 14"
+ id="path1164" />
+ <path
+ id="path1166"
+ d="m 32,230 v 14"
+ style="fill:#8ff0a4;fill-rule:evenodd;stroke:#a2ffa5;stroke-width:3.7416575;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
+ <path
+ id="path1168"
+ d="m 37,230 v 14"
+ style="fill:#8ff0a4;fill-rule:evenodd;stroke:#a2ffa5;stroke-width:1.87082875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
+ <path
+ style="fill:#8ff0a4;fill-rule:evenodd;stroke:#a2ffa5;stroke-width:1.87082875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+ d="m 41,230 v 14"
+ id="path1170" />
+ <path
+ style="fill:#8ff0a4;fill-rule:evenodd;stroke:#a2ffa5;stroke-width:3.7416575;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+ d="m 46,230 v 14"
+ id="path1172" />
+ <path
+ id="path1174"
+ d="m 56,230 v 14"
+ style="fill:#8ff0a4;fill-rule:evenodd;stroke:#a2ffa5;stroke-width:3.7416575;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
+ <path
+ style="fill:#8ff0a4;fill-rule:evenodd;stroke:#a2ffa5;stroke-width:1.87082875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+ d="m 51,230 v 14"
+ id="path1176" />
+ <path
+ id="path1178"
+ d="m 63,230 v 14"
+ style="fill:#8ff0a4;fill-rule:evenodd;stroke:#a2ffa5;stroke-width:5.61248589;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
+ <path
+ id="path1180"
+ d="m 69,230 v 14"
+ style="fill:#8ff0a4;fill-rule:evenodd;stroke:#a2ffa5;stroke-width:1.87082875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
+ <path
+ id="path1182"
+ d="m 73,230 v 14"
+ style="fill:#8ff0a4;fill-rule:evenodd;stroke:#a2ffa5;stroke-width:1.87082875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
+ <path
+ style="fill:#8ff0a4;fill-rule:evenodd;stroke:#a2ffa5;stroke-width:1.87082875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+ d="m 77,230 v 14"
+ id="path1184" />
+ <path
+ style="fill:#8ff0a4;fill-rule:evenodd;stroke:#a2ffa5;stroke-width:3.7416575;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+ d="m 82,230 v 14"
+ id="path1186" />
+ <path
+ id="path1188"
+ d="m 87,230 v 14"
+ style="fill:#8ff0a4;fill-rule:evenodd;stroke:#a2ffa5;stroke-width:1.87082875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
+ <path
+ style="fill:#8ff0a4;fill-rule:evenodd;stroke:#a2ffa5;stroke-width:1.87082875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+ d="m 99,230 v 14"
+ id="path1190" />
+ <path
+ style="fill:#8ff0a4;fill-rule:evenodd;stroke:#a2ffa5;stroke-width:5.61248589;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+ d="m 93,230 v 14"
+ id="path1192" />
+ <path
+ id="path1194"
+ d="m 103,230 v 14"
+ style="fill:#8ff0a4;fill-rule:evenodd;stroke:#a2ffa5;stroke-width:1.87082875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1" />
+ <path
+ style="fill:#8ff0a4;fill-rule:evenodd;stroke:#a2ffa5;stroke-width:1.87082875;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
+ d="m 107,230 v 14"
+ id="path1196" />
+ </g>
+ <g
+ style="display:inline;fill:#8ff0a4;enable-background:new"
+ id="g1130"
+ transform="translate(-4.8522949e-8,12)">
+ <path
+ style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:medium;line-height:1000%;font-family:Cantarell;-inkscape-font-specification:'Cantarell Ultra-Bold';letter-spacing:2px;word-spacing:0px;fill:#8ff0a4;fill-opacity:1;stroke:none;stroke-width:0.74077076px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 22.21582,254 1.197265,5.06836 -3.90039,-3.2793 -1.453125,2.5918 5.009765,1.6582 -5.042968,1.58985 1.433593,2.64062 3.955078,-3.30469 L 22.21582,266 h 3.410156 l -1.101562,-4.95898 3.833984,3.31445 1.476562,-2.61914 -4.978515,-1.68555 5.117187,-1.42578 -1.453125,-2.61914 -4.033203,3.08398 L 25.624023,254 Z"
+ id="path1940" />
+ <path
+ id="path1056"
+ d="m 38.21582,254 1.197265,5.06836 -3.90039,-3.2793 -1.453125,2.5918 5.009765,1.6582 -5.042968,1.58985 1.433593,2.64062 3.955078,-3.30469 L 38.21582,266 h 3.410156 l -1.101562,-4.95898 3.833984,3.31445 1.476562,-2.61914 -4.978515,-1.68555 5.117187,-1.42578 -1.453125,-2.61914 -4.033203,3.08398 L 41.624023,254 Z"
+ style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:medium;line-height:1000%;font-family:Cantarell;-inkscape-font-specification:'Cantarell Ultra-Bold';letter-spacing:2px;word-spacing:0px;fill:#8ff0a4;fill-opacity:1;stroke:none;stroke-width:0.74077076px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:medium;line-height:1000%;font-family:Cantarell;-inkscape-font-specification:'Cantarell Ultra-Bold';letter-spacing:2px;word-spacing:0px;fill:#8ff0a4;fill-opacity:1;stroke:none;stroke-width:0.74077076px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 54.21582,254 1.197265,5.06836 -3.90039,-3.2793 -1.453125,2.5918 5.009765,1.6582 -5.042968,1.58985 1.433593,2.64062 3.955078,-3.30469 L 54.21582,266 h 3.410156 l -1.101562,-4.95898 3.833984,3.31445 1.476562,-2.61914 -4.978515,-1.68555 5.117187,-1.42578 -1.453125,-2.61914 -4.033203,3.08398 L 57.624023,254 Z"
+ id="path1062" />
+ <path
+ id="path1068"
+ d="m 70.21582,254 1.197265,5.06836 -3.90039,-3.2793 -1.453125,2.5918 5.009765,1.6582 -5.042968,1.58985 1.433593,2.64062 3.955078,-3.30469 L 70.21582,266 h 3.410156 l -1.101562,-4.95898 3.833984,3.31445 1.476562,-2.61914 -4.978515,-1.68555 5.117187,-1.42578 -1.453125,-2.61914 -4.033203,3.08398 L 73.624023,254 Z"
+ style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:medium;line-height:1000%;font-family:Cantarell;-inkscape-font-specification:'Cantarell Ultra-Bold';letter-spacing:2px;word-spacing:0px;fill:#8ff0a4;fill-opacity:1;stroke:none;stroke-width:0.74077076px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ <path
+ style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:medium;line-height:1000%;font-family:Cantarell;-inkscape-font-specification:'Cantarell Ultra-Bold';letter-spacing:2px;word-spacing:0px;fill:#8ff0a4;fill-opacity:1;stroke:none;stroke-width:0.74077076px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 86.21582,254 1.197265,5.06836 -3.90039,-3.2793 -1.453125,2.5918 5.009765,1.6582 -5.042968,1.58985 1.433593,2.64062 3.955078,-3.30469 L 86.21582,266 h 3.410156 l -1.101562,-4.95898 3.833984,3.31445 1.476562,-2.61914 -4.978515,-1.68555 5.117187,-1.42578 -1.453125,-2.61914 -4.033203,3.08398 L 89.624023,254 Z"
+ id="path1074" />
+ <path
+ id="path1080"
+ d="m 102.21582,254 1.19726,5.06836 -3.900385,-3.2793 -1.453125,2.5918 5.00976,1.6582 -5.042963,1.58985 1.433593,2.64062 3.95508,-3.30469 L 102.21582,266 h 3.41016 l -1.10157,-4.95898 3.83399,3.31445 1.47656,-2.61914 -4.97851,-1.68555 5.11718,-1.42578 -1.45312,-2.61914 -4.03321,3.08398 L 105.62402,254 Z"
+ style="font-style:normal;font-variant:normal;font-weight:800;font-stretch:normal;font-size:medium;line-height:1000%;font-family:Cantarell;-inkscape-font-specification:'Cantarell Ultra-Bold';letter-spacing:2px;word-spacing:0px;fill:#8ff0a4;fill-opacity:1;stroke:none;stroke-width:0.74077076px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
+ </g>
+ <rect
+ ry="7.9999995"
+ rx="8"
+ y="200"
+ x="8"
+ height="40"
+ width="112"
+ id="rect954"
+ style="display:inline;opacity:1;fill:#009900;fill-opacity:1;stroke:none;stroke-width:0.27003086;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;enable-background:new" />
+ <rect
+ y="214"
+ x="8"
+ height="18"
+ width="112"
+ id="rect961"
+ style="display:inline;opacity:1;fill:#241f31;fill-opacity:1;stroke:none;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;paint-order:normal;enable-background:new" />
+ <path
+ id="path1138"
+ d="m 22,242 -7.2,6 7.2,6 z"
+ style="display:inline;fill:#8ff0a4;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;enable-background:new" />
+ <path
+ id="path3965"
+ style="fill:#bfbfbf;fill-opacity:1;stroke:none;stroke-opacity:1"
+ d="M 43.631743,179.84241 A 27.670397,27.670397 0 0 0 15.961821,207.51233 27.670397,27.670397 0 0 0 43.631743,235.1842 27.670397,27.670397 0 0 0 71.301665,207.51233 27.670397,27.670397 0 0 0 43.631743,179.84241 Z m -0.511719,9.21289 A 18.7078,18.7078 0 0 1 61.829009,207.76428 18.7078,18.7078 0 0 1 43.120024,226.47131 18.7078,18.7078 0 0 1 24.412993,207.76428 18.7078,18.7078 0 0 1 43.120024,189.0553 Z" />
+ <rect
+ style="fill:#ff9900;fill-opacity:1;stroke:none;stroke-opacity:1"
+ id="rect3855"
+ width="67.28389"
+ height="58.518543"
+ x="10.617647"
+ y="206.50064"
+ ry="5.7396421" />
+ <ellipse
+ style="fill:#ff6600;fill-opacity:1;stroke:none;stroke-opacity:1"
+ id="path3961"
+ cx="45.456844"
+ cy="237.08504"
+ rx="7.4120846"
+ ry="7.4808183" />
+ </g>
+ </g>
+</svg>
A => himitsu_gtk/__init__.py +0 -0
A => himitsu_gtk/__main__.py +46 -0
@@ 1,46 @@
+import argparse
+import os
+import gi
+
+from himitsu_gtk.window import KeyringWindow
+
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk, Gio
+
+gi.require_version('Handy', '1')
+from gi.repository import Handy
+
+
+class KeyringApplication(Gtk.Application):
+ def __init__(self, application_id, flags, args, version):
+ Gtk.Application.__init__(self, application_id=application_id, flags=flags)
+ self.connect("activate", self.new_window)
+ self.args = args
+ self.version = version
+
+ def new_window(self, *args):
+ KeyringWindow(self, self.args, self.version)
+
+
+def main(version):
+ Handy.init()
+
+ parser = argparse.ArgumentParser(description="Himitsu GTK keyring tool")
+ args = parser.parse_args()
+
+ if os.path.isfile('himitsu_gtk/keyring.gresource'):
+ print("Using resources from cwd/talk")
+ resource = Gio.resource_load("himitsu_gtk/keyring.gresource")
+ Gio.Resource._register(resource)
+ elif os.path.isfile('keyring.gresource'):
+ print("Using resources from cwd")
+ resource = Gio.resource_load("keyring.gresource")
+ Gio.Resource._register(resource)
+
+ app = KeyringApplication("nl.brixit.Keyring", Gio.ApplicationFlags.FLAGS_NONE, args=args,
+ version=version)
+ app.run()
+
+
+if __name__ == '__main__':
+ main('development')
A => himitsu_gtk/himitsu-keyring.in +22 -0
@@ 1,22 @@
+#!@PYTHON@
+
+import os
+import sys
+import signal
+import gettext
+
+VERSION = '@VERSION@'
+pkgdatadir = '@pkgdatadir@'
+localedir = '@localedir@'
+
+sys.path.insert(1, pkgdatadir)
+signal.signal(signal.SIGINT, signal.SIG_DFL)
+
+if __name__ == '__main__':
+ import gi
+ from gi.repository import Gio
+ resource = Gio.Resource.load(os.path.join(pkgdatadir, 'keyring.gresource'))
+ resource._register()
+
+ from himitsu_gtk import __main__
+ sys.exit(__main__.main(VERSION))
A => himitsu_gtk/himitsu.py +87 -0
@@ 1,87 @@
+import os
+import socket
+
+from himitsu_gtk.key import Key
+
+
+class Himitsu:
+ def __init__(self, path=None):
+ self.path = path
+ if self.path is None:
+ self.path = os.path.join(os.getenv('XDG_RUNTIME_DIR'), 'himitsu')
+
+ if not os.path.exists(self.path):
+ raise RuntimeError(f"Himitsu socket not found: {self.path}")
+
+ def _readline(self, s):
+ buffer = b''
+ while True:
+ char = s.recv(1)
+ if char == b'\n':
+ return buffer
+ buffer += char
+
+ def _cmd(self, cmd):
+ s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ s.connect(self.path)
+ s.sendall(f"{cmd}\n".encode())
+
+ result = []
+ while True:
+ line = self._readline(s).decode('utf-8')
+ if line == 'end':
+ break
+ result.append(Key(line))
+ return result
+
+ def query(self, key=None, decrypt=False):
+ cmd = ["query"]
+ if decrypt:
+ cmd += ["-d"]
+ if isinstance(key, Key):
+ for pair in key:
+ if pair.key == "key":
+ continue
+ if pair.private:
+ continue
+ if pair.value is None:
+ continue
+ cmd += [f"{pair.key}={pair.value}"]
+ elif isinstance(key, dict):
+ for k in key:
+ cmd += [f"{k}={key[k]}"]
+ return self._cmd(" ".join(cmd))
+
+ def delete(self, key):
+ cmd = ["del"]
+ if isinstance(key, Key):
+ for pair in key:
+ if pair.key == "key":
+ continue
+ if pair.private:
+ continue
+ if pair.value is None:
+ continue
+ cmd += [f"{pair.key}={pair.value}"]
+ elif isinstance(key, dict):
+ for k in key:
+ cmd += [f"{k}={key[k]}"]
+ return self._cmd(" ".join(cmd))
+
+ def add(self, key):
+ cmd = ["add"]
+ if isinstance(key, Key):
+ for pair in key:
+ if pair.key == "key":
+ continue
+ cmd += [f"{pair.key}={pair.value}"]
+ elif isinstance(key, dict):
+ for k in key:
+ cmd += [f"{k}={key[k]}"]
+ return self._cmd(" ".join(cmd))
+
+
+if __name__ == '__main__':
+ test = Himitsu()
+ for k in test.query():
+ print(k)
A => himitsu_gtk/key.py +77 -0
@@ 1,77 @@
+import shlex
+from io import StringIO
+
+
+class Key:
+ def __init__(self, key=None):
+ self._pairs = []
+ if key is not None:
+ self._parse(key)
+
+ def _parse(self, key):
+ lexer = shlex.shlex(StringIO(key))
+ lexer.commenters = ''
+
+ while True:
+ key = lexer.get_token()
+ if key == lexer.eof:
+ break
+
+ optional = False
+ private = False
+ while True:
+ tok = lexer.get_token()
+ if tok == "=":
+ lexer.whitespace_split = True
+ value = lexer.get_token()
+ lexer.whitespace_split = False
+ value = shlex.split(value)[0]
+ self._pairs.append(Pair(key, value, optional, private))
+ break
+ elif tok == "!":
+ private = True
+ elif tok == "?":
+ optional = True
+ else:
+ lexer.push_token(tok)
+ self._pairs.append(Pair(key, None, optional, private))
+ break
+
+ def add(self, key, value=None, optional=False, private=False):
+ pair = Pair(key, value, optional, private)
+ self._pairs.append(pair)
+
+ def __str__(self):
+ res = ""
+ for i in range(len(self._pairs)):
+ pair = self._pairs[i]
+ res += pair.key
+ if pair.optional:
+ res += "?"
+ if pair.private:
+ res += "!"
+ if pair.value is not None:
+ res += "=" + shlex.quote(pair.value)
+ if i + 1 < len(self._pairs):
+ res += " "
+ return res
+
+ def __repr__(self):
+ return f"<{str(self)}>"
+
+ def __iter__(self):
+ return iter(self._pairs)
+
+ def __getitem__(self, key):
+ for pair in self._pairs:
+ if pair.key == key:
+ return pair.value
+ return None
+
+
+class Pair:
+ def __init__(self, key, value=None, optional=False, private=False):
+ self.key = key
+ self.value = value
+ self.optional = optional
+ self.private = private
A => himitsu_gtk/keyring.gresource.xml +7 -0
@@ 1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<gresources>
+ <gresource prefix="/nl/brixit/Keyring">
+ <file>ui/main.glade</file>
+ <file>ui/style.css</file>
+ </gresource>
+</gresources>
A => himitsu_gtk/meson.build +37 -0
@@ 1,37 @@
+pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
+moduledir = join_paths(pkgdatadir, 'himitsu_gtk')
+gnome = import('gnome')
+
+gnome.compile_resources('keyring',
+ 'keyring.gresource.xml',
+ gresource_bundle: true,
+ install: true,
+ install_dir: pkgdatadir,
+)
+
+python = import('python')
+
+conf = configuration_data()
+conf.set('PYTHON', python.find_installation('python3').path())
+conf.set('VERSION', meson.project_version())
+conf.set('localedir', join_paths(get_option('prefix'), get_option('localedir')))
+conf.set('pkgdatadir', pkgdatadir)
+
+configure_file(
+ input: 'himitsu-keyring.in',
+ output: 'himitsu-keyring',
+ configuration: conf,
+ install: true,
+ install_mode: 'rwxr-xr-x',
+ install_dir: get_option('bindir')
+)
+
+sources = [
+ '__init__.py',
+ '__main__.py',
+ 'window.py',
+ 'himitsu.py',
+ 'key.py',
+]
+
+install_data(sources, install_dir: moduledir)
A => himitsu_gtk/ui/main.glade +377 -0
@@ 1,377 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.38.2 -->
+<interface>
+ <requires lib="gtk+" version="3.24"/>
+ <requires lib="libhandy" version="1.6"/>
+ <object class="GtkMenu" id="addmenu">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <object class="GtkMenuItem" id="basic">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">New key</property>
+ <property name="use-underline">True</property>
+ <signal name="activate" handler="on_add_clicked" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="web">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">New web key</property>
+ <property name="use-underline">True</property>
+ <signal name="activate" handler="on_add_clicked" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ <object class="GtkApplicationWindow" id="window">
+ <property name="can-focus">False</property>
+ <property name="default-width">800</property>
+ <property name="default-height">600</property>
+ <signal name="destroy" handler="on_main_window_destroy" swapped="no"/>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="HdyLeaflet" id="main_leaflet">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <child>
+ <object class="GtkBox" id="keys">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkSearchEntry" id="search">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="margin-start">8</property>
+ <property name="margin-end">8</property>
+ <property name="margin-top">8</property>
+ <property name="margin-bottom">8</property>
+ <property name="primary-icon-name">edit-find-symbolic</property>
+ <property name="primary-icon-activatable">False</property>
+ <property name="primary-icon-sensitive">False</property>
+ <signal name="search-changed" handler="on_search_changed" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkListBox" id="keylist">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <signal name="row-activated" handler="on_keyrow_activated" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparator">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <style>
+ <class name="sidebar"/>
+ </style>
+ </object>
+ </child>
+ <child>
+ <object class="GtkStack" id="mainstack">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="hexpand">True</property>
+ <child>
+ <object class="HdyStatusPage">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="icon-name">nl.brixit.Keyring</property>
+ <property name="title" translatable="yes">Keyring</property>
+ <property name="description" translatable="yes">Select a key on the left for key information</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="name">nokey</property>
+ <property name="title" translatable="yes">No key</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="hexpand">True</property>
+ <child>
+ <object class="GtkButton" id="decrypt">
+ <property name="label" translatable="yes">Decrypt</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <signal name="clicked" handler="on_decrypt_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkButton" id="delete">
+ <property name="label" translatable="yes">Delete</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <signal name="clicked" handler="on_delete_clicked" swapped="no"/>
+ <style>
+ <class name="destructive-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack-type">end</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkListBox" id="keyinfo">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="selection-mode">none</property>
+ <property name="activate-on-single-click">False</property>
+ <signal name="row-activated" handler="on_key_row_activate" swapped="no"/>
+ <style>
+ <class name="content"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="label" translatable="yes">Double click on a row to copy the value to the clipboard</property>
+ <property name="wrap">True</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">keyinfo</property>
+ <property name="title" translatable="yes">page0</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="shadow-type">none</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="margin-start">12</property>
+ <property name="margin-end">12</property>
+ <property name="margin-top">12</property>
+ <property name="margin-bottom">12</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">12</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">Add new key</property>
+ <style>
+ <class name="heading"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkListBox" id="addkey">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="selection-mode">none</property>
+ <property name="activate-on-single-click">False</property>
+ <style>
+ <class name="content"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="spacing">8</property>
+ <child>
+ <object class="GtkButton" id="add_key_save">
+ <property name="label" translatable="yes">Save</property>
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="receives-default">True</property>
+ <signal name="clicked" handler="on_save_clicked" swapped="no"/>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack-type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="name">add</property>
+ <property name="title" translatable="yes">Add key</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="titlebar">
+ <object class="GtkHeaderBar">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="title" translatable="yes">Keyring</property>
+ <property name="show-close-button">True</property>
+ <child>
+ <object class="GtkMenuButton">
+ <property name="visible">True</property>
+ <property name="can-focus">True</property>
+ <property name="focus-on-click">False</property>
+ <property name="receives-default">True</property>
+ <property name="draw-indicator">True</property>
+ <property name="popup">addmenu</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="can-focus">False</property>
+ <property name="label" translatable="yes">Add</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
A => himitsu_gtk/ui/style.css +3 -0
@@ 1,3 @@
+.content row > box {
+ margin: 8px;
+}<
\ No newline at end of file
A => himitsu_gtk/window.py +331 -0
@@ 1,331 @@
+import json
+import os
+
+import gi
+
+from himitsu_gtk.himitsu import Himitsu
+from himitsu_gtk.key import Key
+
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk, GLib, GObject, Gio, Gdk, GLib, GdkPixbuf
+
+gi.require_version('Handy', '1')
+from gi.repository import Handy
+
+
+class KeyringWindow:
+ def __init__(self, application, args, version):
+ self.application = application
+ self.args = args
+ self.version = version
+
+ Handy.init()
+
+ builder = Gtk.Builder()
+ builder.add_from_resource('/nl/brixit/Keyring/ui/main.glade')
+ builder.connect_signals(self)
+ self.builder = builder
+ css = Gio.resources_lookup_data("/nl/brixit/Keyring/ui/style.css", 0)
+ self.provider = Gtk.CssProvider()
+ self.provider.load_from_data(css.get_data())
+
+ self.provider = Gtk.CssProvider()
+ self.provider.load_from_data(css.get_data())
+
+ self.window = builder.get_object("window")
+ self.window.set_application(self.application)
+ self.mainstack = builder.get_object("mainstack")
+ self.keylist = builder.get_object("keylist")
+ self.keyinfo = builder.get_object("keyinfo")
+ self.addkey = builder.get_object("addkey")
+
+ searchbox = builder.get_object('search')
+ searchbox.grab_focus()
+
+ self.apply_css(self.window, self.provider)
+
+ self.window.show()
+ self.key = None
+
+ self.himitsu = Himitsu()
+ self.load_keys()
+ self.add_entry_key = []
+ self.add_entry_val = []
+ self.search = []
+
+ self.keylist.set_filter_func(self.listbox_filter, None)
+
+ Gtk.main()
+
+ def apply_css(self, widget, provider):
+ Gtk.StyleContext.add_provider(widget.get_style_context(),
+ provider,
+ Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
+
+ if isinstance(widget, Gtk.Container):
+ widget.forall(self.apply_css, provider)
+
+ def empty(self, widget):
+ for child in widget:
+ widget.remove(child)
+
+ def load_keys(self):
+ keys = self.himitsu.query()
+ self.empty(self.keylist)
+
+ grouped = {}
+ for key in keys:
+ proto = key['proto']
+
+ if proto not in grouped:
+ grouped[proto] = []
+ grouped[proto].append(key)
+
+ for group in grouped:
+ if group is None:
+ continue
+ self._make_group(group.title(), grouped[group])
+
+ if None in grouped:
+ self._make_group("Unsorted", grouped[None])
+
+ self.keylist.show_all()
+
+ def _make_group(self, name, keys):
+ row = Gtk.ListBoxRow()
+ row.set_selectable(False)
+ row.set_activatable(False)
+ label = Gtk.Label(label=name)
+ label.get_style_context().add_class('heading')
+ label.set_margin_top(8)
+ row.add(label)
+ self.keylist.add(row)
+ row = Gtk.ListBoxRow()
+ row.set_selectable(False)
+ row.set_activatable(False)
+ row.add(Gtk.Separator())
+ self.keylist.add(row)
+
+ for key in keys:
+ name, detail = self._describe_key(key)
+ box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
+ label = Gtk.Label(label=name, xalign=0.0)
+ label.set_line_wrap(True)
+ box.add(label)
+ if detail is not None:
+ detaillabel = Gtk.Label(label=detail, xalign=0.0)
+ detaillabel.set_line_wrap(True)
+ detaillabel.get_style_context().add_class('dim-label')
+ box.add(detaillabel)
+ box.set_margin_top(8)
+ box.set_margin_bottom(8)
+ box.set_margin_start(10)
+ box.set_margin_end(10)
+ row = Gtk.ListBoxRow()
+ row.add(box)
+ row.key = key
+ self.keylist.add(row)
+
+ def _describe_key(self, key):
+ detail = []
+ for pair in key:
+ if pair.value is None:
+ continue
+ if pair.private:
+ continue
+ if pair.optional:
+ continue
+ detail.append(f'{pair.key}={pair.value}')
+ detail = ' '.join(detail)
+
+ if key['host'] is not None:
+ label = key['host']
+ if key['username']:
+ label = f'{key["username"]}@{label}'
+ return label, detail
+ else:
+ return detail, None
+
+ def show_key(self):
+ self.empty(self.keyinfo)
+ for pair in self.key:
+ if pair.key == 'key':
+ continue
+
+ row = Gtk.Box(spacing=6)
+ row.set_margin_top(8)
+ row.set_margin_bottom(8)
+ row.set_margin_start(8)
+ row.set_margin_end(8)
+ lbr = Gtk.ListBoxRow()
+ lbr.add(row)
+ lbr.pair = pair
+ self.keyinfo.insert(lbr, -1)
+
+ name = Gtk.Label(pair.key, xalign=0.0)
+ name.get_style_context().add_class("heading")
+ row.pack_start(name, True, True, 0)
+
+ if pair.value is not None:
+ value = Gtk.Label(pair.value, xalign=1.0)
+ value.get_style_context().add_class("value")
+ row.pack_start(value, True, True, 0)
+ elif pair.private:
+ value = Gtk.Label("(not shown)", xalign=1.0)
+ context = value.get_style_context()
+ context.add_class("value")
+ context.add_class("secret")
+ context.add_class("dim-label")
+ row.pack_start(value, True, True, 0)
+ else:
+ value = Gtk.Label("(empty)", xalign=1.0)
+ context = value.get_style_context()
+ context.add_class("value")
+ context.add_class("empty")
+ context.add_class("dim-label")
+ row.pack_start(value, True, True, 0)
+ self.keyinfo.show_all()
+
+ def on_keyrow_activated(self, widget, row):
+ self.mainstack.set_visible_child_name('keyinfo')
+ self.key = row.key
+ self.show_key()
+
+ def on_decrypt_clicked(self, widget, *args):
+ result = self.himitsu.query(self.key, decrypt=True)
+ self.key = result[0]
+ self.show_key()
+
+ def on_delete_clicked(self, widget, *args):
+ dialog = Gtk.MessageDialog(
+ transient_for=self.window,
+ flags=0,
+ message_type=Gtk.MessageType.QUESTION,
+ buttons=Gtk.ButtonsType.YES_NO,
+ text="Are you sure you want to remove this key?",
+ )
+ dialog.format_secondary_text(str(self.key))
+ response = dialog.run()
+ if response == Gtk.ResponseType.YES:
+ self.himitsu.delete(self.key)
+ self.load_keys()
+ self.mainstack.set_visible_child_name('nokey')
+ dialog.destroy()
+
+ def on_key_row_activate(self, widget, row):
+ pair = row.pair
+ if pair.value is not None:
+ value = pair.value
+ elif pair.private:
+ result = self.himitsu.query(self.key, decrypt=True)
+ value = result[0][pair.key]
+ else:
+ return
+
+ clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
+ clipboard.set_text(value, -1)
+
+ def addkey_add_placeholder(self, name=None, value=None):
+ row = Gtk.ListBoxRow()
+ box = Gtk.Box(spacing=6)
+ row.add(box)
+
+ keyentry = Gtk.Entry()
+ if name:
+ keyentry.set_text(name)
+ keyentry.connect('key-press-event', self.on_add_keypress)
+ self.add_entry_key.append(keyentry)
+
+ valentry = Gtk.Entry()
+ if value:
+ valentry.set_text(value)
+ valentry.connect('key-press-event', self.on_add_keypress)
+ self.add_entry_val.append(valentry)
+
+ box.pack_start(keyentry, False, False, 0)
+ box.pack_end(valentry, True, True, 0)
+ self.addkey.insert(row, -1)
+
+ def on_add_clicked(self, widget, *args):
+ name = Gtk.Buildable.get_name(widget)
+ self.mainstack.set_visible_child_name('add')
+ self.empty(self.addkey)
+ self.add_entry_key = []
+ self.add_entry_val = []
+ if name == 'web':
+ self.addkey_add_placeholder('proto', 'web')
+ self.addkey_add_placeholder('host')
+ self.addkey_add_placeholder('username')
+ self.addkey_add_placeholder('password!')
+ else:
+ self.addkey_add_placeholder('proto')
+
+ self.addkey_add_placeholder()
+ self.addkey.show_all()
+
+ if name == 'basic':
+ self.add_entry_val[0].grab_focus()
+ elif name == 'web':
+ self.add_entry_val[1].grab_focus()
+
+ def on_add_keypress(self, *args):
+ has_empty = False
+ for entry in self.add_entry_key:
+ if entry.get_text() == "":
+ has_empty = True
+ if not has_empty:
+ self.addkey_add_placeholder()
+ self.addkey_add_placeholder()
+ self.addkey.show_all()
+
+ def on_save_clicked(self, widget, *args):
+ newkey = Key()
+ for i, key in enumerate(self.add_entry_key):
+ key = key.get_text()
+ val = self.add_entry_val[i].get_text()
+
+ if key.strip() == "":
+ continue
+
+ private = False
+ if key.endswith('!'):
+ private = True
+
+ newkey.add(key, val, private=private)
+ self.himitsu.add(newkey)
+ self.load_keys()
+
+ def on_search_changed(self, widget):
+ query = widget.get_text()
+ component = query.split()
+ self.search = []
+ for c in component:
+ if '=' in c:
+ key, val = c.split('=', maxsplit=1)
+ self.search.append((key, val))
+ else:
+ self.search.append(c)
+ self.keylist.invalidate_filter()
+
+ def listbox_filter(self, row, user_data):
+ if len(self.search) == 0:
+ return True
+
+ if not hasattr(row, 'key'):
+ return True
+ key = row.key
+ for component in self.search:
+ if isinstance(component, tuple):
+ val = key[component[0]]
+ if val != component[1]:
+ return False
+ else:
+ for pair in key:
+ if pair.value is not None and component in pair.value:
+ break
+ else:
+ return False
+ return True
+
+ def on_main_window_destroy(self, widget):
+ Gtk.main_quit()
A => meson.build +11 -0
@@ 1,11 @@
+project('keyring',
+ version: '0.1.0',
+ meson_version: '>= 0.50.0',
+ default_options: ['warning_level=2'])
+
+dependency('libhandy-1', version: '>=1.0.0')
+
+subdir('data')
+subdir('himitsu_gtk')
+
+meson.add_install_script('build-aux/meson/postinstall.py')