Commit 982b2883 authored by Stepan Salenikovich's avatar Stepan Salenikovich

Add Ring systray icon

This adds an icon in the notification areas (systray) by default.
The icon also has a popup menu which allows the user to show or hide
the main widow and to quit Ring.

It uses 2 systray icon implementations:
- AppIndicator from Canonical
- GtkStatusIcon from Gtk, but deprecated

A CMakeLists option is added "USE_APPINDICATOR" and is ON by default.
However this adds a dependency on appindicator3-0.1. If the option is
set to OFF or the dependency is not found then we fallback on using
GtkStatusIcon.

AppIndicator is prefered so that the status icon appears for Unity
users, as unity does not support GtkStatusIcon by default.

Additionaly, the GSettings schema has been modified slightly. The
general Ring settings menu also now says has the option "Show Ring
icon in the notification area (systray)" instead of of "Hide Ring
on close instead of quitting". This mimics other gtk app behavior,
such as Transmission. When the systray is enabled, closing the main
window will not quit Ring. When the systray is disabled closing the
main window will quit Ring so that it doesn't continue to run
withou the user being aware.

Tuleap: #370
Change-Id: I9f5c2811e135d20b0b6c81ab4a80be748f6a1be3
parent 526c41eb
......@@ -66,6 +66,7 @@ ELSE(LibRingClient_PROJECT_DIR)
FIND_PACKAGE(LibRingClient REQUIRED)
ENDIF(LibRingClient_PROJECT_DIR)
OPTION(USE_APPINDICATOR "Use AppIndicator instead of GtkStatusIcon for the systray (if appindicator3-0.1 is found)" ON)
# find packages
FIND_PACKAGE(PkgConfig REQUIRED)
......@@ -78,6 +79,17 @@ PKG_CHECK_MODULES(LIBNOTIFY libnotify>=0.7.6) #optional
FIND_PACKAGE(Gettext) #optional for translations
PKG_CHECK_MODULES(LIBQRENCODE libqrencode>=3.4)
IF(USE_APPINDICATOR)
PKG_CHECK_MODULES(APPINDICATOR appindicator3-0.1) #optional
IF(APPINDICATOR_FOUND)
SET(HAVE_APPINDICATOR 1)
ELSE()
SET(HAVE_APPINDICATOR 0)
ENDIF()
ELSE()
SET(HAVE_APPINDICATOR 0)
ENDIF(USE_APPINDICATOR)
IF( LIBQRENCODE_FOUND )
MESSAGE(STATUS "Found libqrencode version >= 3.4: " ${LIBQRENCODE_VERSION})
ELSE()
......@@ -106,6 +118,7 @@ INCLUDE_DIRECTORIES(${CLUTTER_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${CLUTTERGTK_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${EBOOK_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${LIBNOTIFY_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${APPINDICATOR_INCLUDE_DIRS})
# link libs
LINK_DIRECTORIES(${GTK3_LIBRARY_DIRS})
......@@ -115,6 +128,7 @@ LINK_DIRECTORIES(${CLUTTER_LIBRARY_DIRS})
LINK_DIRECTORIES(${CLUTTERGTK_LIBRARY_DIRS})
LINK_DIRECTORIES(${EBOOK_LIBRARY_DIRS})
LINK_DIRECTORIES(${LIBNOTIFY_LIBRARY_DIRS})
LINK_DIRECTORIES(${APPINDICATOR_LIBRARY_DIRS})
# lib compiler flags
ADD_DEFINITIONS(${GTK3_CFLAGS})
......@@ -125,6 +139,7 @@ ADD_DEFINITIONS(${CLUTTER_CFLAGS})
ADD_DEFINITIONS(${CLUTTERGTK_CFLAGS})
ADD_DEFINITIONS(${EBOOK_CFLAGS})
ADD_DEFINITIONS(${LIBNOTIFY_CFLAGS})
ADD_DEFINITIONS(${APPINDICATOR_CFLAGS})
IF(NOT ${ENABLE_STATIC} MATCHES false)
SET(QT5_MODULE_PATH ${QT5_PATH}/lib/cmake)
......@@ -351,6 +366,7 @@ TARGET_LINK_LIBRARIES(gnome-ring
${CLUTTERGTK_LIBRARIES}
${EBOOK_LIBRARIES}
${LIBNOTIFY_LIBRARIES}
${APPINDICATOR_LIBRARIES}
-lpthread
-lrt
-lqrencode
......@@ -364,6 +380,7 @@ TARGET_LINK_LIBRARIES(gnome-ring
${CLUTTERGTK_LIBRARIES}
${EBOOK_LIBRARIES}
${LIBNOTIFY_LIBRARIES}
${APPINDICATOR_LIBRARIES}
-lqrencode
)
ENDIF()
......
......@@ -6,20 +6,20 @@
<summary>Start Ring on login.</summary>
<description>Start Ring on login. Only supported on XDG compliant desktop environments.</description>
</key>
<key name="hide-on-close" type="b">
<key name="show-status-icon" type="b">
<default>true</default>
<summary>Hide on close instead of quitting.</summary>
<description>Hide the main window on close instead of quitting the application. If set to true, Ring will continue to run and be able to receive calls and messages if the main window is closed.</description>
<summary>Show status (system tray) icon.</summary>
<description>When set to true Ring will continue to run if the main window is closed.</description>
</key>
<key name="bring-window-to-front" type="b">
<default>true</default>
<summary>Bring window to foreground on incoming calls.</summary>
<description>Bring window to foreground on incoming calls.</description>
</key>
<key name="window-state-hidden" type="b">
<default>false</default>
<summary>Saves whether or not the main window is currently hidden or not.</summary>
<description>This is used when launching the application with the '--restore-last-window-state' option, which will launch the application with the main window in the same state as when it was last quit.</description>
<key name="show-main-window" type="b">
<default>true</default>
<summary>Saves whether or not the main window is currently visible or not.</summary>
<description>This is used when launching the application with the '--restore-last-window-state' option, which will launch the application with the main window in the same state as when Ring was last quit.</description>
</key>
<key name="chat-pane-horizontal" type="b">
<default>true</default>
......
......@@ -5,6 +5,7 @@
#define VERSION_PATCH @PROJECT_VERSION_PATCH@
#define USE_LIBNOTIFY @USE_LIBNOTIFY@
#define USE_APPINDICATOR @HAVE_APPINDICATOR@
#define RING_CLIENT_APP_ID "cx.ring.RingGnome"
......
......@@ -48,7 +48,7 @@ struct _GeneralSettingsViewPrivate
/* Rint settings */
GtkWidget *checkbutton_autostart;
GtkWidget *checkbutton_hideonclose;
GtkWidget *checkbutton_showstatusicon;
GtkWidget *checkbutton_bringtofront;
GtkWidget *radiobutton_chatright;
GtkWidget *radiobutton_chatbottom;
......@@ -139,8 +139,8 @@ general_settings_view_init(GeneralSettingsView *self)
g_settings_bind(priv->settings, "start-on-login",
priv->checkbutton_autostart, "active",
G_SETTINGS_BIND_DEFAULT);
g_settings_bind(priv->settings, "hide-on-close",
priv->checkbutton_hideonclose, "active",
g_settings_bind(priv->settings, "show-status-icon",
priv->checkbutton_showstatusicon, "active",
G_SETTINGS_BIND_DEFAULT);
g_settings_bind(priv->settings, "bring-window-to-front",
priv->checkbutton_bringtofront, "active",
......@@ -171,7 +171,7 @@ general_settings_view_class_init(GeneralSettingsViewClass *klass)
"/cx/ring/RingGnome/generalsettingsview.ui");
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), GeneralSettingsView, checkbutton_autostart);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), GeneralSettingsView, checkbutton_hideonclose);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), GeneralSettingsView, checkbutton_showstatusicon);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), GeneralSettingsView, checkbutton_bringtofront);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), GeneralSettingsView, radiobutton_chatright);
gtk_widget_class_bind_template_child_private(GTK_WIDGET_CLASS (klass), GeneralSettingsView, radiobutton_chatbottom);
......
......@@ -56,6 +56,10 @@
#include "peerprofilecollection.h"
#include "localprofilecollection.h"
#if USE_APPINDICATOR
#include <libappindicator/app-indicator.h>
#endif
struct _RingClientClass
{
GtkApplicationClass parent_class;
......@@ -87,6 +91,9 @@ struct _RingClientPrivate {
GCancellable *cancellable;
gboolean restore_window_state;
gpointer systray_icon;
GtkWidget *icon_menu;
};
/* this union is used to pass ints as pointers and vice versa for GAction parameters*/
......@@ -144,8 +151,8 @@ action_quit(G_GNUC_UNUSED GSimpleAction *simple,
static void
action_about(G_GNUC_UNUSED GSimpleAction *simple,
G_GNUC_UNUSED GVariant *parameter,
gpointer user_data)
G_GNUC_UNUSED GVariant *parameter,
gpointer user_data)
{
g_return_if_fail(G_IS_APPLICATION(user_data));
RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(user_data);
......@@ -187,30 +194,31 @@ autostart_toggled(GSettings *settings, G_GNUC_UNUSED gchar *key, G_GNUC_UNUSED g
autostart_symlink(g_settings_get_boolean(settings, "start-on-login"));
}
static void
ring_window_show(RingClient *client)
show_main_window_toggled(RingClient *client)
{
g_return_if_fail(IS_RING_CLIENT(client));
RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);
g_return_if_fail(priv->win);
if (g_settings_get_boolean(priv->settings, "show-main-window")) {
gtk_window_present(GTK_WINDOW(priv->win));
} else {
gtk_widget_hide(priv->win);
}
}
g_debug("showing main window");
gtk_window_present(GTK_WINDOW(priv->win));
g_settings_set_boolean(priv->settings, "window-state-hidden", FALSE);
static void
ring_window_show(RingClient *client)
{
RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);
g_settings_set_boolean(priv->settings, "show-main-window", TRUE);
}
static void
ring_window_hide(RingClient *client)
{
g_return_if_fail(IS_RING_CLIENT(client));
RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);
g_return_if_fail(priv->win);
g_debug("hiding main window");
gtk_widget_hide(priv->win);
g_settings_set_boolean(priv->settings, "window-state-hidden", TRUE);
g_settings_set_boolean(priv->settings, "show-main-window", FALSE);
}
static gboolean
......@@ -219,7 +227,7 @@ on_close_window(GtkWidget *window, G_GNUC_UNUSED GdkEvent *event, RingClient *cl
g_return_val_if_fail(GTK_IS_WINDOW(window) && IS_RING_CLIENT(client), FALSE);
RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);
if (g_settings_get_boolean(priv->settings, "hide-on-close")) {
if (g_settings_get_boolean(priv->settings, "show-status-icon")) {
/* we want to simply hide the window and keep the client running */
ring_window_hide(client);
return TRUE; /* do not propogate event */
......@@ -229,15 +237,80 @@ on_close_window(GtkWidget *window, G_GNUC_UNUSED GdkEvent *event, RingClient *cl
}
}
#if !USE_APPINDICATOR
static void
popup_menu(GtkStatusIcon *self,
guint button,
guint when,
RingClient *client)
{
RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);
G_GNUC_BEGIN_IGNORE_DEPRECATIONS // GtkStatusIcon is deprecated since 3.14, but we fallback on it
gtk_menu_popup(GTK_MENU(priv->icon_menu), NULL, NULL, gtk_status_icon_position_menu, self, button, when);
G_GNUC_END_IGNORE_DEPRECATIONS
}
#endif
static void
init_systray(RingClient *client)
{
RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);
// init menu
if (!priv->icon_menu) {
auto builder = gtk_builder_new_from_resource("/cx/ring/RingGnome/ringiconmenu.ui");
auto menu_model = G_MENU_MODEL(gtk_builder_get_object(builder, "menu"));
priv->icon_menu = gtk_menu_new_from_model(menu_model);
/* enable the menu actions */
gtk_widget_insert_action_group(priv->icon_menu, "app", G_ACTION_GROUP(client));
g_object_unref(builder);
g_object_unref(menu_model);
g_object_ref_sink(priv->icon_menu);
}
#if USE_APPINDICATOR
auto indicator = app_indicator_new("ring", "ring", APP_INDICATOR_CATEGORY_COMMUNICATIONS);
app_indicator_set_status(indicator, APP_INDICATOR_STATUS_ACTIVE);
app_indicator_set_title(indicator, "ring");
/* app indicator requires a menu */
app_indicator_set_menu(indicator, GTK_MENU(priv->icon_menu));
priv->systray_icon = indicator;
#else
G_GNUC_BEGIN_IGNORE_DEPRECATIONS // GtkStatusIcon is deprecated since 3.14, but we fallback on it
auto status_icon = gtk_status_icon_new_from_icon_name("ring");
gtk_status_icon_set_title(status_icon, "ring");
G_GNUC_END_IGNORE_DEPRECATIONS
g_signal_connect_swapped(status_icon, "activate", G_CALLBACK(ring_window_show), client);
g_signal_connect(status_icon, "popup-menu", G_CALLBACK(popup_menu), client);
priv->systray_icon = status_icon;
#endif
}
static void
systray_toggled(GSettings *settings, const gchar *key, RingClient *client)
{
RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);
if (g_settings_get_boolean(settings, key)) {
if (!priv->systray_icon)
init_systray(client);
} else {
if (priv->systray_icon)
g_clear_object(&priv->systray_icon);
}
}
static void
ring_client_activate(GApplication *app)
{
RingClient *client = RING_CLIENT(app);
RingClientPrivate *priv = RING_CLIENT_GET_PRIVATE(client);
gboolean show_window = TRUE;
if (priv->win == NULL) {
// activate being called for the first time
priv->win = ring_main_window_new(GTK_APPLICATION(app));
/* make sure win is set to NULL when the window is destroyed */
......@@ -246,16 +319,20 @@ ring_client_activate(GApplication *app)
/* check if the window should be destoryed or not on close */
g_signal_connect(priv->win, "delete-event", G_CALLBACK(on_close_window), client);
/* the only case in which we don't want to show the main window is we're launching the
* primary instance of the application with the '-r' (--restore-last-window-state) option
* and the window was hidden when the application last quit
*/
if ( priv->restore_window_state && g_settings_get_boolean(priv->settings, "window-state-hidden") )
show_window = FALSE;
}
//track window state
g_signal_connect_swapped(priv->settings, "changed::show-main-window", G_CALLBACK(show_main_window_toggled), client);
/* if we didn't launch with the '-r' (--restore-last-window-state) option then force the
* show-main-window to true */
if (!priv->restore_window_state)
ring_window_show(client);
if (show_window)
// track sys icon state
g_signal_connect(priv->settings, "changed::show-status-icon", G_CALLBACK(systray_toggled), client);
systray_toggled(priv->settings, "show-status-icon", client);
} else {
// activate not being called for the first time, force showing of main window
ring_window_show(client);
}
}
static void
......@@ -344,6 +421,10 @@ ring_client_startup(GApplication *app)
g_action_map_add_action_entries(
G_ACTION_MAP(app), ring_actions, G_N_ELEMENTS(ring_actions), app);
/* GActions for settings */
auto action_window_visible = g_settings_create_action(priv->settings, "show-main-window");
g_action_map_add_action(G_ACTION_MAP(app), action_window_visible);
/* add accelerators */
ring_accelerators(RING_CLIENT(app));
......
......@@ -43,7 +43,7 @@ GtkWindow *ring_client_get_main_window(RingClient *client);
/**
* Sets if the client should attempt to restore the main window state (hidden or not) to what it was
* when it was last quit (stored by the "window-state-hidden" gsetting). This function must be
* when it was last quit (stored by the "show-main-window" gsetting). This function must be
* called before the main window is created for the first time for it to have an effect.
*/
void ring_client_set_restore_main_window_state(RingClient *client, gboolean restore);
......
......@@ -50,7 +50,7 @@
<!-- end box ring settings-->
<!-- end profil settings -->
</object>
</child>
</child>
</object>
<packing>
<property name="expand">False</property>
......@@ -95,8 +95,8 @@
</object>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_hideonclose">
<property name="label" translatable="yes">Hide Ring on close instead of quitting.</property>
<object class="GtkCheckButton" id="checkbutton_showstatusicon">
<property name="label" translatable="yes">Show Ring icon in the notification area (systray).</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="xalign">0</property>
......
<?xml version="1.0"?>
<interface>
<!-- interface-requires gtk+ 3.0 -->
<menu id="menu">
<section>
<item>
<attribute name="label" translatable="yes">Show Ring</attribute>
<attribute name="action">app.show-main-window</attribute>
</item>
</section>
<section>
<item>
<attribute name="label" translatable="yes">Quit</attribute>
<attribute name="action">app.quit</attribute>
</item>
</section>
</menu>
</interface>
......@@ -17,5 +17,6 @@
<file preprocess="xml-stripblanks">choosecontactview.ui</file>
<file preprocess="xml-stripblanks">chatview.ui</file>
<file preprocess="xml-stripblanks">avatarmanipulation.ui</file>
<file preprocess="xml-stripblanks">ringiconmenu.ui</file>
</gresource>
</gresources>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment