Skip to content
Commits on Source (131)
include: 'https://gitlab.gnome.org/GNOME/citemplates/raw/master/flatpak/flatpak_ci_initiative.yml'
variables:
BUNDLE: 'seahorse.flatpak'
GIT_SUBMODULE_STRATEGY: recursive
stages:
- review
- test
- deploy
style-check:
stage: review
script:
- ./.gitlab/ci/style-check.sh
artifacts:
expire_in: 1 week
name: "style-check-junit-report"
when: always
reports:
junit: style-check-junit-report.xml
paths:
- "style-check-junit-report.xml"
flatpak:
image: registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:master
variables:
MANIFEST_PATH: 'build-aux/org.gnome.seahorse.ApplicationDevel.json'
FLATPAK_MODULE: "seahorse"
RUNTIME_REPO: "https://nightly.gnome.org/gnome-nightly.flatpakrepo"
APP_ID: "org.gnome.seahorse.ApplicationDevel"
extends: .flatpak
nightly:
extends: '.publish_nightly'
dependencies: ['flatpak']
needs: ['flatpak']
#!/usr/bin/env bash
#
# junit-report.sh: JUnit report helpers
#
# Source this file into your CI scripts to get a nice JUnit report file which
# can be shown in the GitLab UI.
JUNIT_REPORT_TESTS_FILE=$(mktemp)
# We need this to make sure we don't send funky stuff into the XML report,
# making it invalid XML (and thus unparsable by CI)
function escape_xml() {
echo "$1" | sed -e 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/"/\&quot;/g; s/'"'"'/\&#39;/g'
}
# Append a failed test case with the given name and message
function append_failed_test_case() {
test_name="$1"
test_message="$2"
# Escape both fields before putting them into the xml
test_name_esc="$(escape_xml "$test_name")"
test_message_esc="$(escape_xml "$test_message")"
echo "<testcase name=\"$test_name_esc\">" >> $JUNIT_REPORT_TESTS_FILE
echo " <failure message=\"$test_message_esc\"/>" >> $JUNIT_REPORT_TESTS_FILE
echo "</testcase>" >> $JUNIT_REPORT_TESTS_FILE
# Also output to stderr, so it shows up in the job output
echo >&2 "Test '$test_name' failed: $test_message"
}
# Append a successful test case with the given name
function append_passed_test_case() {
test_name="$1"
test_name_esc="$(escape_xml "$test_name")"
echo "<testcase name=\"$test_name_esc\"></testcase>" >> $JUNIT_REPORT_TESTS_FILE
# Also output to stderr, so it shows up in the job output
echo >&2 "Test '$test_name' succeeded"
}
# Aggregates the test cases into a proper JUnit report XML file
function generate_junit_report() {
junit_report_file="$1"
testsuite_name="$2"
num_tests=$(fgrep '<testcase' -- "$JUNIT_REPORT_TESTS_FILE" | wc -l)
num_failures=$(fgrep '<failure' -- "$JUNIT_REPORT_TESTS_FILE" | wc -l )
echo Generating JUnit report \"$(pwd)/$junit_report_file\" with $num_tests tests and $num_failures failures.
cat > $junit_report_file << __EOF__
<?xml version="1.0" encoding="utf-8"?>
<testsuites tests="$num_tests" errors="0" failures="$num_failures">
<testsuite name="$testsuite_name" tests="$num_tests" errors="0" failures="$num_failures" skipped="0">
$(< $JUNIT_REPORT_TESTS_FILE)
</testsuite>
</testsuites>
__EOF__
}
# Returns a non-zero exit status if any of the tests in the given JUnit report failed
# You probably want to call this at the very end of your script.
function check_junit_report() {
junit_report_file="$1"
! fgrep -q '<failure' -- "$junit_report_file"
}
#!/usr/bin/env python3
# Turns a Meson testlog.json file into a JUnit XML report
#
# Copyright 2019 GNOME Foundation
#
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# Original author: Emmanuele Bassi
import argparse
import datetime
import json
import os
import sys
import xml.etree.ElementTree as ET
aparser = argparse.ArgumentParser(description='Turns a Meson test log into a JUnit report')
aparser.add_argument('--project-name', metavar='NAME',
help='The project name',
default='unknown')
aparser.add_argument('--job-id', metavar='ID',
help='The job ID for the report',
default='Unknown')
aparser.add_argument('--branch', metavar='NAME',
help='Branch of the project being tested',
default='master')
aparser.add_argument('--output', metavar='FILE',
help='The output file, stdout by default',
type=argparse.FileType('w', encoding='UTF-8'),
default=sys.stdout)
aparser.add_argument('infile', metavar='FILE',
help='The input testlog.json, stdin by default',
type=argparse.FileType('r', encoding='UTF-8'),
default=sys.stdin)
args = aparser.parse_args()
outfile = args.output
testsuites = ET.Element('testsuites')
testsuites.set('id', '{}/{}'.format(args.job_id, args.branch))
testsuites.set('package', args.project_name)
testsuites.set('timestamp', datetime.datetime.utcnow().isoformat(timespec='minutes'))
suites = {}
for line in args.infile:
data = json.loads(line)
(full_suite, unit_name) = data['name'].split(' / ')
(project_name, suite_name) = full_suite.split(':')
duration = data['duration']
return_code = data['returncode']
log = data['stdout']
unit = {
'suite': suite_name,
'name': unit_name,
'duration': duration,
'returncode': return_code,
'stdout': log,
}
units = suites.setdefault(suite_name, [])
units.append(unit)
for name, units in suites.items():
print('Processing suite {} (units: {})'.format(name, len(units)))
def if_failed(unit):
if unit['returncode'] != 0:
return True
return False
def if_succeded(unit):
if unit['returncode'] == 0:
return True
return False
successes = list(filter(if_succeded, units))
failures = list(filter(if_failed, units))
print(' - {}: {} pass, {} fail'.format(name, len(successes), len(failures)))
testsuite = ET.SubElement(testsuites, 'testsuite')
testsuite.set('name', '{}/{}'.format(args.project_name, name))
testsuite.set('tests', str(len(units)))
testsuite.set('errors', str(len(failures)))
testsuite.set('failures', str(len(failures)))
for unit in successes:
testcase = ET.SubElement(testsuite, 'testcase')
testcase.set('classname', '{}/{}'.format(args.project_name, unit['suite']))
testcase.set('name', unit['name'])
testcase.set('time', str(unit['duration']))
for unit in failures:
testcase = ET.SubElement(testsuite, 'testcase')
testcase.set('classname', '{}/{}'.format(args.project_name, unit['suite']))
testcase.set('name', unit['name'])
testcase.set('time', str(unit['duration']))
failure = ET.SubElement(testcase, 'failure')
failure.set('classname', '{}/{}'.format(args.project_name, unit['suite']))
failure.set('name', unit['name'])
failure.set('type', 'error')
failure.text = unit['stdout']
output = ET.tostring(testsuites, encoding='unicode')
outfile.write(output)
#!/bin/bash
set +e
xvfb-run -a -s "-screen 0 1024x768x24" \
flatpak build app \
meson test -C _build
exit_code=$?
python3 .gitlab-ci/meson-junit-report.py \
--project-name=seahorse \
--job-id "${CI_JOB_NAME}" \
--output "_build/${CI_JOB_NAME}-report.xml" \
_build/meson-logs/testlog.json
exit $exit_code
#!/usr/bin/env bash
#
# style-check.sh: Performs some basic style checks
# Source the JUnit helpers
scriptdir="$(dirname "$BASH_SOURCE")"
source "$scriptdir/junit-report.sh"
TESTNAME="No trailing whitespace"
source_dirs="common data gkr libseahorse pgp pkcs11 src ssh"
trailing_ws_occurrences="$(grep -nRI '[[:blank:]]$' $source_dirs)"
if [[ -z "$trailing_ws_occurrences" ]]; then
append_passed_test_case "$TESTNAME"
else
append_failed_test_case "$TESTNAME" \
$'Please remove the trailing whitespace at the following places:\n\n'"$trailing_ws_occurrences"
fi
generate_junit_report "$CI_JOB_NAME-junit-report.xml" "$CI_JOB_NAME"
check_junit_report "$CI_JOB_NAME-junit-report.xml"
<!--
Please check first if your problem isn't already listed in the issue tracker
and/or if it's fixed in the latest stable version.
-->
# Affected version
- Passwords and Keys (Seahorse) version: <!-- The version, or the commit if building from git -->
- Application provider: distribution / built from git / flatpak <!-- Delete the unwanted anwsers -->
- Related info:
<!-- If you can, please mention distro (+version), GPG/SSH version (if applicable) -->
# Steps to reproduce
<!--
Explain in detail the steps on how the issue can be reproduced.
-->
1.
2.
3.
# Current behavior
<!-- Describe the current behavior. -->
# Expected behavior
<!-- Describe the expected behavior. -->
# Additional information
<!--
Provide more information that could be relevant.
If the issue is a crash, provide a stack trace following the steps in:
https://wiki.gnome.org/Community/GettingInTouch/Bugzilla/GettingTraces
-->
<!-- Ignore the text under this line. -->
/label ~"1. Bug"
### Use cases
<!-- Describe what problem(s) the user is experiencing and that this request
is trying to solve. -->
### Desired behavior
<!-- Describe the desired functionality. -->
### Benefits of the solution
<!-- List the possible benefits of the solution and how it fits in the project. -->
### Possible drawbacks
<!--
Describe possible drawbacks of the feature and list how it could affect the
project i.e. UI discoverability, complexity, impact in more or less number of
users, etc.
-->
<!-- Ignore the text under this line. -->
/label ~"1. Feature"
seahorse 41.0
-------------
* appdata: State hardware support
* Updated translations
seahorse 41.beta
----------------
* pgp: Redesign of the PGP key properties dialog [!177,!179,!183]
* pgp: Also monitor .kbx in gpghomedir, making sure we get notified of new keys [!185,#171]
* pgp: Start adding (basic) unit tests [!184]
* pgp: Make pgp key id equality accept more values
* pgp: Make HKP keyid check more robust
* pgp: Fix expiration date for certain keys [!173,#327,#328]
* pgp: Allow building with GnuPG-2.3.x [!170]
* pgp: Fix segfault when built with LDAP disabled [!171,#321]
* ui: Follow GNOME HIG more closely [!178]
* ui: Datepicker now only pops down on double click [!175,#331]
* gkr: Fix warnings for items with NULL labels [#266]
* gkr: Don't show "Details" if no attributes are shown [!180]
* appdata: Updated screenshot
* Update README.md
* Updated translations
seahorse 40.0
-------------
* Fix paint issue when unlocking empty login keyring [#315,!153]
......
# Seahorse
Seahorse
========
Seahorse is a graphical interface for managing and using encryption keys.
Currently it supports PGP keys (using GPG/GPGME) and SSH keys. Its goal is to
provide an easy to use Key Management Tool, along with an easy to use interface
provide an easy to use key management tool, along with an easy to use interface
for encryption operations.
## Building
Building
--------
You can build and install Seahorse using [Meson]:
```sh
meson build
ninja -C build
ninja -C build install
meson setup _build
meson compile -C _build
meson install -C _build
```
## Contributing
Contributing
------------
You can browse the code, issues and more at Seahorse's [GitLab repository].
If you find a bug in Seahorse, please file an issue on the [issue tracker].
......@@ -28,14 +32,16 @@ teams in GNOME. Translators do not commit directly to Git, but are advised to
use our separate translation infrastructure instead. More info can be found at
the [translation project wiki page].
## More information
More information
----------------
Seahorse has its own web page on https://wiki.gnome.org/Apps/Seahorse.
To discuss issues with developers and other users, you can go to [GNOME's
Discourse instance] and use the "seahorse" tag, or join [#seahorse] on
irc.gnome.org.
## License
License
-------
Seahorse is released under the GPL. See [COPYING] for more info.
......
......@@ -48,7 +48,7 @@ namespace Progress {
[CCode (cheader_filename = "pgp/seahorse-pgp-backend.h")]
public class Pgp.Backend : GLib.Object, Gcr.Collection, Place {
public static void initialize();
public static void initialize(string? gpg_homedir);
public static unowned Pgp.Backend get();
public unowned GLib.ListModel get_remotes();
......
......@@ -72,6 +72,7 @@ public class Seahorse.DatePicker : Gtk.Box {
this.calendar.show_day_names = true;
this.calendar.show_heading = true;
this.calendar.day_selected.connect(on_calendar_day_selected);
this.calendar.day_selected_double_click.connect(on_calendar_day_selected_double_click);
this.calendar_popover.add(this.calendar);
}
......@@ -101,6 +102,9 @@ public class Seahorse.DatePicker : Gtk.Box {
calendar.get_date(out y, out m, out d);
// Note: GtkCalendar's months are [0,11] while GDateTime uses [1,12]
this.datetime = new DateTime.utc((int) y, (int) m + 1, (int) d, 0, 0, 0);
}
private void on_calendar_day_selected_double_click(Gtk.Calendar calendar) {
this.calendar_popover.popdown();
}
}
......@@ -4,6 +4,7 @@
* Copyright (C) 2003 Jacob Perkins
* Copyright (C) 2004-2005 Stefan Walter
* Copyright (C) 2011 Collabora Ltd.
* Copyright (C) 2020 Niels De Graef
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
......@@ -47,32 +48,11 @@ namespace Seahorse.Util {
dialog.destroy();
}
public string get_display_date_string (uint64 time)
{
if (time == 0)
return "";
var created_date = new DateTime.from_unix_utc((int64) time);
return created_date.format("%x");
}
// TODO: one we rely on GLib >= 2.54, we can use g_list_store_find
public bool list_store_find(GLib.ListStore store, GLib.Object item, out uint position) {
return list_store_find_with_equal_func (store, item, GLib.direct_equal, out position);
}
// TODO: one we rely on GLib >= 2.54, we can use g_list_store_find_with_equal_func
public bool list_store_find_with_equal_func(GLib.ListStore store,
GLib.Object item,
GLib.EqualFunc func,
out uint position) {
for (uint i = 0; i < store.get_n_items(); i++) {
if (func(store.get_item(i), item)) {
if (&position != null)
position = i;
return true;
}
}
return false;
public void toggle_action (GLib.SimpleAction action,
GLib.Variant? variant,
void* user_data) {
var old_state = action.get_state();
var new_state = new GLib.Variant.boolean(!old_state.get_boolean());
action.change_state(new_state);
}
}
......@@ -32,7 +32,10 @@ if appstream_util.found()
suite: 'data',
args: [
'validate-relax', '--nonet', appdata_file.full_path()
]
],
depends: [
appdata_file,
],
)
endif
......@@ -60,7 +63,10 @@ if desktop_file_validate.found()
suite: 'data',
args: [
desktop_file.full_path()
]
],
depends: [
desktop_file,
],
)
endif
......
......@@ -17,10 +17,35 @@
</description>
<screenshots>
<screenshot type="default">https://wiki.gnome.org/Apps/Seahorse?action=AttachFile&amp;do=get&amp;target=seahorse-screenshot.png</screenshot>
<screenshot type="default">
<image width="1024" height="576">https://gitlab.gnome.org/GNOME/seahorse/raw/master/data/screenshot-default.png</image>
</screenshot>
</screenshots>
<releases>
<release date="2021-09-29" version="41.0">
<description>
<p>
Version 41 is the next stable release after version 40, with the
following major improvements:
</p>
<ul>
<li>
A redesign of several dialogs to follow the new GNOME HIG more
closely. Especially the PGP key properties dialog got a major
overhaul
</li>
<li>
A long-standing bug has been fixed where newly imported GPG keys
would not automatically show up, unless the application was killed
and restarted
</li>
<li>Fixed an issue where the expiration date was set incorrectly</li>
<li>Add compatibility with the GnuPG version 2.3 series</li>
</ul>
<p>This release also updates translations in several languages.</p>
</description>
</release>
<release date="2021-04-11" version="40.0">
<description>
<p>
......@@ -159,9 +184,15 @@
</release>
</releases>
<provides>
<binary>seahorse</binary>
</provides>
<recommends>
<control>keyboard</control>
<control>pointing</control>
<control>touch</control>
</recommends>
<requires>
<display_length compare="ge">360</display_length>
</requires>
<url type="homepage">https://wiki.gnome.org/Apps/Seahorse</url>
<url type="bugtracker">https://gitlab.gnome.org/GNOME/seahorse/issues</url>
<url type="donation">http://www.gnome.org/friends/</url>
......@@ -169,6 +200,10 @@
<update_contact>nielsdegraef@gmail.com</update_contact>
<project_group>GNOME</project_group>
<developer_name>The GNOME Project</developer_name>
<provides>
<binary>seahorse</binary>
</provides>
<launchable type="desktop-id">org.gnome.seahorse.Application.desktop</launchable>
<translation type="gettext">seahorse</translation>
<content_rating type="oars-1.1">
......
......@@ -38,6 +38,8 @@
<file alias="seahorse-keyserver-sync.ui" preprocess="xml-stripblanks">../pgp/seahorse-keyserver-sync.ui</file>
<file alias="seahorse-pgp-private-key-properties.ui" preprocess="xml-stripblanks">../pgp/seahorse-pgp-private-key-properties.ui</file>
<file alias="seahorse-pgp-public-key-properties.ui" preprocess="xml-stripblanks">../pgp/seahorse-pgp-public-key-properties.ui</file>
<file alias="seahorse-pgp-subkey-list-box-row.ui" preprocess="xml-stripblanks">../pgp/seahorse-pgp-subkey-list-box-row.ui</file>
<file alias="seahorse-pgp-uid-list-box-row.ui" preprocess="xml-stripblanks">../pgp/seahorse-pgp-uid-list-box-row.ui</file>
<!-- PKCS#11 -->
<file alias="seahorse-pkcs11-generate.ui" preprocess="xml-stripblanks">../pkcs11/seahorse-pkcs11-generate.ui</file>
......
......@@ -30,15 +30,15 @@ public class Seahorse.Gkr.ItemProperties : Gtk.Dialog {
[GtkChild]
private unowned Gtk.Label type_field;
[GtkChild]
private unowned Gtk.Label details_label;
private unowned Hdy.PreferencesGroup details_group;
[GtkChild]
private unowned Gtk.Label details_box;
private unowned Gtk.ListBox details_box;
[GtkChild]
private unowned Gtk.Label server_label;
private unowned Hdy.ActionRow server_row;
[GtkChild]
private unowned Gtk.Label server_field;
[GtkChild]
private unowned Gtk.Label login_label;
private unowned Hdy.ActionRow login_row;
[GtkChild]
private unowned Gtk.Label login_field;
[GtkChild]
......@@ -159,11 +159,8 @@ public class Seahorse.Gkr.ItemProperties : Gtk.Dialog {
private void update_visibility() {
var use = this.item.use;
bool visible = use == Use.NETWORK || use == Use.WEB;
this.server_label.visible = visible;
this.server_field.visible = visible;
this.login_label.visible = visible;
this.login_field.visible = visible;
this.server_row.visible =
this.login_row.visible = (use == Use.NETWORK || use == Use.WEB);
}
private void update_server() {
......@@ -181,23 +178,34 @@ public class Seahorse.Gkr.ItemProperties : Gtk.Dialog {
}
private void update_details() {
var contents = new GLib.StringBuilder();
var attrs = this.item.attributes;
var iter = GLib.HashTableIter<string, string>(attrs);
bool any_details = false;
string key, value;
while (iter.next(out key, out value)) {
if (key.has_prefix("gkr:") || key.has_prefix("xdg:"))
continue;
contents.append_printf("<b>%s</b>: %s\n",
GLib.Markup.escape_text(key),
GLib.Markup.escape_text(value));
}
if (contents.len > 0) {
this.details_label.visible = true;
this.details_box.visible = true;
this.details_box.label = contents.str;
any_details = true;
var row = new Hdy.ActionRow();
row.title = key;
row.can_focus = false;
var label = new Gtk.Label (value);
label.xalign = 1;
label.selectable = true;
label.wrap = true;
label.wrap_mode = Pango.WrapMode.WORD_CHAR;
label.max_width_chars = 32;
row.add(label);
row.show_all();
this.details_box.insert(row, -1);
}
this.details_group.visible = any_details;
}
private async void save_password() {
......
......@@ -84,7 +84,7 @@ public class Item : Secret.Item, Deletable, Viewable {
owned get {
ensure_item_info ();
var result = new StringBuilder("");
result.append(Markup.escape_text(this._info.label));
result.append(Markup.escape_text(this._info.label ?? ""));
if (this._info.details != null && this._info.details != "") {
result.append("<span size='small' rise='0' foreground='#555555'>\n");
result.append(this._info.details);
......
......@@ -61,8 +61,13 @@ public class Seahorse.Gkr.KeyringProperties : Gtk.Dialog {
}
private void set_created(uint64 timestamp) {
this.created_label.label = (timestamp != 0)? Util.get_display_date_string((long) timestamp)
: _("Unknown date");
if (timestamp == 0) {
this.created_label.label = _("Unknown date");
return;
}
var datetime = new DateTime.from_unix_utc((int64) timestamp);
this.created_label.label = datetime.format("%x");
}
[GtkCallback]
......
......@@ -6,7 +6,6 @@
<property name="height_request">400</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Item Properties</property>
<property name="resizable">False</property>
<property name="window_position">center-on-parent</property>
<property name="type_hint">dialog</property>
<child>
......@@ -14,14 +13,11 @@
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="halign">center</property>
<property name="margin">12</property>
<property name="halign">fill</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<property name="border_width">0</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
</object>
<packing>
<property name="expand">False</property>
......@@ -30,64 +26,157 @@
</packing>
</child>
<child>
<object class="GtkGrid">
<object class="GtkScrolledWindow">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="row_spacing">12</property>
<property name="column_spacing">12</property>
<property name="hscrollbar_policy">never</property>
<property name="vscrollbar_policy">automatic</property>
<property name="propagate_natural_height">True</property>
<property name="propagate_natural_width">True</property>
<child>
<object class="GtkLabel">
<object class="GtkBox">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Description</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="description_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="width_chars">0</property>
<property name="max_width_chars">40</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Password</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="password_box_area">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="margin">18</property>
<property name="spacing">12</property>
<child>
<object class="GtkListBox">
<property name="visible">True</property>
<property name="selection_mode">none</property>
<style>
<class name="content"/>
</style>
<child>
<object class="HdyActionRow">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Description</property>
<property name="activatable_widget">description_field</property>
<child>
<object class="GtkEntry" id="description_field">
<property name="visible">True</property>
<property name="valign">center</property>
<property name="can_focus">True</property>
<property name="max_width_chars">32</property>
</object>
</child>
</object>
</child>
<child>
<object class="HdyActionRow">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Password</property>
<child>
<object class="GtkBox" id="password_box_area">
<property name="visible">True</property>
<property name="valign">center</property>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Copy</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_copy_button_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
<style>
<class name="linked"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="HdyActionRow">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes" comments="To translators: This is the noun not the verb.">Use</property>
<child>
<object class="GtkLabel" id="use_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
</child>
</object>
</child>
<child>
<object class="HdyActionRow">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Type</property>
<child>
<object class="GtkLabel" id="type_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
</child>
</object>
</child>
<child>
<object class="HdyActionRow" id="server_row">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Server</property>
<child>
<object class="GtkLabel" id="server_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
</child>
</object>
</child>
<child>
<object class="HdyActionRow" id="login_row">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">Login</property>
<child>
<object class="GtkLabel" id="login_field">
<property name="visible">True</property>
<property name="can_focus">True</property>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="HdyPreferencesGroup" id="details_group">
<property name="title" translatable="yes">Details</property>
<child>
<object class="GtkListBox" id="details_box">
<property name="visible">True</property>
<property name="selection_mode">none</property>
<property name="can_focus">False</property>
<style>
<class name="content"/>
</style>
</object>
</child>
</object>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Copy</property>
<property name="label" translatable="yes">Delete Password</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_copy_button_clicked" swapped="no"/>
<property name="halign">end</property>
<signal name="clicked" handler="on_delete_button_clicked" swapped="no"/>
<style>
<class name="destructive-action"/>
</style>
</object>
<packing>
<property name="expand">False</property>
......@@ -96,178 +185,9 @@
<property name="position">1</property>
</packing>
</child>
<style>
<class name="linked"/>
</style>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes" comments="To translators: This is the noun not the verb.">Use</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="use_field">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Type</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="type_field">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="server_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Server</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="server_field">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="login_label">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">end</property>
<property name="label" translatable="yes">Login</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="login_field">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">5</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="details_label">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="halign">end</property>
<property name="valign">start</property>
<property name="label" translatable="yes">Details</property>
<style>
<class name="dim-label"/>
</style>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">6</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="details_box">
<property name="can_focus">False</property>
<property name="no_show_all">True</property>
<property name="halign">start</property>
<property name="use_markup">True</property>
<property name="wrap">True</property>
<property name="wrap_mode">word-char</property>
<property name="selectable">True</property>
<property name="width_chars">40</property>
<property name="max_width_chars">50</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="top_attach">6</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="GtkButton">
<property name="label" translatable="yes">Delete Password</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="halign">end</property>
<signal name="clicked" handler="on_delete_button_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">1</property>
</packing>
</child>
</object>
</child>
......