From ac461747c3eca9caa6b8d6669b91b5ed220ad35b Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Fri, 5 Apr 2019 23:32:21 +1100 Subject: [PATCH 01/21] Depend on libhandy for the client build --- .gitlab-ci.yml | 32 ++++++++++++++++---------------- INSTALL | 39 +++++++++++++++------------------------ meson.build | 1 + org.gnome.Geary.json | 14 ++++++++++++++ src/client/meson.build | 2 ++ src/meson.build | 5 +++-- 6 files changed, 51 insertions(+), 42 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dd6221f9..f2b3c404 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -16,25 +16,25 @@ variables: INSTALL_CMD: ninja -v -C $BUILD_DIR install # Fedora packages - FEDORA_DEPS: vala - meson desktop-file-utils libcanberra-devel - folks-devel libgee-devel glib2-devel gmime-devel - gtk3-devel libnotify-devel sqlite-devel - webkitgtk4-devel libsecret-devel libxml2-devel - vala-tools gcr-devel enchant2-devel libunwind-devel - iso-codes-devel gnome-online-accounts-devel itstool - json-glib-devel + FEDORA_DEPS: meson vala + desktop-file-utils enchant2-devel folks-devel gcr-devel + glib2-devel gmime-devel gnome-online-accounts-devel + gtk3-devel iso-codes-devel json-glib-devel itstool + libcanberra-devel libgee-devel libhandy-devel + libnotify-devel libsecret-devel libunwind-devel + libxml2-devel sqlite-devel webkitgtk4-devel FEDORA_TEST_DEPS: Xvfb tar xz # Ubuntu packages - UBUNTU_DEPS: valac build-essential - meson desktop-file-utils libcanberra-dev - libfolks-dev libgee-0.8-dev libglib2.0-dev - libgmime-2.6-dev libgtk-3-dev libsecret-1-dev - libxml2-dev libnotify-dev libsqlite3-dev - libwebkit2gtk-4.0-dev libgcr-3-dev libenchant-dev - libunwind-dev iso-codes libgoa-1.0-dev itstool gettext - libmessaging-menu-dev libunity-dev libjson-glib-dev + UBUNTU_DEPS: meson build-essential valac + desktop-file-utils gettext iso-codes itstool + libcanberra-dev libenchant-dev libfolks-dev + libgcr-3-dev libgee-0.8-dev libglib2.0-dev + libgmime-2.6-dev libgoa-1.0-dev libgtk-3-dev + libhandy-0.0-dev libjson-glib-dev libmessaging-menu-dev + libnotify-dev libsecret-1-dev libsqlite3-dev + libunity-dev libunwind-dev libwebkit2gtk-4.0-dev + libxml2-dev UBUNTU_TEST_DEPS: xauth xvfb fedora: diff --git a/INSTALL b/INSTALL index 446319e8..a9ee7066 100644 --- a/INSTALL +++ b/INSTALL @@ -38,40 +38,31 @@ distribution's package repositories: Installing dependencies on Fedora --------------------------------- -Fedora 25 and later ships with the correct versions of the required -libraries. Install them by running this command: +Install them by running this command: - sudo yum install vala meson desktop-file-utils iso-codes-devel \ - libcanberra-devel folks-devel libgee-devel glib2-devel \ - gmime-devel gtk3-devel libnotify-devel sqlite-devel \ - webkitgtk4-devel libsecret-devel libxml2-devel vala-tools \ - gcr-devel enchant2-devel libunwind-devel json-glib-devel \ - gnome-online-accounts-devel itstool + sudo yum install meson vala \ + desktop-file-utils enchant2-devel folks-devel gcr-devel \ + glib2-devel gmime-devel gnome-online-accounts-devel gtk3-devel \ + iso-codes-devel json-glib-devel libcanberra-devel \ + libgee-devel libhandy-devel libnotify-devel libsecret-devel \ + libunwind-devel libxml2-devel sqlite-devel webkitgtk4-devel Installing dependencies on Ubuntu/Debian ---------------------------------------- -Ubuntu 17.10 (Artful) and later ships with the correct versions of the -required libraries. - -Ubuntu 16.04 LTS (Xenial) does not meet the minimum requirements, -users of that are encourage to use Geary 0.12 LTS instead. - -Debian 9 (Stretch) and later ships with the correct versions of the -required libraries. - Install them by running this command: - sudo apt-get install valac meson desktop-file-utils iso-codes \ - libcanberra-dev libfolks-dev libgee-0.8-dev libglib2.0-dev \ - libgmime-2.6-dev libgtk-3-dev libsecret-1-dev libxml2-dev \ - libnotify-dev libsqlite3-dev libwebkit2gtk-4.0-dev \ - libgcr-3-dev libenchant-dev libunwind-dev libgoa-1.0-dev \ - libjson-glib-dev itstool gettext + sudo apt-get install meson build-essential valac \ + desktop-file-utils iso-codes gettext libcanberra-dev \ + libenchant-dev libfolks-dev libgcr-3-dev libgee-0.8-dev \ + libglib2.0-dev libgmime-2.6-dev libgoa-1.0-dev libgtk-3-dev \ + libjson-glib-dev libhandy-dev libnotify-dev libsecret-1-dev \ + libsqlite3-dev libunwind-dev libwebkit2gtk-4.0-dev \ + libxml2-dev And for Ubuntu Unity integration: - sudo apt-get install libunity-dev libmessaging-menu-dev + sudo apt-get install libmessaging-menu-dev libunity-dev Running ------- diff --git a/meson.build b/meson.build index 479e5743..23bfa02d 100644 --- a/meson.build +++ b/meson.build @@ -66,6 +66,7 @@ iso_codes = dependency('iso-codes') javascriptcoregtk = dependency('javascriptcoregtk-4.0', version: '>=' + target_webkit) json_glib = dependency('json-glib-1.0', version: '>= 1.0') libcanberra = dependency('libcanberra', version: '>= 0.28') +libhandy = dependency('libhandy-0.0', version: '>= 0.0.9') libmath = cc.find_library('m') libnotify = dependency('libnotify', version: '>= 0.7.5') libsecret = dependency('libsecret-1', version: '>= 0.11') diff --git a/org.gnome.Geary.json b/org.gnome.Geary.json index 290c2bcb..1d7168fa 100644 --- a/org.gnome.Geary.json +++ b/org.gnome.Geary.json @@ -181,6 +181,20 @@ } ] }, + { + "name": "libhandy", + "buildsystem": "meson", + "builddir": true, + "config-opts": [ + "-Dglade_catalog=disabled" + ], + "sources": [ + { + "type": "git", + "url": "https://source.puri.sm/Librem5/libhandy.git" + } + ] + }, { "name": "libunwind", "sources": [ diff --git a/src/client/meson.build b/src/client/meson.build index d1c7863a..a2678ec8 100644 --- a/src/client/meson.build +++ b/src/client/meson.build @@ -22,6 +22,7 @@ geary_client_vala_sources = files( 'accounts/accounts-manager.vala', 'components/client-web-view.vala', + 'components/components-inspector.vala', 'components/components-placeholder-pane.vala', 'components/components-validator.vala', 'components/count-badge.vala', @@ -120,6 +121,7 @@ geary_client_dependencies = [ gtk, json_glib, libcanberra, + libhandy, libnotify, libsecret, libsoup, diff --git a/src/meson.build b/src/meson.build index ea5509a9..175c4aa9 100644 --- a/src/meson.build +++ b/src/meson.build @@ -37,9 +37,10 @@ geary_c_options = [ # Select libunwind's optimised, local-only backtrace unwiding. See # libunwind(3). '-DUNW_LOCAL_ONLY', - # Neither GOA nor GCK want to hang out unless you are cool enough - '-DGOA_API_IS_SUBJECT_TO_CHANGE', + # None of these kids want to hang out unless you are cool enough '-DGCK_API_SUBJECT_TO_CHANGE', + '-DGOA_API_IS_SUBJECT_TO_CHANGE', + '-DHANDY_USE_UNSTABLE_API', ] subdir('sqlite3-unicodesn') From 7f13ce491d0b2e67b9fa4770e2a92446369ed9c5 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Sun, 7 Apr 2019 11:36:43 +1000 Subject: [PATCH 02/21] Target Ubuntu and Fedora devel images in CI This should provide some more flexibility in adding new deps (like libhandy). --- .gitlab-ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f2b3c404..b359baad 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -39,7 +39,7 @@ variables: fedora: stage: build - image: fedora:latest + image: fedora:rawhide before_script: - dnf update -y --nogpgcheck - dnf install -y --nogpgcheck $FEDORA_DEPS $FEDORA_TEST_DEPS @@ -51,7 +51,7 @@ fedora: ubuntu: stage: build - image: ubuntu:rolling + image: ubuntu:devel before_script: - apt-get update - apt-get install -q -y --no-install-recommends $UBUNTU_DEPS $UBUNTU_TEST_DEPS From 433215570bd9c044f23a919e1c27d5f31c1d0c67 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Fri, 5 Apr 2019 23:34:08 +1100 Subject: [PATCH 03/21] Make generic account editor ListBoxRow style reusable elsewhere --- src/client/accounts/accounts-editor-row.vala | 1 + ui/geary.css | 76 ++++++++++---------- 2 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/client/accounts/accounts-editor-row.vala b/src/client/accounts/accounts-editor-row.vala index 0906bbbd..211ce277 100644 --- a/src/client/accounts/accounts-editor-row.vala +++ b/src/client/accounts/accounts-editor-row.vala @@ -27,6 +27,7 @@ internal class Accounts.EditorRow : Gtk.ListBoxRow { public EditorRow() { get_style_context().add_class("geary-settings"); + get_style_context().add_class("geary-labelled-row"); this.layout.orientation = Gtk.Orientation.HORIZONTAL; this.layout.show(); diff --git a/ui/geary.css b/ui/geary.css index b1bd0031..6057afd1 100644 --- a/ui/geary.css +++ b/ui/geary.css @@ -191,6 +191,45 @@ grid.geary-message-summary { font-weight: bold; } +/* GtkListboxRows with padded labels */ + +row.geary-labelled-row { + padding: 0px; +} + +row.geary-labelled-row > grid > * { + margin: 18px 6px; +} + +row.geary-labelled-row > grid > *:first-child:dir(ltr), +row.geary-labelled-row > grid > *:last-child:dir(rtl) { + margin-left: 18px; +} + +row.geary-labelled-row > grid > *:last-child:dir(ltr), +row.geary-labelled-row > grid > *:first-child:dir(rtl) { + margin-right: 18px; +} + +/* Images should have some padding to offset them from adjacent +widgets, but care ust be taken since images are also used as children +of other widgets like entries, comboboxes and switches, and these +shouldn't be be touched. */ +row.geary-labelled-row widget > image, +row.geary-labelled-row grid > image { + padding: 0px 6px; +} + +row.geary-labelled-row > grid > combobox, +row.geary-labelled-row > grid > entry, +row.geary-labelled-row:not(.geary-add-row) > grid > image, +row.geary-labelled-row > grid > switch { + /* These use more space than labels, so set their valign to center + when adding them and free up some space around them here to keep a + consistent row height. */ + margin: 0 12px; +} + /* Accounts.Editor */ grid.geary-accounts-editor-pane-content { @@ -214,10 +253,6 @@ label.geary-settings-heading { margin-bottom: 12px; } -row.geary-settings { - padding: 0px; -} - row.geary-settings entry { border-width: 0px; background-color: transparent; @@ -233,20 +268,6 @@ row.geary-settings.geary-drag-icon { border: 1px solid @borders; } -row.geary-settings > grid > * { - margin: 18px 6px; -} - -row.geary-settings > grid > *:first-child:dir(ltr), -row.geary-settings > grid > *:last-child:dir(rtl) { - margin-left: 18px; -} - -row.geary-settings > grid > *:last-child:dir(ltr), -row.geary-settings > grid > *:first-child:dir(rtl) { - margin-right: 18px; -} - /* dir pseudo-class used here for required additional specificity */ row.geary-settings > grid > grid.geary-drag-handle:dir(ltr), row.geary-settings > grid > grid.geary-drag-handle:dir(rtl) { @@ -267,25 +288,6 @@ frame.geary-settings.geary-signature { min-height: 5em; } -/* Images should have some padding to offset them from adjacent -widgets, but care ust be taken since images are also used as children -of other widgets like entries, comboboxes and switches, and these -shouldn't be be touched. */ -row.geary-settings widget > image, -row.geary-settings grid > image { - padding: 0px 6px; -} - -row.geary-settings > grid > combobox, -row.geary-settings > grid > entry, -row.geary-settings:not(.geary-add-row) > grid > image, -row.geary-settings > grid > switch { - /* These use more space than labels, so set their valign to center - when adding them and free up some space around them here to keep a - consistent row height. */ - margin: 0 12px; -} - buttonbox.geary-settings { margin-top: 36px; } From 93cb84b0a3a239b0280f09f18f6c326149aaad75 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Fri, 5 Apr 2019 23:34:58 +1100 Subject: [PATCH 04/21] Make runtime information collection reusable elsewhere Move gathering information like app & lib versions to GearyApplication, also gather app prefix and distro info. --- src/client/application/geary-application.vala | 92 +++++++++++++++++++ .../dialogs-problem-details-dialog.vala | 29 +++--- 2 files changed, 108 insertions(+), 13 deletions(-) diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala index b891af11..efcbfb37 100644 --- a/src/client/application/geary-application.vala +++ b/src/client/application/geary-application.vala @@ -74,6 +74,14 @@ public class GearyApplication : Gtk.Application { private const int64 FORCE_SHUTDOWN_USEC = 5 * USEC_PER_SEC; + /** Object returned by {@link get_runtime_information}. */ + public struct RuntimeDetail { + + public string name; + public string value; + + } + [Version (deprecated = true)] public static GearyApplication instance { get { return _instance; } @@ -130,6 +138,90 @@ public class GearyApplication : Gtk.Application { private bool is_destroyed = false; + /** + * Returns name/value pairs of application information. + * + * This includes Geary library version information, the current + * desktop, and so on. + */ + public Gee.Collection get_runtime_information() { + Gee.LinkedList info = + new Gee.LinkedList(); + + /// Application runtime information label + info.add({ _("Geary version"), VERSION }); + /// Application runtime information label + info.add({ _("GTK version"), + "%u.%u.%u".printf( + Gtk.get_major_version(), + Gtk.get_minor_version(), + Gtk.get_micro_version() + )}); + /// Applciation runtime information label + info.add({ _("GLib version"), + "%u.%u.%u".printf( + GLib.Version.major, + GLib.Version.minor, + GLib.Version.micro + )}); + /// Application runtime information label + info.add({ _("WebKitGTK version"), + "%u.%u.%u".printf( + WebKit.get_major_version(), + WebKit.get_minor_version(), + WebKit.get_micro_version() + )}); + /// Application runtime information label + info.add({ _("Desktop environment"), + Environment.get_variable("XDG_CURRENT_DESKTOP") ?? + _("Unknown") + }); + + // Distro name and version using LSB util + + GLib.SubprocessLauncher launcher = new GLib.SubprocessLauncher( + GLib.SubprocessFlags.STDOUT_PIPE | + GLib.SubprocessFlags.STDERR_SILENCE + ); + // Reset lang vars so we can guess the strings below + launcher.setenv("LANGUAGE", "C", true); + launcher.setenv("LANG", "C", true); + launcher.setenv("LC_ALL", "C", true); + + string lsb_output = ""; + try { + GLib.Subprocess lsb_release = launcher.spawnv( + { "lsb_release", "-ir" } + ); + lsb_release.communicate_utf8(null, null, out lsb_output, null); + } catch (GLib.Error err) { + warning("Failed to exec lsb_release: %s", err.message); + } + if (lsb_output != "") { + foreach (string line in lsb_output.split("\n")) { + string[] parts = line.split(":", 2); + if (parts.length > 1) { + if (parts[0].has_prefix("Distributor ID")) { + /// Application runtime information label + info.add( + { _("Distribution name"), parts[1].strip() } + ); + } else if (parts[0].has_prefix("Release")) { + /// Application runtime information label + info.add( + { _("Distribution release"), parts[1].strip() } + ); + } + } + } + } + + /// Application runtime information label + info.add({ _("Installation prefix"), INSTALL_PREFIX }); + + return info; + } + /** * Signal that is activated when 'exit' is called, but before the application actually exits. * diff --git a/src/client/dialogs/dialogs-problem-details-dialog.vala b/src/client/dialogs/dialogs-problem-details-dialog.vala index 28d8e2fd..32bb55d1 100644 --- a/src/client/dialogs/dialogs-problem-details-dialog.vala +++ b/src/client/dialogs/dialogs-problem-details-dialog.vala @@ -24,7 +24,10 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog { Geary.ErrorContext error, Geary.AccountInformation? account, Geary.ServiceInformation? service) { - Object(use_header_bar: 1); + Object( + transient_for: parent, + use_header_bar: 1 + ); set_default_size(600, -1); this.error = error; @@ -50,18 +53,18 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog { private string format_details() { StringBuilder details = new StringBuilder(); - details.append_printf( - "Geary version: %s\n", - GearyApplication.VERSION - ); - details.append_printf( - "GTK version: %u.%u.%u\n", - Gtk.get_major_version(), Gtk.get_minor_version(), Gtk.get_micro_version() - ); - details.append_printf( - "Desktop: %s\n", - Environment.get_variable("XDG_CURRENT_DESKTOP") ?? "Unknown" - ); + + Gtk.ApplicationWindow? parent = + this.get_toplevel() as Gtk.ApplicationWindow; + GearyApplication? app = (parent != null) + ? parent.application as GearyApplication + : null; + if (app != null) { + foreach (GearyApplication.RuntimeDetail? detail + in app.get_runtime_information()) { + details.append_printf("%s: %s", detail.name, detail.value); + } + } if (this.account != null) { details.append_printf( "Account id: %s\n", From ae4d7655b08d4bc0e6650027ac45e552295953f5 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Fri, 5 Apr 2019 23:37:02 +1100 Subject: [PATCH 05/21] Add basic Geary Inspector, hook it up to Alt+Shift+I --- po/POTFILES.in | 2 + src/client/application/geary-application.vala | 8 + .../components/components-inspector.vala | 82 ++++++++ ui/components-inspector.ui | 185 ++++++++++++++++++ ui/org.gnome.Geary.gresource.xml | 1 + 5 files changed, 278 insertions(+) create mode 100644 src/client/components/components-inspector.vala create mode 100644 ui/components-inspector.ui diff --git a/po/POTFILES.in b/po/POTFILES.in index 9cc6ea67..3761ac74 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -26,6 +26,7 @@ src/client/application/goa-mediator.vala src/client/application/main.vala src/client/application/secret-mediator.vala src/client/components/client-web-view.vala +src/client/components/components-inspector.vala src/client/components/components-placeholder-pane.vala src/client/components/components-validator.vala src/client/components/count-badge.vala @@ -412,6 +413,7 @@ ui/composer-headerbar.ui ui/composer-link-popover.ui ui/composer-menus.ui ui/composer-widget.ui +ui/components-inspector.ui ui/components-placeholder-pane.ui ui/conversation-email.ui ui/conversation-email-attachment-view.ui diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala index efcbfb37..75915977 100644 --- a/src/client/application/geary-application.vala +++ b/src/client/application/geary-application.vala @@ -55,6 +55,7 @@ public class GearyApplication : Gtk.Application { private const string ACTION_ABOUT = "about"; private const string ACTION_ACCOUNTS = "accounts"; private const string ACTION_COMPOSE = "compose"; + private const string ACTION_INSPECT = "inspect"; private const string ACTION_HELP = "help"; private const string ACTION_MAILTO = "mailto"; private const string ACTION_PREFERENCES = "preferences"; @@ -64,6 +65,7 @@ public class GearyApplication : Gtk.Application { {ACTION_ABOUT, on_activate_about}, {ACTION_ACCOUNTS, on_activate_accounts}, {ACTION_COMPOSE, on_activate_compose}, + {ACTION_INSPECT, on_activate_inspect}, {ACTION_HELP, on_activate_help}, {ACTION_MAILTO, on_activate_mailto, "s"}, {ACTION_PREFERENCES, on_activate_preferences}, @@ -342,6 +344,7 @@ public class GearyApplication : Gtk.Application { // Application accels add_app_accelerators(ACTION_COMPOSE, { "N" }); add_app_accelerators(ACTION_HELP, { "F1" }); + add_app_accelerators(ACTION_INSPECT, { "I" }); add_app_accelerators(ACTION_QUIT, { "Q" }); // Common window accels @@ -561,6 +564,11 @@ public class GearyApplication : Gtk.Application { } } + private void on_activate_inspect() { + Components.Inspector inspector = new Components.Inspector(this); + inspector.show(); + } + private void on_activate_mailto(SimpleAction action, Variant? param) { if (this.controller != null && param != null) { this.controller.compose_mailto(param.get_string()); diff --git a/src/client/components/components-inspector.vala b/src/client/components/components-inspector.vala new file mode 100644 index 00000000..a75432d5 --- /dev/null +++ b/src/client/components/components-inspector.vala @@ -0,0 +1,82 @@ +/* + * Copyright 2019 Michael Gratton + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +/** + * A window that displays debugging and development information. + */ +[GtkTemplate (ui = "/org/gnome/Geary/components-inspector.ui")] +public class Components.Inspector : Gtk.Window { + + + [GtkChild] + private Hdy.SearchBar search_bar; + + [GtkChild] + private Gtk.ListBox detail_list; + + private string details; + + + public Inspector(GearyApplication app) { + StringBuilder details = new StringBuilder(); + foreach (GearyApplication.RuntimeDetail? detail + in app.get_runtime_information()) { + this.detail_list.add( + new DetailRow("%s:".printf(detail.name), detail.value) + ); + details.append_printf("%s: %s\n", detail.name, detail.value); + } + this.details = details.str; + } + + [GtkCallback] + private void on_copy_clicked() { + get_clipboard(Gdk.SELECTION_CLIPBOARD).set_text(this.details, -1); + } + + [GtkCallback] + private void on_search_clicked() { + this.search_bar.set_search_mode(!this.search_bar.get_search_mode()); + } + +} + + +private class Components.DetailRow : Gtk.ListBoxRow { + + + private Gtk.Grid layout { get; private set; default = new Gtk.Grid(); } + private Gtk.Label label { get; private set; default = new Gtk.Label(""); } + private Gtk.Label value { get; private set; default = new Gtk.Label(""); } + + + public DetailRow(string label, string value) { + get_style_context().add_class("geary-labelled-row"); + + this.label.halign = Gtk.Align.START; + this.label.valign = Gtk.Align.CENTER; + this.label.set_text(label); + this.label.show(); + + this.value.halign = Gtk.Align.END; + this.value.hexpand = true; + this.value.valign = Gtk.Align.CENTER; + this.value.xalign = 1.0f; + this.value.set_text(value); + this.value.show(); + + this.layout.orientation = Gtk.Orientation.HORIZONTAL; + this.layout.add(this.label); + this.layout.add(this.value); + this.layout.show(); + add(this.layout); + + this.activatable = false; + show(); + } + +} diff --git a/ui/components-inspector.ui b/ui/components-inspector.ui new file mode 100644 index 00000000..2c408f6b --- /dev/null +++ b/ui/components-inspector.ui @@ -0,0 +1,185 @@ + + + + + + + + + + + + + Inspector opened + + + + + diff --git a/ui/org.gnome.Geary.gresource.xml b/ui/org.gnome.Geary.gresource.xml index 45c0e4f6..6a3374c4 100644 --- a/ui/org.gnome.Geary.gresource.xml +++ b/ui/org.gnome.Geary.gresource.xml @@ -10,6 +10,7 @@ certificate_warning_dialog.glade client-web-view.js client-web-view-allow-remote-images.js + components-inspector.ui components-placeholder-pane.ui composer-headerbar.ui composer-link-popover.ui From 0bac8f77fa5b696b1eca26a8f67b716bee933a54 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Sat, 6 Apr 2019 17:13:42 +1100 Subject: [PATCH 06/21] Keep a bounded list of last n log messages Allow these log records to be accessed by the application. --- src/engine/api/geary-logging.vala | 105 ++++++++++++++++++++++++++---- 1 file changed, 92 insertions(+), 13 deletions(-) diff --git a/src/engine/api/geary-logging.vala b/src/engine/api/geary-logging.vala index e09dfac3..ee626292 100644 --- a/src/engine/api/geary-logging.vala +++ b/src/engine/api/geary-logging.vala @@ -13,6 +13,7 @@ namespace Geary.Logging { +public const uint DEFAULT_MAX_LOG_BUFFER_LENGTH = 4096; private const string DOMAIN = "Geary"; [Flags] @@ -36,21 +37,77 @@ public enum Flag { } } +/** + * A single message sent to the logging system. + * + * A record is created for each log message, + */ +public class LogRecord { + + + private string domain; + private LogLevelFlags flags; + private int64 timestamp; + private double elapsed; + private string message; + + internal LogRecord? next = null; + + + internal LogRecord(string domain, + LogLevelFlags flags, + int64 timestamp, + double elapsed, + string message) { + this.domain = domain; + this.flags = flags; + this.timestamp = timestamp; + this.elapsed = elapsed; + this.message = message; + } + + public LogRecord? get_next() { + return this.next; + } + + public string format() { + GLib.DateTime time = new GLib.DateTime.from_unix_utc( + this.timestamp / 1000 / 1000 + ).to_local(); + return "%s %02d:%02d:%02d %lf %s: %s".printf( + to_prefix(this.flags), + time.get_hour(), time.get_minute(), time.get_second(), + this.elapsed, + this.domain ?? "default", + this.message + ); + } + +} + private int init_count = 0; private Flag logging_flags = Flag.NONE; private unowned FileStream? stream = null; private Timer? entry_timer = null; +private LogRecord? first_record = null; +private LogRecord? last_record = null; +private uint log_length = 0; +private uint max_log_length = 0; + + /** * Must be called before ''any'' call to the Logging namespace. * - * This will be initialized by the Engine when it's opened, but applications may want to set up - * logging before that, in which case, call this directly. + * This will be initialized by the Engine when it's opened, but + * applications may want to set up logging before that, in which case, + * call this directly. */ public void init() { if (init_count++ != 0) return; entry_timer = new Timer(); + max_log_length = DEFAULT_MAX_LOG_BUFFER_LENGTH; } /** @@ -120,6 +177,10 @@ public inline void debug(Flag flags, string fmt, ...) { } } +public LogRecord? get_logs() { + return first_record; +} + /** * Registers a FileStream to receive all log output from the Engine, be it via the specialized * Logging calls (which use the topic-based {@link Flag} or GLib's standard issue @@ -135,6 +196,33 @@ public void log_to(FileStream? stream) { public void default_handler(string? domain, LogLevelFlags log_levels, string message) { + LogRecord record = new LogRecord( + domain, + log_levels, + GLib.get_real_time(), + entry_timer.elapsed(), + message + ); + entry_timer.start(); + + // Update the record linked list + if (first_record == null) { + first_record = record; + last_record = record; + } else { + last_record.next = record; + last_record = record; + } + log_length++; + while (log_length > max_log_length) { + first_record = first_record.next; + log_length--; + } + if (first_record == null) { + last_record = null; + } + + // Print to the output stream if needed unowned FileStream? out = stream; if (out != null || ((LogLevelFlags.LEVEL_WARNING & log_levels) > 0) || @@ -145,17 +233,8 @@ public void default_handler(string? domain, out = GLib.stderr; } - GLib.Time tm = GLib.Time.local(time_t()); - out.printf( - "%s %02d:%02d:%02d %lf %s: %s\n", - to_prefix(log_levels), - tm.hour, tm.minute, tm.second, - entry_timer.elapsed(), - domain ?? "default", - message - ); - - entry_timer.start(); + out.puts(record.format()); + out.putc('\n'); } } From 1f4ef0ab956781c2bc8f0add3eabaa5847626054 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Sat, 6 Apr 2019 17:13:10 +1100 Subject: [PATCH 07/21] Display all known logs in inspector --- .../components/components-inspector.vala | 41 +++++++++++++++++++ ui/components-inspector.ui | 9 ++-- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/src/client/components/components-inspector.vala b/src/client/components/components-inspector.vala index a75432d5..7798d4c5 100644 --- a/src/client/components/components-inspector.vala +++ b/src/client/components/components-inspector.vala @@ -12,16 +12,52 @@ public class Components.Inspector : Gtk.Window { + [GtkChild] + private Gtk.HeaderBar header_bar; + [GtkChild] private Hdy.SearchBar search_bar; + [GtkChild] + private Gtk.TreeView logs_view; + + [GtkChild] + private Gtk.CellRendererText log_renderer; + [GtkChild] private Gtk.ListBox detail_list; + private Gtk.ListStore logs_store = new Gtk.ListStore.newv({ + typeof(string) + }); + private string details; public Inspector(GearyApplication app) { + this.title = this.header_bar.title = _("Inspector"); + + // Log a marker for when the inspector was opened + debug("---- 8< ---- %s ---- 8< ----", this.header_bar.title); + + Gtk.ListStore logs_store = this.logs_store; + Geary.Logging.LogRecord? logs = Geary.Logging.get_logs(); + while (logs != null) { + Gtk.TreeIter iter; + logs_store.append(out iter); + logs_store.set_value(iter, 0, logs.format()); + logs = logs.get_next(); + } + + GLib.Settings system = app.config.gnome_interface; + system.bind( + "monospace-font-name", + this.log_renderer, "font", + SettingsBindFlags.DEFAULT + ); + + this.logs_view.set_model(logs_store); + StringBuilder details = new StringBuilder(); foreach (GearyApplication.RuntimeDetail? detail in app.get_runtime_information()) { @@ -43,6 +79,11 @@ public class Components.Inspector : Gtk.Window { this.search_bar.set_search_mode(!this.search_bar.get_search_mode()); } + [GtkCallback] + private void on_destroy() { + destroy(); + } + } diff --git a/ui/components-inspector.ui b/ui/components-inspector.ui index 2c408f6b..1478fcec 100644 --- a/ui/components-inspector.ui +++ b/ui/components-inspector.ui @@ -3,7 +3,7 @@ - + @@ -18,8 +18,9 @@ False 750 500 + - + True False True @@ -93,10 +94,10 @@ True in - + True True - logs_liststore + logs_store False False False From 1b121e0a8e4a1a0c2c24ab7d26c2f7d18ab582de Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Sat, 6 Apr 2019 17:24:48 +1100 Subject: [PATCH 08/21] Update the UI when the switcher pane changes --- .../components/components-inspector.vala | 24 +++++++++++++++++++ ui/components-inspector.ui | 7 +++--- 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/client/components/components-inspector.vala b/src/client/components/components-inspector.vala index 7798d4c5..7540c280 100644 --- a/src/client/components/components-inspector.vala +++ b/src/client/components/components-inspector.vala @@ -15,6 +15,18 @@ public class Components.Inspector : Gtk.Window { [GtkChild] private Gtk.HeaderBar header_bar; + [GtkChild] + private Gtk.Stack stack; + + [GtkChild] + private Gtk.Button copy_button; + + [GtkChild] + private Gtk.Widget logs_pane; + + [GtkChild] + private Gtk.Button search_button; + [GtkChild] private Hdy.SearchBar search_bar; @@ -69,6 +81,18 @@ public class Components.Inspector : Gtk.Window { this.details = details.str; } + private void update_ui() { + bool logs_visible = this.stack.visible_child == this.logs_pane; + uint logs_selected = this.logs_view.get_selection().count_selected_rows(); + this.copy_button.set_sensitive(!logs_visible || logs_selected > 0); + this.search_button.set_visible(logs_visible); + } + + [GtkCallback] + private void on_visible_child_changed() { + update_ui(); + } + [GtkCallback] private void on_copy_clicked() { get_clipboard(Gdk.SELECTION_CLIPBOARD).set_text(this.details, -1); diff --git a/ui/components-inspector.ui b/ui/components-inspector.ui index 1478fcec..e577aed9 100644 --- a/ui/components-inspector.ui +++ b/ui/components-inspector.ui @@ -25,7 +25,7 @@ False True - + True True True @@ -50,7 +50,7 @@ - + Copy to Clipboard True True @@ -71,8 +71,9 @@ True False + - + True False From 0e4486bbf4c33e10b9b46295b2d12a43177452ba Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Sat, 6 Apr 2019 17:26:14 +1100 Subject: [PATCH 09/21] Make Copy button copy selected log rows, make it less prominent --- .../components/components-inspector.vala | 40 ++++++++++++++++++- ui/components-inspector.ui | 19 ++++++--- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/client/components/components-inspector.vala b/src/client/components/components-inspector.vala index 7540c280..c25f8f83 100644 --- a/src/client/components/components-inspector.vala +++ b/src/client/components/components-inspector.vala @@ -12,6 +12,9 @@ public class Components.Inspector : Gtk.Window { + private const int COL_MESSAGE = 0; + + [GtkChild] private Gtk.HeaderBar header_bar; @@ -36,6 +39,9 @@ public class Components.Inspector : Gtk.Window { [GtkChild] private Gtk.CellRendererText log_renderer; + [GtkChild] + private Gtk.Widget detail_pane; + [GtkChild] private Gtk.ListBox detail_list; @@ -57,7 +63,7 @@ public class Components.Inspector : Gtk.Window { while (logs != null) { Gtk.TreeIter iter; logs_store.append(out iter); - logs_store.set_value(iter, 0, logs.format()); + logs_store.set_value(iter, COL_MESSAGE, logs.format()); logs = logs.get_next(); } @@ -95,7 +101,32 @@ public class Components.Inspector : Gtk.Window { [GtkCallback] private void on_copy_clicked() { - get_clipboard(Gdk.SELECTION_CLIPBOARD).set_text(this.details, -1); + string clipboard_value = ""; + if (this.stack.visible_child == this.logs_pane) { + StringBuilder rows = new StringBuilder(); + Gtk.TreeModel model = this.logs_view.model; + foreach (Gtk.TreePath path in + this.logs_view.get_selection().get_selected_rows(null)) { + Gtk.TreeIter iter; + if (model.get_iter(out iter, path)) { + Value value; + model.get_value(iter, COL_MESSAGE, out value); + + string? message = (string) value; + if (message != null) { + rows.append(message); + rows.append_c('\n'); + } + } + } + clipboard_value = rows.str; + } else if (this.stack.visible_child == this.detail_pane) { + clipboard_value = this.details; + } + + if (!Geary.String.is_empty(clipboard_value)) { + get_clipboard(Gdk.SELECTION_CLIPBOARD).set_text(clipboard_value, -1); + } } [GtkCallback] @@ -103,6 +134,11 @@ public class Components.Inspector : Gtk.Window { this.search_bar.set_search_mode(!this.search_bar.get_search_mode()); } + [GtkCallback] + private void on_logs_selection_changed() { + update_ui(); + } + [GtkCallback] private void on_destroy() { destroy(); diff --git a/ui/components-inspector.ui b/ui/components-inspector.ui index e577aed9..098c4d7b 100644 --- a/ui/components-inspector.ui +++ b/ui/components-inspector.ui @@ -51,14 +51,18 @@ - Copy to Clipboard True True True - + + + True + False + Copy selected rows + edit-copy-symbolic + + end @@ -103,7 +107,10 @@ False False - + + multiple + + @@ -131,7 +138,7 @@ - + True True never From b5f27dce7986ee287e70e7f280212d3e3cb36e4b Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Sat, 6 Apr 2019 18:49:10 +1100 Subject: [PATCH 10/21] Add UI and implement saving details and logs to file --- .../components/components-inspector.vala | 63 +++++++++++++++++++ ui/components-inspector.ui | 24 +++++-- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/client/components/components-inspector.vala b/src/client/components/components-inspector.vala index c25f8f83..dc4bf0d0 100644 --- a/src/client/components/components-inspector.vala +++ b/src/client/components/components-inspector.vala @@ -87,6 +87,41 @@ public class Components.Inspector : Gtk.Window { this.details = details.str; } + private async void save(string path, + GLib.Cancellable? cancellable) + throws GLib.Error { + GLib.File dest = GLib.File.new_for_path(path); + GLib.FileIOStream dest_io = yield dest.create_readwrite_async( + GLib.FileCreateFlags.NONE, + GLib.Priority.DEFAULT, + cancellable + ); + GLib.DataOutputStream out = new GLib.DataOutputStream( + new GLib.BufferedOutputStream(dest_io.get_output_stream()) + ); + + out.put_string(this.details); + out.put_byte('\n'); + out.put_byte('\n'); + + Gtk.TreeModel model = this.logs_view.model; + Gtk.TreeIter? iter; + bool valid = model.get_iter_first(out iter); + while (valid && !cancellable.is_cancelled()) { + Value value; + model.get_value(iter, COL_MESSAGE, out value); + string? message = (string) value; + if (message != null) { + out.put_string(message); + out.put_byte('\n'); + } + valid = model.iter_next(ref iter); + } + + yield out.close_async(); + yield dest_io.close_async(); + } + private void update_ui() { bool logs_visible = this.stack.visible_child == this.logs_pane; uint logs_selected = this.logs_view.get_selection().count_selected_rows(); @@ -129,6 +164,34 @@ public class Components.Inspector : Gtk.Window { } } + [GtkCallback] + private void on_save_as_clicked() { + Gtk.FileChooserNative chooser = new Gtk.FileChooserNative( + _("Save As"), + this, + Gtk.FileChooserAction.SAVE, + _("Save As"), + _("Cancel") + ); + chooser.set_current_name( + new GLib.DateTime.now_local().format("Geary Inspector - %F %T.txt") + ); + + if (chooser.run() == Gtk.ResponseType.ACCEPT) { + this.save.begin( + chooser.get_filename(), + null, + (obj, res) => { + try { + this.save.end(res); + } catch (GLib.Error err) { + warning("Failed to save inspector data: %s", err.message); + } + } + ); + } + } + [GtkCallback] private void on_search_clicked() { this.search_bar.set_search_mode(!this.search_bar.get_search_mode()); diff --git a/ui/components-inspector.ui b/ui/components-inspector.ui index 098c4d7b..95824f0e 100644 --- a/ui/components-inspector.ui +++ b/ui/components-inspector.ui @@ -38,9 +38,6 @@ - - 1 - @@ -49,6 +46,25 @@ stack + + + True + True + True + + + + True + False + document-save-as-symbolic + + + + + end + 1 + + True @@ -66,7 +82,7 @@ end - 1 + 2 From e5893e1d66f24f43c6d121220cbdb759c0c9119f Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Sat, 6 Apr 2019 20:20:58 +1100 Subject: [PATCH 11/21] Implement filtering log messages --- .../components/components-inspector.vala | 77 +++++++++++++++---- ui/components-inspector.ui | 10 +++ 2 files changed, 72 insertions(+), 15 deletions(-) diff --git a/src/client/components/components-inspector.vala b/src/client/components/components-inspector.vala index dc4bf0d0..8e8d9168 100644 --- a/src/client/components/components-inspector.vala +++ b/src/client/components/components-inspector.vala @@ -33,6 +33,9 @@ public class Components.Inspector : Gtk.Window { [GtkChild] private Hdy.SearchBar search_bar; + [GtkChild] + private Gtk.SearchEntry search_entry; + [GtkChild] private Gtk.TreeView logs_view; @@ -49,12 +52,35 @@ public class Components.Inspector : Gtk.Window { typeof(string) }); + private Gtk.TreeModelFilter logs_filter; + + private string[] logs_filter_terms = new string[0]; + private string details; public Inspector(GearyApplication app) { this.title = this.header_bar.title = _("Inspector"); + this.search_bar.connect_entry(this.search_entry); + + GLib.Settings system = app.config.gnome_interface; + system.bind( + "monospace-font-name", + this.log_renderer, "font", + SettingsBindFlags.DEFAULT + ); + + StringBuilder details = new StringBuilder(); + foreach (GearyApplication.RuntimeDetail? detail + in app.get_runtime_information()) { + this.detail_list.add( + new DetailRow("%s:".printf(detail.name), detail.value) + ); + details.append_printf("%s: %s\n", detail.name, detail.value); + } + this.details = details.str; + // Log a marker for when the inspector was opened debug("---- 8< ---- %s ---- 8< ----", this.header_bar.title); @@ -67,24 +93,35 @@ public class Components.Inspector : Gtk.Window { logs = logs.get_next(); } - GLib.Settings system = app.config.gnome_interface; - system.bind( - "monospace-font-name", - this.log_renderer, "font", - SettingsBindFlags.DEFAULT - ); + this.logs_filter = new Gtk.TreeModelFilter(logs_store, null); + this.logs_filter.set_visible_func((model, iter) => { + bool ret = true; + if (this.logs_filter_terms.length > 0) { + ret = false; + Value value; + model.get_value(iter, COL_MESSAGE, out value); + string? message = (string) value; + if (message != null) { + foreach (string term in this.logs_filter_terms) { + if (term in message) { + ret = true; + break; + } + } + } + } + return ret; + }); - this.logs_view.set_model(logs_store); + this.logs_view.set_model(this.logs_filter); + } - StringBuilder details = new StringBuilder(); - foreach (GearyApplication.RuntimeDetail? detail - in app.get_runtime_information()) { - this.detail_list.add( - new DetailRow("%s:".printf(detail.name), detail.value) - ); - details.append_printf("%s: %s\n", detail.name, detail.value); + public override bool key_press_event(Gdk.EventKey event) { + bool ret = this.search_bar.handle_event(event); + if (ret == Gdk.EVENT_PROPAGATE) { + ret = base.key_press_event(event); } - this.details = details.str; + return ret; } private async void save(string path, @@ -129,6 +166,11 @@ public class Components.Inspector : Gtk.Window { this.search_button.set_visible(logs_visible); } + private void update_logs_filter() { + this.logs_filter_terms = this.search_entry.text.split(" "); + this.logs_filter.refilter(); + } + [GtkCallback] private void on_visible_child_changed() { update_ui(); @@ -202,6 +244,11 @@ public class Components.Inspector : Gtk.Window { update_ui(); } + [GtkCallback] + private void on_logs_search_changed() { + update_logs_filter(); + } + [GtkCallback] private void on_destroy() { destroy(); diff --git a/ui/components-inspector.ui b/ui/components-inspector.ui index 95824f0e..d62575b1 100644 --- a/ui/components-inspector.ui +++ b/ui/components-inspector.ui @@ -102,6 +102,16 @@ True False True + + + True + True + edit-find-symbolic + False + False + + + 0 From fc119ebb784c386698a2b7be469f88b9e303c753 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Sun, 7 Apr 2019 11:31:56 +1000 Subject: [PATCH 12/21] Add a means for apps to be notified as new log records are added --- .../components/components-inspector.vala | 3 +- src/engine/api/geary-logging.vala | 58 ++++++++++++++----- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/client/components/components-inspector.vala b/src/client/components/components-inspector.vala index 8e8d9168..321176be 100644 --- a/src/client/components/components-inspector.vala +++ b/src/client/components/components-inspector.vala @@ -85,7 +85,8 @@ public class Components.Inspector : Gtk.Window { debug("---- 8< ---- %s ---- 8< ----", this.header_bar.title); Gtk.ListStore logs_store = this.logs_store; - Geary.Logging.LogRecord? logs = Geary.Logging.get_logs(); + Geary.Logging.Record? logs = Geary.Logging.get_logs(); + int index = 0; while (logs != null) { Gtk.TreeIter iter; logs_store.append(out iter); diff --git a/src/engine/api/geary-logging.vala b/src/engine/api/geary-logging.vala index ee626292..2019d872 100644 --- a/src/engine/api/geary-logging.vala +++ b/src/engine/api/geary-logging.vala @@ -13,9 +13,19 @@ namespace Geary.Logging { + +/** Specifies the default number of log records retained. */ public const uint DEFAULT_MAX_LOG_BUFFER_LENGTH = 4096; + private const string DOMAIN = "Geary"; +/** + * Denotes a type of log message. + * + * Logging for each type of log message may be dynamically enabled or + * disabled at run time by {@link enable_flags} and {@link + * disable_flags}. + */ [Flags] public enum Flag { NONE = 0, @@ -38,11 +48,14 @@ public enum Flag { } /** - * A single message sent to the logging system. + * A record of a single message sent to the logging system. * - * A record is created for each log message, + * A record is created for each message logged, and stored in a + * limited-length, singly-linked buffer. Applications can retrieve + * this by calling {@link get_logs} and then {get_next}, and can be + * notified of new records via {@link set_log_listener}. */ -public class LogRecord { +public class Record { private string domain; @@ -51,14 +64,14 @@ public class LogRecord { private double elapsed; private string message; - internal LogRecord? next = null; + internal Record? next = null; - internal LogRecord(string domain, - LogLevelFlags flags, - int64 timestamp, - double elapsed, - string message) { + internal Record(string domain, + LogLevelFlags flags, + int64 timestamp, + double elapsed, + string message) { this.domain = domain; this.flags = flags; this.timestamp = timestamp; @@ -66,10 +79,12 @@ public class LogRecord { this.message = message; } - public LogRecord? get_next() { + /** Returns the next log record in the buffer, if any. */ + public Record? get_next() { return this.next; } + /** Returns a formatted string representation of this record. */ public string format() { GLib.DateTime time = new GLib.DateTime.from_unix_utc( this.timestamp / 1000 / 1000 @@ -85,15 +100,19 @@ public class LogRecord { } +/** Specifies the function signature for {@link set_log_listener}. */ +public delegate void LogRecord(Record record); + private int init_count = 0; private Flag logging_flags = Flag.NONE; private unowned FileStream? stream = null; private Timer? entry_timer = null; -private LogRecord? first_record = null; -private LogRecord? last_record = null; +private Record? first_record = null; +private Record? last_record = null; private uint log_length = 0; private uint max_log_length = 0; +private LogRecord? listener = null; /** @@ -132,6 +151,12 @@ public void disable_flags(Flag flags) { logging_flags &= ~flags; } +/** Sets a function to be called when a new log record is created. */ +public void set_log_listener(LogRecord? new_listener) { + listener = new_listener; +} + + /** * Returns the current logging flags. */ @@ -177,7 +202,8 @@ public inline void debug(Flag flags, string fmt, ...) { } } -public LogRecord? get_logs() { +/** Returns the oldest log record in the logging system's buffer. */ +public Record? get_logs() { return first_record; } @@ -196,7 +222,7 @@ public void log_to(FileStream? stream) { public void default_handler(string? domain, LogLevelFlags log_levels, string message) { - LogRecord record = new LogRecord( + Record record = new Record( domain, log_levels, GLib.get_real_time(), @@ -222,6 +248,10 @@ public void default_handler(string? domain, last_record = null; } + if (listener != null) { + listener(record); + } + // Print to the output stream if needed unowned FileStream? out = stream; if (out != null || From 6dc8b278f0c71b8fddaf3bf09f93980caf808af0 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Sun, 7 Apr 2019 11:32:38 +1000 Subject: [PATCH 13/21] Append new log records to the inspector and scroll to them --- .../components/components-inspector.vala | 58 +++++++++++++++++-- src/engine/imap-db/imap-db-attachment.vala | 12 ++-- ui/components-inspector.ui | 8 ++- 3 files changed, 62 insertions(+), 16 deletions(-) diff --git a/src/client/components/components-inspector.vala b/src/client/components/components-inspector.vala index 321176be..bee621b3 100644 --- a/src/client/components/components-inspector.vala +++ b/src/client/components/components-inspector.vala @@ -36,6 +36,9 @@ public class Components.Inspector : Gtk.Window { [GtkChild] private Gtk.SearchEntry search_entry; + [GtkChild] + private Gtk.ScrolledWindow logs_scroller; + [GtkChild] private Gtk.TreeView logs_view; @@ -58,6 +61,8 @@ public class Components.Inspector : Gtk.Window { private string details; + private bool autoscroll = true; + public Inspector(GearyApplication app) { this.title = this.header_bar.title = _("Inspector"); @@ -81,15 +86,14 @@ public class Components.Inspector : Gtk.Window { } this.details = details.str; - // Log a marker for when the inspector was opened - debug("---- 8< ---- %s ---- 8< ----", this.header_bar.title); + enable_log_updates(true); Gtk.ListStore logs_store = this.logs_store; Geary.Logging.Record? logs = Geary.Logging.get_logs(); int index = 0; while (logs != null) { Gtk.TreeIter iter; - logs_store.append(out iter); + logs_store.insert(out iter, index++); logs_store.set_value(iter, COL_MESSAGE, logs.format()); logs = logs.get_next(); } @@ -117,6 +121,13 @@ public class Components.Inspector : Gtk.Window { this.logs_view.set_model(this.logs_filter); } + public override void destroy() { + // Don't use enable_log_updates() here because we don't want a + // marker logged. + Geary.Logging.set_log_listener(null); + base.destroy(); + } + public override bool key_press_event(Gdk.EventKey event) { bool ret = this.search_bar.handle_event(event); if (ret == Gdk.EVENT_PROPAGATE) { @@ -125,6 +136,16 @@ public class Components.Inspector : Gtk.Window { return ret; } + private void enable_log_updates(bool enabled) { + // Log a marker it indicate when it was toggled + debug("---- 8< ---- %s ---- 8< ----", this.header_bar.title); + if (enabled) { + Geary.Logging.set_log_listener(this.on_log_record); + } else { + Geary.Logging.set_log_listener(null); + } + } + private async void save(string path, GLib.Cancellable? cancellable) throws GLib.Error { @@ -167,11 +188,22 @@ public class Components.Inspector : Gtk.Window { this.search_button.set_visible(logs_visible); } + private void update_scrollbar() { + Gtk.Adjustment adj = this.logs_scroller.get_vadjustment(); + adj.set_value(adj.upper - adj.page_size); + } + private void update_logs_filter() { this.logs_filter_terms = this.search_entry.text.split(" "); this.logs_filter.refilter(); } + private void append_record(Geary.Logging.Record record) { + Gtk.TreeIter inserted_iter; + this.logs_store.append(out inserted_iter); + this.logs_store.set_value(inserted_iter, COL_MESSAGE, record.format()); + } + [GtkCallback] private void on_visible_child_changed() { update_ui(); @@ -240,6 +272,13 @@ public class Components.Inspector : Gtk.Window { this.search_bar.set_search_mode(!this.search_bar.get_search_mode()); } + [GtkCallback] + private void on_logs_size_allocate() { + if (this.autoscroll) { + update_scrollbar(); + } + } + [GtkCallback] private void on_logs_selection_changed() { update_ui(); @@ -250,9 +289,16 @@ public class Components.Inspector : Gtk.Window { update_logs_filter(); } - [GtkCallback] - private void on_destroy() { - destroy(); + private void on_log_record(Geary.Logging.Record record) { + if (GLib.MainContext.default() == + GLib.MainContext.get_thread_default()) { + append_record(record); + } else { + GLib.Idle.add(() => { + append_record(record); + return GLib.Source.REMOVE; + }); + } } } diff --git a/src/engine/imap-db/imap-db-attachment.vala b/src/engine/imap-db/imap-db-attachment.vala index f840aeb8..60c7028c 100644 --- a/src/engine/imap-db/imap-db-attachment.vala +++ b/src/engine/imap-db/imap-db-attachment.vala @@ -95,9 +95,9 @@ private class Geary.ImapDB.Attachment : Geary.Attachment { save_file(part, attachments_dir, cancellable); update_db(cx, cancellable); } catch (Error err) { - // Don't honour the cancellable here, we need to delete - // it. - this.delete(cx, cancellable); + // Don't honour the cancellable here, it needs to be + // deleted + this.delete(cx, null); throw err; } } @@ -161,10 +161,8 @@ private class Geary.ImapDB.Attachment : Geary.Attachment { // create directory, but don't throw exception if already exists try { target.get_parent().make_directory_with_parents(cancellable); - } catch (IOError ioe) { - // fall through if already exists - if (!(ioe is IOError.EXISTS)) - throw ioe; + } catch (IOError.EXISTS err) { + // All good } // Delete any existing file now since we might not be creating diff --git a/ui/components-inspector.ui b/ui/components-inspector.ui index d62575b1..732b2845 100644 --- a/ui/components-inspector.ui +++ b/ui/components-inspector.ui @@ -18,7 +18,6 @@ False 750 500 - True @@ -50,6 +49,8 @@ True True + Save logs and details to a file True @@ -69,13 +70,13 @@ True True + Copy selected rows True True False - Copy selected rows edit-copy-symbolic @@ -119,7 +120,7 @@ - + True True True @@ -132,6 +133,7 @@ False False False + multiple From 584ead9eb882ff93acc4c96188fe7809788fb245 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Sun, 7 Apr 2019 15:02:08 +1000 Subject: [PATCH 14/21] Tidy up inspector tooltips --- ui/components-inspector.ui | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/components-inspector.ui b/ui/components-inspector.ui index 732b2845..e9552e34 100644 --- a/ui/components-inspector.ui +++ b/ui/components-inspector.ui @@ -28,6 +28,7 @@ True True True + Search fo matching log entries @@ -49,9 +50,8 @@ True True - Save logs and details to a file True + Save logs entries and details @@ -70,8 +70,8 @@ True True - Copy selected rows True + Copy selected log entries From ccde37c20d72011f6abbf8b6e17c9775288ab439 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Sun, 7 Apr 2019 16:11:26 +1000 Subject: [PATCH 15/21] Enable pausing/unpausing the log viewer --- .../components/components-inspector.vala | 68 ++++++++++++++----- ui/components-inspector.ui | 20 ++++++ 2 files changed, 72 insertions(+), 16 deletions(-) diff --git a/src/client/components/components-inspector.vala b/src/client/components/components-inspector.vala index bee621b3..67976d83 100644 --- a/src/client/components/components-inspector.vala +++ b/src/client/components/components-inspector.vala @@ -1,3 +1,4 @@ + /* * Copyright 2019 Michael Gratton * @@ -28,7 +29,10 @@ public class Components.Inspector : Gtk.Window { private Gtk.Widget logs_pane; [GtkChild] - private Gtk.Button search_button; + private Gtk.ToggleButton play_button; + + [GtkChild] + private Gtk.ToggleButton search_button; [GtkChild] private Hdy.SearchBar search_bar; @@ -61,6 +65,9 @@ public class Components.Inspector : Gtk.Window { private string details; + private bool update_logs = true; + private Geary.Logging.Record? first_pending = null; + private bool autoscroll = true; @@ -86,8 +93,13 @@ public class Components.Inspector : Gtk.Window { } this.details = details.str; + // Enable updates to get the log marker enable_log_updates(true); + // Install the listener then starting add the backlog + // (ba-doom-tish) so to avoid the race. + Geary.Logging.set_log_listener(this.on_log_record); + Gtk.ListStore logs_store = this.logs_store; Geary.Logging.Record? logs = Geary.Logging.get_logs(); int index = 0; @@ -122,8 +134,6 @@ public class Components.Inspector : Gtk.Window { } public override void destroy() { - // Don't use enable_log_updates() here because we don't want a - // marker logged. Geary.Logging.set_log_listener(null); base.destroy(); } @@ -137,12 +147,26 @@ public class Components.Inspector : Gtk.Window { } private void enable_log_updates(bool enabled) { - // Log a marker it indicate when it was toggled - debug("---- 8< ---- %s ---- 8< ----", this.header_bar.title); + // Log a marker to indicate when it was started/stopped + debug( + "---- 8< ---- %s %s ---- 8< ----", + this.header_bar.title, + enabled ? "▶" : "■" + ); + + this.update_logs = enabled; + + // Disable autoscroll when not updating as well to stop the + // tree view jumping to the bottom when changing the filter. + this.autoscroll = enabled; + if (enabled) { - Geary.Logging.set_log_listener(this.on_log_record); - } else { - Geary.Logging.set_log_listener(null); + Geary.Logging.Record? logs = this.first_pending; + while (logs != null) { + append_record(logs); + logs = logs.get_next(); + } + this.first_pending = null; } } @@ -185,6 +209,7 @@ public class Components.Inspector : Gtk.Window { bool logs_visible = this.stack.visible_child == this.logs_pane; uint logs_selected = this.logs_view.get_selection().count_selected_rows(); this.copy_button.set_sensitive(!logs_visible || logs_selected > 0); + this.play_button.set_visible(logs_visible); this.search_button.set_visible(logs_visible); } @@ -284,20 +309,31 @@ public class Components.Inspector : Gtk.Window { update_ui(); } + [GtkCallback] + private void on_logs_play_toggled(Gtk.ToggleButton button) { + if (this.update_logs != button.active) { + enable_log_updates(button.active); + } + } + [GtkCallback] private void on_logs_search_changed() { update_logs_filter(); } private void on_log_record(Geary.Logging.Record record) { - if (GLib.MainContext.default() == - GLib.MainContext.get_thread_default()) { - append_record(record); - } else { - GLib.Idle.add(() => { - append_record(record); - return GLib.Source.REMOVE; - }); + if (this.update_logs) { + if (GLib.MainContext.default() == + GLib.MainContext.get_thread_default()) { + append_record(record); + } else { + GLib.Idle.add(() => { + append_record(record); + return GLib.Source.REMOVE; + }); + } + } else if (this.first_pending == null) { + this.first_pending = record; } } diff --git a/ui/components-inspector.ui b/ui/components-inspector.ui index e9552e34..d138ade8 100644 --- a/ui/components-inspector.ui +++ b/ui/components-inspector.ui @@ -23,6 +23,23 @@ True False True + + + True + True + True + Togggle appending new log entries + True + + + + True + False + media-playback-start-symbolic + + + + True @@ -38,6 +55,9 @@ + + 1 + From d87a2b02e05eeb8d1f8daf981a868e32dc88abf4 Mon Sep 17 00:00:00 2001 From: Michael Gratton Date: Sun, 7 Apr 2019 17:49:43 +1000 Subject: [PATCH 16/21] Enable some keyboard shortcuts for the inspector --- src/client/application/geary-application.vala | 1 + .../components/components-inspector.vala | 78 +++++++++++++++---- ui/components-inspector.ui | 10 +-- 3 files changed, 71 insertions(+), 18 deletions(-) diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala index 75915977..d205a3c3 100644 --- a/src/client/application/geary-application.vala +++ b/src/client/application/geary-application.vala @@ -355,6 +355,7 @@ public class GearyApplication : Gtk.Application { add_window_accelerators(ACTION_UNDO, { "Z" }); ComposerWidget.add_window_accelerators(this); + Components.Inspector.add_window_accelerators(this); yield controller.open_async(null); diff --git a/src/client/components/components-inspector.vala b/src/client/components/components-inspector.vala index 67976d83..e42f5226 100644 --- a/src/client/components/components-inspector.vala +++ b/src/client/components/components-inspector.vala @@ -10,11 +10,31 @@ * A window that displays debugging and development information. */ [GtkTemplate (ui = "/org/gnome/Geary/components-inspector.ui")] -public class Components.Inspector : Gtk.Window { +public class Components.Inspector : Gtk.ApplicationWindow { private const int COL_MESSAGE = 0; + private const string ACTION_CLOSE = "inspector-close"; + private const string ACTION_PLAY_TOGGLE = "toggle-play"; + private const string ACTION_SEARCH_TOGGLE = "toggle-search"; + private const string ACTION_SEARCH_ACTIVATE = "activate-search"; + + private const ActionEntry[] action_entries = { + {GearyApplication.ACTION_CLOSE, on_close }, + {GearyApplication.ACTION_COPY, on_copy_clicked }, + {ACTION_CLOSE, on_close }, + {ACTION_PLAY_TOGGLE, on_logs_play_toggled, null, "true" }, + {ACTION_SEARCH_TOGGLE, on_logs_search_toggled, null, "false" }, + {ACTION_SEARCH_ACTIVATE, on_logs_search_activated }, + }; + + public static void add_window_accelerators(GearyApplication app) { + app.add_window_accelerators(ACTION_CLOSE, { "Escape" } ); + app.add_window_accelerators(ACTION_PLAY_TOGGLE, { "space" } ); + app.add_window_accelerators(ACTION_SEARCH_ACTIVATE, { "F" } ); + } + [GtkChild] private Gtk.HeaderBar header_bar; @@ -72,8 +92,11 @@ public class Components.Inspector : Gtk.Window { public Inspector(GearyApplication app) { + Object(application: app); this.title = this.header_bar.title = _("Inspector"); + add_action_entries(Inspector.action_entries, this); + this.search_bar.connect_entry(this.search_entry); GLib.Settings system = app.config.gnome_interface; @@ -139,7 +162,26 @@ public class Components.Inspector : Gtk.Window { } public override bool key_press_event(Gdk.EventKey event) { - bool ret = this.search_bar.handle_event(event); + bool ret = Gdk.EVENT_PROPAGATE; + + if (this.search_bar.search_mode_enabled && + event.keyval == Gdk.Key.Escape) { + // Manually deactivate search so the button stays in sync + this.search_button.set_active(false); + ret = Gdk.EVENT_STOP; + } + + if (ret == Gdk.EVENT_PROPAGATE) { + ret = this.search_bar.handle_event(event); + } + + if (ret == Gdk.EVENT_PROPAGATE && + this.search_bar.search_mode_enabled) { + // Ensure and others are passed to the search + // entry before getting used as an accelerator. + ret = this.search_entry.key_press_event(event); + } + if (ret == Gdk.EVENT_PROPAGATE) { ret = base.key_press_event(event); } @@ -234,7 +276,6 @@ public class Components.Inspector : Gtk.Window { update_ui(); } - [GtkCallback] private void on_copy_clicked() { string clipboard_value = ""; if (this.stack.visible_child == this.logs_pane) { @@ -292,11 +333,6 @@ public class Components.Inspector : Gtk.Window { } } - [GtkCallback] - private void on_search_clicked() { - this.search_bar.set_search_mode(!this.search_bar.get_search_mode()); - } - [GtkCallback] private void on_logs_size_allocate() { if (this.autoscroll) { @@ -309,11 +345,23 @@ public class Components.Inspector : Gtk.Window { update_ui(); } - [GtkCallback] - private void on_logs_play_toggled(Gtk.ToggleButton button) { - if (this.update_logs != button.active) { - enable_log_updates(button.active); - } + private void on_logs_search_toggled(GLib.SimpleAction action, + GLib.Variant? param) { + bool enabled = !((bool) action.state); + this.search_bar.set_search_mode(enabled); + action.set_state(enabled); + } + + private void on_logs_search_activated() { + this.search_button.set_active(true); + this.search_entry.grab_focus(); + } + + private void on_logs_play_toggled(GLib.SimpleAction action, + GLib.Variant? param) { + bool enabled = !((bool) action.state); + enable_log_updates(enabled); + action.set_state(enabled); } [GtkCallback] @@ -337,6 +385,10 @@ public class Components.Inspector : Gtk.Window { } } + private void on_close() { + destroy(); + } + } diff --git a/ui/components-inspector.ui b/ui/components-inspector.ui index d138ade8..b874fb13 100644 --- a/ui/components-inspector.ui +++ b/ui/components-inspector.ui @@ -14,7 +14,7 @@ -