Support Ubuntu messaging menu: Closes #5648

libindicate is supported in this patch (with some framework in place
to support others, such as libmessagingmenu if/when it comes down
the pipe).  libindicate support must be configured with a ./configure
switch (which is one-half of #5607) and is off by default.

Note that this patch does not fully implement our design spec for
how new messages are cleared due to user interaction.  That is
covered by #5669.
This commit is contained in:
Jim Nelson 2012-08-15 17:21:03 -07:00
parent c104f1e45e
commit 6da84dfc79
8 changed files with 225 additions and 22 deletions

7
configure vendored
View file

@ -29,6 +29,9 @@ configure_help() {
printf "\t--disable-icon-update\n"
printf "\t\t\t\tDisable icon cache update.\n"
printf "\t--enable-libindicate\n"
printf "\t\t\t\tEnable libindicate messaging menu support.\n"
printf "\n"
}
@ -72,6 +75,10 @@ do
CMDLINE="${CMDLINE} -DDESKTOP_UPDATE=OFF"
;;
--enable-libindicate)
CMDLINE="${CMDLINE} -DENABLE_LIBINDICATE=ON"
;;
*) abort $option
;;
esac

View file

@ -196,6 +196,11 @@ client/geary-config.vala
client/geary-controller.vala
client/main.vala
client/notification/libindicate.vala
client/notification/new-messages-indicator.vala
client/notification/notification-bubble.vala
client/notification/null-indicator.vala
client/ui/composer-window.vala
client/ui/geary-login.vala
client/ui/email-entry.vala
@ -212,7 +217,6 @@ client/ui/message-web-view.vala
client/ui/password-dialog.vala
client/ui/preferences-dialog.vala
client/ui/webview-edit-fixer.vala
client/ui/notification-bubble.vala
client/ui/sidebar/sidebar-branch.vala
client/ui/sidebar/sidebar-common.vala
@ -249,6 +253,20 @@ include(ValaVersion)
ensure_vala_version("0.17.4" MINIMUM)
include(ValaPrecompile)
option(ENABLE_LIBINDICATE "Enable libindicate messaging menu support." OFF)
if (ENABLE_LIBINDICATE STREQUAL "ON")
message(STATUS "Building with messaging menu support.")
set(EXTRA_CLIENT_PKG_CONFIG
indicate-0.7>=0.6.1
)
set(EXTRA_CLIENT_PACKAGES
Dbusmenu-0.4 Indicate-0.7
)
set(EXTRA_VALA_OPTIONS
-D HAVE_LIBINDICATE
)
endif ()
# Packages
find_package(PkgConfig)
pkg_check_modules(DEPS REQUIRED
@ -264,6 +282,7 @@ pkg_check_modules(DEPS REQUIRED
gmime-2.6>=2.6.0
gnome-keyring-1>=3.2.2
webkitgtk-3.0>=1.8.0
${EXTRA_CLIENT_PKG_CONFIG}
)
set(ENGINE_PACKAGES
@ -271,7 +290,7 @@ set(ENGINE_PACKAGES
)
set(CLIENT_PACKAGES
gtk+-3.0 gnome-keyring-1 webkitgtk-3.0 libnotify libcanberra
gtk+-3.0 gnome-keyring-1 webkitgtk-3.0 libnotify libcanberra ${EXTRA_CLIENT_PACKAGES}
)
set(CONSOLE_PACKAGES
@ -306,6 +325,7 @@ set(VALAC_OPTIONS
--enable-checking
--debug
--fatal-warnings
${EXTRA_VALA_OPTIONS}
)
# Engine (conceptually a library, but not built as a separate library)

View file

@ -162,7 +162,7 @@ along with Geary; if not, write to the Free Software Foundation, Inc.,
public override void activate(string[] args) {
// If Geary is already running, show the main window and return.
if (controller != null && controller.main_window != null) {
controller.main_window.present();
controller.main_window.present_with_time((uint32) TimeVal().tv_sec);
handle_args(args);
return;
}

View file

@ -67,6 +67,7 @@ public class GearyController {
public MainWindow main_window { get; private set; }
private Geary.EngineAccount? account = null;
private Cancellable cancellable_folder = new Cancellable();
private Cancellable cancellable_inbox = new Cancellable();
private Cancellable cancellable_message = new Cancellable();
@ -79,8 +80,7 @@ public class GearyController {
private Geary.Conversation? last_deleted_conversation = null;
private Gee.LinkedList<ComposerWindow> composer_windows = new Gee.LinkedList<ComposerWindow>();
private File? last_save_directory = null;
private Geary.EngineAccount? account { get; private set; }
private NewMessagesIndicator new_messages_indicator;
public GearyController() {
// Setup actions.
@ -92,6 +92,12 @@ public class GearyController {
// Listen for attempts to close the application.
GearyApplication.instance.exiting.connect(on_application_exiting);
// New messages indicator (Ubuntuism)
new_messages_indicator = NewMessagesIndicator.create();
new_messages_indicator.application_activated.connect(on_indicator_activated_application);
new_messages_indicator.composer_activated.connect(on_indicator_activated_composer);
new_messages_indicator.inbox_activated.connect(on_indicator_activated_inbox);
// Create the main window (must be done after creating actions.)
main_window = new MainWindow();
@ -267,6 +273,7 @@ public class GearyController {
}
inbox_folder.email_locally_appended.disconnect(on_inbox_new_email);
inbox_folder.email_flags_changed.disconnect(on_inbox_email_flags_changed);
}
try {
@ -435,7 +442,14 @@ public class GearyController {
do_notify_new_email.begin(email_ids);
}
public async void do_notify_new_email(Gee.Collection<Geary.EmailIdentifier> email_ids) {
private void on_inbox_email_flags_changed(Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> ids) {
foreach(Geary.EmailIdentifier id in ids.keys) {
if (!ids[id].is_unread())
new_messages_indicator.not_new_message(id);
}
}
private async void do_notify_new_email(Gee.Collection<Geary.EmailIdentifier> email_ids) {
try {
Gee.List<Geary.Email>? list = yield inbox_folder.list_email_by_sparse_id_async(email_ids,
NotificationBubble.REQUIRED_FIELDS | Geary.Email.Field.FLAGS, Geary.Folder.ListFlags.NONE,
@ -446,38 +460,57 @@ public class GearyController {
return;
}
int unread = 0;
Geary.Email? last_unread = null;
foreach (Geary.Email email in list) {
if (email.email_flags.is_unread()) {
unread++;
last_unread = email;
// notify via new messages indicator (i.e. libindicate, libmessagingmenu when available)
new_messages_indicator.new_message(email.id);
}
}
debug("do_notify_new_email: %d messages listed, %d unread", list.size, unread);
debug("do_notify_new_email: %d messages listed, %d unread", list.size, new_messages_indicator.count);
// notify via notification bubble (i.e. libnotify)
NotificationBubble notification = new NotificationBubble();
notification.invoked.connect(on_notification_bubble_invoked);
if (unread == 1 && last_unread != null)
if (new_messages_indicator.count == 1 && last_unread != null)
yield notification.notify_one_message_async(last_unread, cancellable_inbox);
else if (unread > 0)
notification.notify_new_mail(unread);
else if (new_messages_indicator.count > 0)
notification.notify_new_mail(new_messages_indicator.count);
} catch (Error err) {
debug("Unable to notify of new email: %s", err.message);
}
}
private void on_notification_bubble_invoked(Geary.Email? email) {
if(inbox_folder != null) {
if (email == null || inbox_folder == null)
return;
main_window.folder_list.select_path(inbox_folder.get_path());
Geary.Conversation? conversation = current_conversations.get_conversation_for_email(email.id);
if (conversation != null)
main_window.message_list_view.select_conversation(conversation);
}
private void on_indicator_activated_application(uint32 timestamp) {
main_window.present_with_time(timestamp);
}
private void on_indicator_activated_composer(uint32 timestamp) {
main_window.present_with_time(timestamp);
on_new_message();
}
private void on_indicator_activated_inbox(uint32 timestamp) {
main_window.present_with_time(timestamp);
// reset new messages
new_messages_indicator.clear_new_messages();
// attempt to select Inbox
if (inbox_folder != null)
main_window.folder_list.select_path(inbox_folder.get_path());
if(email != null) {
Geary.Conversation? conversation = current_conversations.get_conversation_for_email(email.id);
if(conversation != null) {
main_window.message_list_view.select_conversation(conversation);
}
}
}
}
private void on_conversation_appended(Geary.Conversation conversation,
@ -631,10 +664,14 @@ public class GearyController {
if (folder.get_special_folder_type() == Geary.SpecialFolderType.INBOX && inbox_folder == null) {
inbox_folder = folder;
inbox_folder.email_locally_appended.connect(on_inbox_new_email);
inbox_folder.email_flags_changed.connect(on_inbox_email_flags_changed);
// select the inbox and get the show started
main_window.folder_list.select_path(folder.get_path());
inbox_folder.open_async.begin(false, cancellable_inbox);
// reset new messages indicator
new_messages_indicator.clear_new_messages();
}
folder.special_folder_type_changed.connect(on_special_folder_type_changed);

View file

@ -0,0 +1,68 @@
/* Copyright 2011-2012 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public class Libindicate : NewMessagesIndicator {
#if HAVE_LIBINDICATE
private Indicate.Server indicator;
private Indicate.Indicator compose;
private Indicate.Indicator inbox;
public Libindicate() {
debug("Using libindicate for messaging menu support");
indicator = Indicate.Server.ref_default();
indicator.set_type("message.email");
// Find the desktop file this app instance is using (running from build dir vs. install dir)
File desktop_file = GearyApplication.instance.get_resource_directory().get_child("geary.desktop");
if (!desktop_file.query_exists())
desktop_file = File.new_for_path("/usr/share/applications/geary.desktop");
indicator.set_desktop_file(desktop_file.get_path());
indicator.server_display.connect(on_display_server);
// Create "Compose Message" option and always display it
compose = new Indicate.Indicator.with_server(indicator);
compose.set_property_variant("name", _("Compose Message"));
compose.user_display.connect(on_activate_composer);
compose.show();
// Create "New Messages" option which is only displayed if new messages are available
inbox = new Indicate.Indicator.with_server(indicator);
inbox.set_property_variant("name", _("New Messages"));
inbox.user_display.connect(on_activate_inbox);
notify["count"].connect(on_new_messages_changed);
indicator.show();
}
private void on_new_messages_changed() {
if (count > 0) {
// count is in fact a string property
inbox.set_property_variant("count", count.to_string());
inbox.set_property_bool("draw-attention", true);
inbox.show();
} else {
inbox.hide();
}
}
private void on_display_server(uint timestamp) {
application_activated(timestamp);
}
private void on_activate_composer(uint timestamp) {
composer_activated(timestamp);
}
private void on_activate_inbox(uint timestamp) {
inbox_activated(timestamp);
}
#endif
}

View file

@ -0,0 +1,58 @@
/* Copyright 2012 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
// This is coded this way to allow for libindicate and libmessagingmenu to coexist in code (if not
// compiled at same time) and minimize the exposure of differences to the rest of the application.
// Subclasses should trap the "notify::count" signal and use that to perform whatever magic
// they need for their implementation.
public abstract class NewMessagesIndicator : Object {
public int count { get; private set; default = 0; }
private Gee.HashSet<Geary.EmailIdentifier> new_ids = new Gee.HashSet<Geary.EmailIdentifier>(
Geary.Hashable.hash_func, Geary.Equalable.equal_func);
public signal void application_activated(uint32 timestamp);
public signal void inbox_activated(uint32 timestamp);
public signal void composer_activated(uint32 timestamp);
protected NewMessagesIndicator() {
}
public void new_message(Geary.EmailIdentifier email_id) {
new_ids.add(email_id);
update_count();
}
public void not_new_message(Geary.EmailIdentifier email_id) {
new_ids.remove(email_id);
update_count();
}
public void clear_new_messages() {
new_ids.clear();
update_count();
}
private void update_count() {
// Documentation for "notify" signal seems to suggest that it's possible for the signal to
// fire even if the value of the property doesn't change. Since this signal can trigger
// big events, want to avoid firing it unless necessary
if (count != new_ids.size)
count = new_ids.size;
}
public static NewMessagesIndicator create() {
#if HAVE_LIBINDICATE
return new Libindicate();
#else
return new NullIndicator();
#endif
}
}

View file

@ -45,7 +45,7 @@ public class NotificationBubble : GLib.Object {
invoked(email);
GearyApplication.instance.activate(new string[0]);
}
public void notify_new_mail(int count) throws GLib.Error {
// don't pass email if invoked
email = null;
@ -113,7 +113,6 @@ public class NotificationBubble : GLib.Object {
notification.set_hint_string("sound-name", sound);
else
play_sound(sound);
}
public static void play_sound(string sound) {

View file

@ -0,0 +1,14 @@
/* Copyright 2012 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
// Do-nothing NewMessagesIndicator, used on non-Ubuntu compiles.
public class NullIndicator : NewMessagesIndicator {
public NullIndicator() {
debug("No messaging menu support in this build");
}
}