diff --git a/THANKS b/THANKS index 10288858..007ab814 100644 --- a/THANKS +++ b/THANKS @@ -17,6 +17,7 @@ Michael George Michael Gratton Sven Hagemann Mathias Hasselmann +Mohamed Ibrahim Timo Kluck Charles Lehner Avi Levy diff --git a/cmake/FindIntltool.cmake b/cmake/FindIntltool.cmake index 700a9bcc..ac45ce99 100644 --- a/cmake/FindIntltool.cmake +++ b/cmake/FindIntltool.cmake @@ -19,5 +19,12 @@ if (INTLTOOL_MERGE_FOUND) ) install (FILES ${CMAKE_CURRENT_BINARY_DIR}/geary.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications) endmacro (INTLTOOL_MERGE_DESKTOP desktop_id po_dir) + macro (INTLTOOL_MERGE_AUTOSTART_DESKTOP desktop_id po_dir) + add_custom_target (geary-autostart.desktop ALL + ${INTLTOOL_MERGE_EXECUTABLE} --desktop-style ${CMAKE_SOURCE_DIR}/${po_dir} + ${CMAKE_CURRENT_SOURCE_DIR}/${desktop_id}.desktop.in ${desktop_id}.desktop + ) + install (FILES ${CMAKE_CURRENT_BINARY_DIR}/geary-autostart.desktop DESTINATION ${CMAKE_INSTALL_PREFIX}/share/applications) + endmacro (INTLTOOL_MERGE_AUTOSTART_DESKTOP desktop_id po_dir) endif (INTLTOOL_MERGE_FOUND) diff --git a/desktop/CMakeLists.txt b/desktop/CMakeLists.txt index e0214b41..e37988fe 100644 --- a/desktop/CMakeLists.txt +++ b/desktop/CMakeLists.txt @@ -8,10 +8,12 @@ include (FindIntltool) include (FindDesktopFileValidate) if (INTLTOOL_MERGE_FOUND) INTLTOOL_MERGE_DESKTOP (geary po) + INTLTOOL_MERGE_AUTOSTART_DESKTOP (geary-autostart po) if (DESKTOP_VALIDATE) if (DESKTOP_FILE_VALIDATE_FOUND) VALIDATE_DESKTOP_FILE (geary) + VALIDATE_DESKTOP_FILE (geary-autostart) else (DESKTOP_FILE_VALIDATE_FOUND) message (FATAL_ERROR "desktop-file-validate must be installed to validate generated .desktop file") endif (DESKTOP_FILE_VALIDATE_FOUND) diff --git a/desktop/geary-autostart.desktop.in b/desktop/geary-autostart.desktop.in new file mode 100644 index 00000000..7ded47f4 --- /dev/null +++ b/desktop/geary-autostart.desktop.in @@ -0,0 +1,15 @@ +[Desktop Entry] +_Name=Geary +_GenericName=Mail Client +_X-GNOME-FullName=Geary Mail +_Comment=Send and receive email +_Keywords=Email;E-mail;Mail; +Icon=geary +TryExec=geary +Exec=geary --hidden +Type=Application +Terminal=false +Categories=GNOME;GTK;Network;Email; +MimeType=x-scheme-handler/mailto; +StartupNotify=true +NoDisplay=true diff --git a/desktop/org.yorba.geary.gschema.xml b/desktop/org.yorba.geary.gschema.xml index 022a094e..b619d137 100644 --- a/desktop/org.yorba.geary.gschema.xml +++ b/desktop/org.yorba.geary.gschema.xml @@ -59,6 +59,11 @@ show notifications for new mail True to show notification bubbles. + + false + notify of new mail at startup + True to notify of new mail at startup. + true ask when opening an attachment diff --git a/po/POTFILES.in b/po/POTFILES.in index 4e11a577..8679e353 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -1,5 +1,6 @@ [encoding: UTF-8] desktop/geary.desktop.in +desktop/geary-autostart.desktop.in src/client/accounts/account-dialog-account-list-pane.vala src/client/accounts/account-dialog-add-edit-pane.vala src/client/accounts/account-dialog-pane.vala diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d08f30a6..54e10da1 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -291,6 +291,7 @@ engine/util/util-trillian.vala ) set(CLIENT_SRC +client/application/autostart-manager.vala client/application/geary-action-adapter.vala client/application/geary-application.vala client/application/geary-args.vala diff --git a/src/client/application/autostart-manager.vala b/src/client/application/autostart-manager.vala new file mode 100644 index 00000000..466b56cf --- /dev/null +++ b/src/client/application/autostart-manager.vala @@ -0,0 +1,90 @@ +/* Copyright 2014 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. + */ + +/* + * A simple class for manipulating autostarting Geary as a hidden application through a simple + * desktop file at $HOME/.config/autostart/geary.desktop + */ +public class AutostartManager : Object { + private const string AUTOSTART_FOLDER = "autostart"; + private const string AUTOSTART_DESKTOP_FILE = "geary-autostart.desktop"; + + private File startup_file; // Startup '.desktop' file + + public AutostartManager() { + startup_file = File.new_for_path(Environment.get_user_config_dir()).get_child(AUTOSTART_FOLDER) + .get_child(AUTOSTART_DESKTOP_FILE); + + // Connect startup-notifications option callback + GearyApplication.instance.config.settings.changed[Configuration.STARTUP_NOTIFICATIONS_KEY].connect( + on_startup_notification_change); + } + + /** + * Returns the system-wide autostart desktop file + */ + public File? get_autostart_desktop_file() { + File? install_dir = GearyApplication.instance.get_install_dir(); + File desktop_file = (install_dir != null) + ? install_dir.get_child("share").get_child("applications").get_child(AUTOSTART_DESKTOP_FILE) + : File.new_for_path(GearyApplication.SOURCE_ROOT_DIR).get_child("build").get_child("desktop").get_child(AUTOSTART_DESKTOP_FILE); + + return desktop_file.query_exists() ? desktop_file : null; + } + + /** + * Deletes the desktop file from autostart directory. + */ + public void delete_startup_file() { + if (startup_file.query_exists()) { + try { + startup_file.delete(); + } catch (Error err) { + message("Failed to delete startup file: %s", err.message); + } + } + } + + /** + * Creates .desktop file in autostart directory (usually '$HOME/.config/autostart/') if no one exists. + */ + public void create_startup_file() { + if (startup_file.query_exists()) + return; + + try { + File autostart_dir = startup_file.get_parent(); + if (!autostart_dir.query_exists()) + autostart_dir.make_directory_with_parents(); + File? autostart = get_autostart_desktop_file(); + if (autostart == null) { + message("Autostart file is not installed!"); + } else { + autostart.copy(startup_file, 0); + } + } catch (Error err) { + message("Failed to create startup file: %s", err.message); + } + } + + /** + * Callback for startup notification option changes. + */ + public void on_startup_notification_change() { + if (GearyApplication.instance.config.startup_notifications) + create_startup_file(); + else + delete_startup_file(); + } + + /* + * A convenience method. The purpose of this method is to synchronize the state of startup notifications setting + * with the actual state of the file, so it's not misleading for the user (the option is checked while the file doesn't exist) + */ + public void sync_with_config() { + GearyApplication.instance.config.startup_notifications = startup_file.query_exists(); + } +} diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala index 5c9d827d..ee10326f 100644 --- a/src/client/application/geary-application.vala +++ b/src/client/application/geary-application.vala @@ -154,7 +154,11 @@ public class GearyApplication : Gtk.Application { if (controller == null || controller.main_window == null) return false; - controller.main_window.present(); + if (!controller.main_window.get_realized()) + controller.main_window.show_all(); + else + controller.main_window.present(); + return true; } diff --git a/src/client/application/geary-args.vala b/src/client/application/geary-args.vala index 1cbbea57..a26a9c8b 100644 --- a/src/client/application/geary-args.vala +++ b/src/client/application/geary-args.vala @@ -7,6 +7,7 @@ namespace Args { private const OptionEntry[] options = { + { "hidden", 0, 0, OptionArg.NONE, ref hidden_startup, N_("Start Geary with hidden main window"), null }, { "debug", 'd', 0, OptionArg.NONE, ref log_debug, N_("Output debugging information"), null }, { "log-conversations", 0, 0, OptionArg.NONE, ref log_conversations, N_("Log conversation monitoring"), null }, { "log-deserializer", 0, 0, OptionArg.NONE, ref log_deserializer, N_("Log network deserialization"), null }, @@ -26,6 +27,7 @@ private const OptionEntry[] options = { { null } }; +public bool hidden_startup = false; public bool log_debug = false; public bool log_network = false; public bool log_serializer = false; diff --git a/src/client/application/geary-config.vala b/src/client/application/geary-config.vala index ad42a212..2e843042 100644 --- a/src/client/application/geary-config.vala +++ b/src/client/application/geary-config.vala @@ -16,6 +16,7 @@ public class Configuration { public const string SPELL_CHECK_KEY = "spell-check"; public const string PLAY_SOUNDS_KEY = "play-sounds"; public const string SHOW_NOTIFICATIONS_KEY = "show-notifications"; + public const string STARTUP_NOTIFICATIONS_KEY = "startup-notifications"; public const string ASK_OPEN_ATTACHMENT_KEY = "ask-open-attachment"; public const string COMPOSE_AS_HTML_KEY = "compose-as-html"; @@ -63,6 +64,11 @@ public class Configuration { public bool show_notifications { get { return settings.get_boolean(SHOW_NOTIFICATIONS_KEY); } } + + public bool startup_notifications { + get { return settings.get_boolean(STARTUP_NOTIFICATIONS_KEY); } + set { set_boolean(STARTUP_NOTIFICATIONS_KEY, value); } + } private const string CLOCK_FORMAT_KEY = "clock-format"; private const string TIME_FORMAT_KEY = "time-format"; diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala index aa9c01cd..2c4b4f8e 100644 --- a/src/client/application/geary-controller.vala +++ b/src/client/application/geary-controller.vala @@ -79,6 +79,8 @@ public class GearyController : Geary.BaseObject { public Geary.App.ConversationMonitor? current_conversations { get; private set; default = null; } + public AutostartManager? autostart_manager { get; private set; default = null; } + private Geary.Account? current_account = null; private Gee.HashMap email_stores = new Gee.HashMap(); @@ -218,6 +220,9 @@ public class GearyController : Geary.BaseObject { main_window.conversation_list_view.grab_focus(); + // instantiate here to ensure that Config is initialized and ready + autostart_manager = new AutostartManager(); + // Start Geary. try { yield Geary.Engine.instance.open_async(GearyApplication.instance.get_user_data_directory(), @@ -916,7 +921,7 @@ public class GearyController : Geary.BaseObject { */ private void display_main_window_if_ready() { if (did_attempt_open_all_accounts() && !upgrade_dialog.visible && - !cancellable_open_account.is_cancelled()) + !cancellable_open_account.is_cancelled() && !Args.hidden_startup) main_window.show_all(); } diff --git a/src/client/components/main-window.vala b/src/client/components/main-window.vala index cd50bce6..7809dab6 100644 --- a/src/client/components/main-window.vala +++ b/src/client/components/main-window.vala @@ -96,6 +96,9 @@ public class MainWindow : Gtk.ApplicationWindow { } private bool on_delete_event() { + if (Args.hidden_startup || GearyApplication.instance.config.startup_notifications) + return hide_on_delete(); + GearyApplication.instance.exit(); return true; diff --git a/src/client/dialogs/preferences-dialog.vala b/src/client/dialogs/preferences-dialog.vala index 4df1d9dc..30fb0d7f 100644 --- a/src/client/dialogs/preferences-dialog.vala +++ b/src/client/dialogs/preferences-dialog.vala @@ -21,9 +21,12 @@ public class PreferencesDialog : Object { config.bind(Configuration.SPELL_CHECK_KEY, builder.get_object("spell_check"), "active"); config.bind(Configuration.PLAY_SOUNDS_KEY, builder.get_object("play_sounds"), "active"); config.bind(Configuration.SHOW_NOTIFICATIONS_KEY, builder.get_object("show_notifications"), "active"); + config.bind(Configuration.STARTUP_NOTIFICATIONS_KEY, builder.get_object("startup_notifications"), "active"); } public void run() { + // Sync startup notification option with file state + GearyApplication.instance.controller.autostart_manager.sync_with_config(); dialog.show_all(); dialog.run(); dialog.destroy(); diff --git a/ui/preferences.glade b/ui/preferences.glade index c6ccab45..2342747c 100644 --- a/ui/preferences.glade +++ b/ui/preferences.glade @@ -210,6 +210,27 @@ 1 + + + Notify of new mail at start_up + True + True + False + 12 + 5 + 5 + 5 + True + 0 + True + + + 0 + 9 + 1 + 1 + +