diff --git a/gtk/CMakeLists.txt b/gtk/CMakeLists.txt index ea923ea6d..bacbb448f 100644 --- a/gtk/CMakeLists.txt +++ b/gtk/CMakeLists.txt @@ -64,6 +64,7 @@ set(SOURCE_FILES main.c propertybox.c singleinstance.c + status_icon.c support.c update.c utils.c diff --git a/gtk/Makefile.am b/gtk/Makefile.am index f979376ef..03c209b55 100644 --- a/gtk/Makefile.am +++ b/gtk/Makefile.am @@ -55,6 +55,7 @@ linphone_SOURCES= \ config-fetching.c \ audio_assistant.c \ videowindow.c \ + status_icon.c \ linphone.h if BUILD_WIZARD linphone_SOURCES+= \ diff --git a/gtk/main.c b/gtk/main.c index aa993cf4c..d4f5d6718 100644 --- a/gtk/main.c +++ b/gtk/main.c @@ -55,6 +55,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include #endif +#include "status_icon.h" const char *this_program_ident_string="linphone_ident_string=" LINPHONE_VERSION; @@ -1496,15 +1497,6 @@ void linphone_gtk_link_to_website(GtkWidget *item){ linphone_gtk_open_browser(home); } -#ifndef HAVE_GTK_OSX - -static GtkStatusIcon *icon=NULL; - -static void icon_popup_menu(GtkStatusIcon *status_icon, guint button, guint activate_time, gpointer user_data){ - GtkWidget *menu=(GtkWidget*)g_object_get_data(G_OBJECT(status_icon),"menu"); - gtk_menu_popup(GTK_MENU(menu),NULL,NULL,gtk_status_icon_position_menu,status_icon,button,activate_time); -} - static GtkWidget *create_icon_menu(){ GtkWidget *menu=gtk_menu_new(); GtkWidget *menu_item; @@ -1542,7 +1534,7 @@ void linphone_gtk_save_main_window_position(GtkWindow* mw, GdkEvent *event, gpoi gtk_window_get_position(GTK_WINDOW(mw), &main_window_x, &main_window_y); } -static void handle_icon_click() { +static void handle_icon_click(LinphoneStatusIcon *si, void *user_data) { GtkWidget *mw=linphone_gtk_get_main_window(); if (!gtk_window_is_active((GtkWindow*)mw)) { if(!gtk_widget_is_drawable(mw)){ @@ -1556,65 +1548,25 @@ static void handle_icon_click() { } } -static void linphone_gtk_init_status_icon(){ - const char *icon_path=linphone_gtk_get_ui_config("icon",LINPHONE_ICON); - const char *call_icon_path=linphone_gtk_get_ui_config("start_call_icon","startcall-green.png"); - GdkPixbuf *pbuf=create_pixbuf(icon_path); - GtkWidget *menu=create_icon_menu(); - const char *title; - title=linphone_gtk_get_ui_config("title",_("Linphone - a video internet phone")); - icon=gtk_status_icon_new_from_pixbuf(pbuf); -#if GTK_CHECK_VERSION(2,20,2) - gtk_status_icon_set_name(icon,title); -#endif - g_signal_connect_swapped(G_OBJECT(icon),"activate",(GCallback)handle_icon_click,NULL); - g_signal_connect(G_OBJECT(icon),"popup-menu",(GCallback)icon_popup_menu,NULL); - gtk_status_icon_set_tooltip(icon,title); - gtk_status_icon_set_visible(icon,TRUE); - g_object_set_data(G_OBJECT(icon),"menu",menu); - g_object_weak_ref(G_OBJECT(icon),(GWeakNotify)gtk_widget_destroy,menu); - g_object_set_data(G_OBJECT(icon),"icon",pbuf); - g_object_weak_ref(G_OBJECT(icon),(GWeakNotify)g_object_unref,pbuf); - pbuf=create_pixbuf(call_icon_path); - g_object_set_data(G_OBJECT(icon),"call_icon",pbuf); +static void linphone_gtk_init_status_icon(void) { + LinphoneStatusIcon *icon = linphone_status_icon_get(); + if(icon) { + GtkWidget *menu = create_icon_menu(); + LinphoneStatusIconParams *params = linphone_status_icon_params_new(); + linphone_status_icon_params_set_menu(params, menu); + linphone_status_icon_params_set_title(params, _("Linphone - a video internet phone")); + linphone_status_icon_params_set_on_click_cb(params, handle_icon_click, NULL); + linphone_status_icon_start(icon, params); + g_object_unref(G_OBJECT(menu)); + linphone_status_icon_params_unref(params); + } } -static gboolean do_icon_blink(GtkStatusIcon *gi){ - GdkPixbuf *call_icon=g_object_get_data(G_OBJECT(gi),"call_icon"); - GdkPixbuf *normal_icon=g_object_get_data(G_OBJECT(gi),"icon"); - GdkPixbuf *cur_icon=gtk_status_icon_get_pixbuf(gi); - if (cur_icon==call_icon){ - gtk_status_icon_set_from_pixbuf(gi,normal_icon); - }else{ - gtk_status_icon_set_from_pixbuf(gi,call_icon); +void linphone_gtk_status_icon_set_blinking(gboolean val) { + LinphoneStatusIcon *icon = linphone_status_icon_get(); + if(icon) { + linphone_status_icon_enable_blinking(icon, val); } - return TRUE; -} - -#endif - -void linphone_gtk_status_icon_set_blinking(gboolean val){ -#ifdef HAVE_GTK_OSX - static gint attention_id; - GtkosxApplication *theMacApp=gtkosx_application_get(); - if (val) - attention_id=gtkosx_application_attention_request(theMacApp,CRITICAL_REQUEST); - else gtkosx_application_cancel_attention_request(theMacApp,attention_id); -#else - if (icon!=NULL){ - guint tout; - tout=(unsigned)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(icon),"timeout")); - if (val && tout==0){ - tout=g_timeout_add(500,(GSourceFunc)do_icon_blink,icon); - g_object_set_data(G_OBJECT(icon),"timeout",GINT_TO_POINTER(tout)); - }else if (!val && tout!=0){ - GdkPixbuf *normal_icon=g_object_get_data(G_OBJECT(icon),"icon"); - g_source_remove(tout); - g_object_set_data(G_OBJECT(icon),"timeout",NULL); - gtk_status_icon_set_from_pixbuf(icon,normal_icon); - } - } -#endif } void linphone_gtk_options_activate(GtkWidget *item){ @@ -2018,10 +1970,6 @@ static void linphone_gtk_quit(void){ quit_done=TRUE; linphone_gtk_quit_core(); linphone_gtk_uninit_instance(); -#ifndef HAVE_GTK_OSX - g_object_unref(icon); - icon=NULL; -#endif #ifdef HAVE_NOTIFY notify_uninit(); #endif @@ -2242,10 +2190,6 @@ core_start: goto core_start; } if (config_file) g_free(config_file); -#ifndef HAVE_GTK_OSX - /*workaround a bug on win32 that makes status icon still present in the systray even after program exit.*/ - if (icon) gtk_status_icon_set_visible(icon,FALSE); -#endif free(progpath); /*output a translated "hello" string to the terminal, which allows the builder to check that translations are working.*/ if (selftest){ diff --git a/gtk/status_icon.c b/gtk/status_icon.c new file mode 100644 index 000000000..d57a04f10 --- /dev/null +++ b/gtk/status_icon.c @@ -0,0 +1,241 @@ +/* +linphone, gtk-glade interface. +Copyright (C) 2015 Belledonne Communications + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include +#include +#include "status_icon.h" +#include "linphone.h" + +struct _LinphoneStatusIconParams { + char *title; + GtkWidget *menu; + LinphoneStatusIconOnClickCallback on_click_cb; + void *user_data; + int ref; +}; + +LinphoneStatusIconParams *linphone_status_icon_params_new(void) { + return g_new0(LinphoneStatusIconParams, 1); +} + +LinphoneStatusIconParams *linphone_status_icon_params_ref(LinphoneStatusIconParams *obj) { + obj->ref++; + return obj; +} + +void linphone_status_icon_params_unref(LinphoneStatusIconParams *obj) { + obj->ref--; + if(obj->ref < 0) { + if(obj->title) g_free(obj->title); + if(obj->menu) g_object_unref(obj->menu); + g_free(obj); + } +} + +void linphone_status_icon_params_set_title(LinphoneStatusIconParams *obj, const char *title) { + if(obj->title) g_free(obj->title); + if(title) obj->title = g_strdup(title); + else obj->title = NULL; +} + +void linphone_status_icon_params_set_menu(LinphoneStatusIconParams *obj, GtkWidget *menu) { + if(obj->menu) g_object_unref(obj->menu); + if(menu) obj->menu = g_object_ref(menu); + else obj->menu = NULL; +} + +void linphone_status_icon_params_set_on_click_cb(LinphoneStatusIconParams *obj, LinphoneStatusIconOnClickCallback cb, void *user_data) { + obj->on_click_cb = cb; + obj->user_data = user_data; +} + + +typedef void (*LinphoneStatusIconDescInitFunc)(LinphoneStatusIcon *obj); +typedef void (*LinphoneStatusIconDescUninitFunc)(LinphoneStatusIcon *obj); +typedef void (*LinphoneStatusIconDescStartFunc)(LinphoneStatusIcon *obj); +typedef void (*LinphoneStatusIconDescEnableBlinkingFunc)(LinphoneStatusIcon *obj, gboolean enable); +typedef gboolean (*LinphoneStatusIconDescIsSupported)(void); + +typedef struct { + const char *impl_name; + LinphoneStatusIconDescInitFunc init; + LinphoneStatusIconDescUninitFunc uninit; + LinphoneStatusIconDescStartFunc start; + LinphoneStatusIconDescEnableBlinkingFunc enable_blinking; + LinphoneStatusIconDescIsSupported is_supported; +} _LinphoneStatusIconDesc; + +static const _LinphoneStatusIconDesc *_linphone_status_icon_impls[]; + +static const _LinphoneStatusIconDesc *_status_icon_find_instance(void) { + int i; + for(i=0; _linphone_status_icon_impls[i] && !_linphone_status_icon_impls[i]->is_supported(); i++); + return _linphone_status_icon_impls[i]; +} + + +struct _LinphoneStatusIcon { + const _LinphoneStatusIconDesc *desc; + LinphoneStatusIconParams *params; + void *data; +}; + +static LinphoneStatusIcon *_linphone_status_icon_new(const _LinphoneStatusIconDesc *desc) { + LinphoneStatusIcon *si = (LinphoneStatusIcon *)g_new0(LinphoneStatusIcon, 1); + si->desc = desc; + if(desc->init) desc->init(si); + return si; +} + +static void _linphone_status_icon_free(LinphoneStatusIcon *obj) { + if(obj->desc->uninit) obj->desc->uninit(obj->data); + g_free(obj); +} + +const char *linphone_status_icon_get_implementation_name(const LinphoneStatusIcon *obj) { + return obj->desc->impl_name; +} + +void linphone_status_icon_start(LinphoneStatusIcon *obj, LinphoneStatusIconParams *params) { + obj->params = linphone_status_icon_params_ref(params); + obj->desc->start(obj); +} + +void linphone_status_icon_enable_blinking(LinphoneStatusIcon *obj, gboolean enable) { + obj->desc->enable_blinking(obj, enable); +} + +static void _linphone_status_icon_notify_click(LinphoneStatusIcon *obj) { + if(obj->params->on_click_cb) { + obj->params->on_click_cb(obj, obj->params->user_data); + } +} + + +static LinphoneStatusIcon *_linphone_status_icon_instance = NULL; + +static void _linphone_status_icon_free_singleton(void) { + _linphone_status_icon_free(_linphone_status_icon_instance); +} + +LinphoneStatusIcon *linphone_status_icon_get(void) { + if(_linphone_status_icon_instance == NULL) { + const _LinphoneStatusIconDesc *desc = _status_icon_find_instance(); + if(desc) { + _linphone_status_icon_instance = _linphone_status_icon_new(desc); + atexit(_linphone_status_icon_free_singleton); + } + } + return _linphone_status_icon_instance; +} + + +/* GtkStatusIcon implementation */ +static void _linphone_status_icon_impl_gtk_on_click_cb(LinphoneStatusIcon *si) { + _linphone_status_icon_notify_click(si); +} + +static void _linphone_status_icon_impl_gtk_popup_menu(GtkStatusIcon *status_icon, guint button, guint activate_time, LinphoneStatusIcon *si){ + GtkWidget *menu = si->params->menu; + gtk_menu_popup(GTK_MENU(menu),NULL,NULL,gtk_status_icon_position_menu,status_icon,button,activate_time); +} + +static void _linphone_status_icon_impl_gtk_init(LinphoneStatusIcon *si) { + const char *icon_path=linphone_gtk_get_ui_config("icon",LINPHONE_ICON); + const char *call_icon_path=linphone_gtk_get_ui_config("start_call_icon","startcall-green.png"); + GdkPixbuf *pbuf=create_pixbuf(icon_path); + GtkStatusIcon *icon=gtk_status_icon_new_from_pixbuf(pbuf); + g_signal_connect_swapped(G_OBJECT(icon),"activate", G_CALLBACK(_linphone_status_icon_impl_gtk_on_click_cb), si); + g_signal_connect(G_OBJECT(icon), "popup-menu", G_CALLBACK(_linphone_status_icon_impl_gtk_popup_menu), si); + g_object_set_data(G_OBJECT(icon),"icon",pbuf); + g_object_unref(pbuf); + pbuf=create_pixbuf(call_icon_path); + g_object_set_data(G_OBJECT(icon),"call_icon",pbuf); + g_object_unref(pbuf); + si->data = icon; +} + +// static void _linphone_status_icon_impl_gtk_uninit(LinphoneStatusIcon *si) { +// g_object_unref((GtkStatusIcon *)si->data); +// } + +static void _linphone_status_icon_impl_gtk_start(LinphoneStatusIcon *si) { + GtkStatusIcon *icon = GTK_STATUS_ICON(si->data); +#if GTK_CHECK_VERSION(2,20,2) + gtk_status_icon_set_name(icon, si->params->title); +#endif + gtk_status_icon_set_visible(icon,TRUE); +} + +static gboolean _linphone_status_icon_impl_gtk_do_icon_blink_cb(GtkStatusIcon *gi){ + GdkPixbuf *call_icon=g_object_get_data(G_OBJECT(gi),"call_icon"); + GdkPixbuf *normal_icon=g_object_get_data(G_OBJECT(gi),"icon"); + GdkPixbuf *cur_icon=gtk_status_icon_get_pixbuf(gi); + if (cur_icon==call_icon){ + gtk_status_icon_set_from_pixbuf(gi,normal_icon); + }else{ + gtk_status_icon_set_from_pixbuf(gi,call_icon); + } + return TRUE; +} + +static void _linphone_status_icon_impl_enable_blinking(LinphoneStatusIcon *si, gboolean val) { + GtkStatusIcon *icon = GTK_STATUS_ICON(si->data); +#ifdef HAVE_GTK_OSX + static gint attention_id; + GtkosxApplication *theMacApp=gtkosx_application_get(); + if (val) + attention_id=gtkosx_application_attention_request(theMacApp,CRITICAL_REQUEST); + else gtkosx_application_cancel_attention_request(theMacApp,attention_id); +#else + guint tout; + tout=(unsigned)GPOINTER_TO_INT(g_object_get_data(G_OBJECT(icon),"timeout")); + if (val && tout==0){ + tout=g_timeout_add(500,(GSourceFunc)_linphone_status_icon_impl_gtk_do_icon_blink_cb,icon); + g_object_set_data(G_OBJECT(icon),"timeout",GINT_TO_POINTER(tout)); + }else if (!val && tout!=0){ + GdkPixbuf *normal_icon=g_object_get_data(G_OBJECT(icon),"icon"); + g_source_remove(tout); + g_object_set_data(G_OBJECT(icon),"timeout",NULL); + gtk_status_icon_set_from_pixbuf(icon,normal_icon); + } +#endif +} + +static gboolean _linphone_status_icon_impl_is_supported(void) { +#ifndef HAVE_GTK_OSX + return 1; +#else + return 0; +#endif +} + +static const _LinphoneStatusIconDesc _linphone_status_icon_impl_gtk_desc = { + .impl_name = "gtk_status_icon", + .init = _linphone_status_icon_impl_gtk_init, + .uninit = NULL, + .start = _linphone_status_icon_impl_gtk_start, + .enable_blinking = _linphone_status_icon_impl_enable_blinking, + .is_supported = _linphone_status_icon_impl_is_supported +}; + +static const _LinphoneStatusIconDesc *_linphone_status_icon_impls[] = { + &_linphone_status_icon_impl_gtk_desc, + NULL +}; diff --git a/gtk/status_icon.h b/gtk/status_icon.h new file mode 100644 index 000000000..630a7493e --- /dev/null +++ b/gtk/status_icon.h @@ -0,0 +1,42 @@ +/* +linphone, gtk-glade interface. +Copyright (C) 2015 Belledonne Communications + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +#include + +struct _LinphoneStatusIcon; +typedef void (*LinphoneStatusIconOnClickCallback)(struct _LinphoneStatusIcon *si, void *user_data); + + +typedef struct _LinphoneStatusIconParams LinphoneStatusIconParams; + +LinphoneStatusIconParams *linphone_status_icon_params_new(void); +LinphoneStatusIconParams *linphone_status_icon_params_ref(LinphoneStatusIconParams *obj); +void linphone_status_icon_params_unref(LinphoneStatusIconParams *obj); + +void linphone_status_icon_params_set_title(LinphoneStatusIconParams *obj, const char *title); +void linphone_status_icon_params_set_menu(LinphoneStatusIconParams *obj, GtkWidget *menu); +void linphone_status_icon_params_set_on_click_cb(LinphoneStatusIconParams* obj, LinphoneStatusIconOnClickCallback cb, void *user_data); + + +typedef struct _LinphoneStatusIcon LinphoneStatusIcon; + +LinphoneStatusIcon *linphone_status_icon_get(void); +const char *linphone_status_icon_get_implementation_name(const LinphoneStatusIcon *obj); +void linphone_status_icon_start(LinphoneStatusIcon *obj, LinphoneStatusIconParams *params); +void linphone_status_icon_enable_blinking(LinphoneStatusIcon *obj, gboolean enable);