/* linphone, gtk interface. Copyright (C) 2014 Belledonne Communications SARL 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "linphone.h" #ifdef GDK_WINDOWING_X11 #include #elif defined(_WIN32) #include #elif defined(__APPLE__) extern void *gdk_quartz_window_get_nswindow(GdkWindow *window); extern void *gdk_quartz_window_get_nsview(GdkWindow *window); #endif #include enum { TARGET_STRING, TARGET_TEXT, TARGET_URILIST }; static GtkTargetEntry targets[] = { { "text/uri-list", GTK_TARGET_OTHER_APP, TARGET_URILIST }, }; static void set_video_controls_position(GtkWidget *video_window); static void on_end_of_play(LinphonePlayer *player){ linphone_player_close(player); } static void drag_data_received(GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *selection_data, guint target_type, guint time, gpointer user_data){ int datalen=gtk_selection_data_get_length(selection_data); const void *data=gtk_selection_data_get_data(selection_data); LinphoneCall *call=g_object_get_data(G_OBJECT(widget),"call"); ms_message("target_type=%i, datalen=%i, data=%p",target_type,datalen,data); if (target_type==TARGET_URILIST && data){ LinphonePlayer *player=linphone_call_get_player(call); char *path=ms_strdup(data); while (datalen&&(path[datalen-1]=='\r'||path[datalen-1]=='\n')) { path[datalen-1]='\0'; datalen--; } if (player){ LinphonePlayerCbs *cbs = linphone_player_get_callbacks(player); linphone_player_cbs_set_eof_reached(cbs, on_end_of_play); const char* filepath = (strstr(path,"file://")==path) ? path+strlen("file://") : path; if (linphone_player_open(player,filepath)==0){ linphone_player_start(player); }else{ GtkWidget *warn=gtk_message_dialog_new(GTK_WINDOW(widget),GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,GTK_BUTTONS_CLOSE, _("Cannot play %s."),filepath); g_signal_connect(warn,"response",(GCallback)gtk_widget_destroy,NULL); gtk_widget_show(warn); } } ms_free(path); } gtk_drag_finish (context, TRUE, FALSE, time); } static gboolean drag_drop(GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, guint time, gpointer user_data){ #if GTK_CHECK_VERSION(2,21,0) GList *l=gdk_drag_context_list_targets(drag_context); GList *elem; if (l){ ms_message("drag_drop"); /* Choose the best target type */ for(elem=l;elem!=NULL;elem=g_list_next(elem)){ char *name=gdk_atom_name(GDK_POINTER_TO_ATOM(elem->data)); ms_message("target: %s",name); g_free(name); } }else{ ms_warning("drag_drop no targets"); return FALSE; } #endif return TRUE; } static void *get_native_handle(GdkWindow *gdkw){ #ifdef GDK_WINDOWING_X11 return (void *)GDK_WINDOW_XID(gdkw); #elif defined(_WIN32) return (void *)GDK_WINDOW_HWND(gdkw); #elif defined(__APPLE__) return (void *)gdk_quartz_window_get_nsview(gdkw); #endif g_warning("No way to get the native handle from gdk window"); return 0; } static void _resize_video_window(GtkWidget *video_window, MSVideoSize vsize){ MSVideoSize cur; gtk_window_get_size(GTK_WINDOW(video_window),&cur.width,&cur.height); if (vsize.width*vsize.height > cur.width*cur.height || ms_video_size_get_orientation(vsize)!=ms_video_size_get_orientation(cur) ){ gtk_window_resize(GTK_WINDOW(video_window),vsize.width,vsize.height); } } static gboolean resize_video_window(LinphoneCall *call){ const LinphoneCallParams *params=linphone_call_get_current_params(call); if (params){ MSVideoSize vsize=linphone_call_params_get_received_video_size(params); if (vsize.width>0 && vsize.height>0){ GtkWidget *callview=(GtkWidget*)linphone_call_get_user_pointer(call); GtkWidget *video_window=(GtkWidget*)g_object_get_data(G_OBJECT(callview),"video_window"); if (video_window){ _resize_video_window(video_window,vsize); } } } return TRUE; } static void on_video_window_destroy(GtkWidget *w, guint timeout){ g_source_remove(timeout); linphone_core_set_native_video_window_id(linphone_gtk_get_core(),LINPHONE_VIDEO_DISPLAY_NONE); } static void video_window_set_fullscreen(GtkWidget *w, gboolean val){ if (val){ g_object_set_data(G_OBJECT(w),"fullscreen",GINT_TO_POINTER(1)); gtk_window_fullscreen(GTK_WINDOW(w)); }else{ g_object_set_data(G_OBJECT(w),"fullscreen",GINT_TO_POINTER(0)); gtk_window_unfullscreen(GTK_WINDOW(w)); } } /*old names in old version of gdk*/ #ifndef GDK_KEY_Escape #define GDK_KEY_Escape GDK_Escape #define GDK_KEY_F GDK_F #define GDK_KEY_f GDK_f #endif static void on_video_window_key_press(GtkWidget *w, GdkEvent *ev, gpointer up){ g_message("Key press event"); switch(ev->key.keyval){ case GDK_KEY_f: case GDK_KEY_F: video_window_set_fullscreen(w,TRUE); break; case GDK_KEY_Escape: video_window_set_fullscreen(w,FALSE); break; } } static void on_controls_response(GtkWidget *dialog, int response_id, GtkWidget *video_window){ gtk_widget_destroy(dialog); switch(response_id){ case GTK_RESPONSE_YES: video_window_set_fullscreen(video_window,TRUE); break; case GTK_RESPONSE_NO: video_window_set_fullscreen(video_window,FALSE); break; case GTK_RESPONSE_REJECT: { LinphoneCall *call=(LinphoneCall*)g_object_get_data(G_OBJECT(video_window),"call"); linphone_call_terminate(call); } break; case GTK_RESPONSE_APPLY: { LinphoneCall *call=(LinphoneCall*)g_object_get_data(G_OBJECT(video_window),"call"); char *path = (char *)linphone_gtk_get_snapshot_path(); linphone_call_take_video_snapshot(call, path); } } } static gboolean on_controls_destroy(GtkWidget *w){ GtkWidget *video_window=(GtkWidget*)g_object_get_data(G_OBJECT(w),"video_window"); gint timeout=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w),"timeout")); if (timeout!=0){ g_source_remove(timeout); g_object_set_data(G_OBJECT(w),"timeout",GINT_TO_POINTER(0)); } if (video_window) { g_object_set_data(G_OBJECT(video_window),"controls",NULL); } return FALSE; } static gboolean _set_video_controls_position(GtkWidget *video_window){ GtkWidget *w=(GtkWidget*)g_object_get_data(G_OBJECT(video_window),"controls"); if (w){ gint vw,vh; gint cw,ch; gint x,y; gtk_window_get_size(GTK_WINDOW(video_window),&vw,&vh); gtk_window_get_position(GTK_WINDOW(video_window),&x,&y); gtk_window_get_size(GTK_WINDOW(w),&cw,&ch); gtk_window_move(GTK_WINDOW(w),x+vw/2 - cw/2, y + vh - ch); } return FALSE; } static void set_video_controls_position(GtkWidget *video_window){ /*do it a first time*/ _set_video_controls_position(video_window); /*and schedule to do it a second time in order to workaround a bug in fullscreen mode, where poistion is not taken into account the first time*/ g_timeout_add(0,(GSourceFunc)_set_video_controls_position,video_window); } static gboolean video_window_moved(GtkWidget *widget, GdkEvent *event, gpointer user_data){ /*Workaround to Video window bug on Windows. */ /* set_video_controls_position(widget); */ return FALSE; } static gint do_gtk_widget_destroy(GtkWidget *w){ gtk_widget_destroy(w); return FALSE; } static void schedule_video_controls_disapearance(GtkWidget *w){ gint timeout=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(w),"timeout")); if (timeout != 0) g_source_remove(timeout); timeout=g_timeout_add(3000,(GSourceFunc)do_gtk_widget_destroy,w); g_object_set_data(G_OBJECT(w),"timeout",GINT_TO_POINTER(timeout)); } static GtkWidget *show_video_controls(GtkWidget *video_window){ GtkWidget *w; w=(GtkWidget*)g_object_get_data(G_OBJECT(video_window),"controls"); if (!w){ gboolean isfullscreen=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(video_window),"fullscreen")); const char *stock_button=isfullscreen ? GTK_STOCK_LEAVE_FULLSCREEN : GTK_STOCK_FULLSCREEN; gint response_id=isfullscreen ? GTK_RESPONSE_NO : GTK_RESPONSE_YES ; GtkWidget *image = gtk_image_new_from_icon_name(linphone_gtk_get_ui_config("stop_call_icon_name","linphone-stop-call"), GTK_ICON_SIZE_BUTTON); GtkWidget *button; w=gtk_dialog_new_with_buttons("",GTK_WINDOW(video_window),GTK_DIALOG_DESTROY_WITH_PARENT,stock_button,response_id,NULL); gtk_window_set_opacity(GTK_WINDOW(w),0.5); gtk_window_set_decorated(GTK_WINDOW(w),FALSE); button=gtk_button_new_with_label(_("Hang up")); gtk_button_set_image(GTK_BUTTON(button), image); gtk_widget_show(button); gtk_dialog_add_action_widget(GTK_DIALOG(w),button,GTK_RESPONSE_REJECT); button=gtk_button_new_with_label(_("Take screenshot")); image = gtk_image_new_from_icon_name("linphone-take-screenshot", GTK_ICON_SIZE_BUTTON); gtk_button_set_image(GTK_BUTTON(button), image); gtk_widget_show(button); gtk_dialog_add_action_widget(GTK_DIALOG(w),button,GTK_RESPONSE_APPLY); g_signal_connect(w,"response",(GCallback)on_controls_response,video_window); schedule_video_controls_disapearance(w); g_signal_connect(w,"destroy",(GCallback)on_controls_destroy,NULL); g_object_set_data(G_OBJECT(w),"video_window",video_window); g_object_set_data(G_OBJECT(video_window),"controls",w); set_video_controls_position(video_window); gtk_widget_show(w); }else{ schedule_video_controls_disapearance(w); } return w; } static GtkWidget *create_video_window(LinphoneCall *call){ char *remote,*title; GtkWidget *video_window; const LinphoneAddress *addr; guint timeout; MSVideoSize vsize={MS_VIDEO_SIZE_CIF_W,MS_VIDEO_SIZE_CIF_H}; GdkColor color; addr=linphone_call_get_remote_address(call); remote=linphone_gtk_address(addr); video_window=gtk_window_new(GTK_WINDOW_TOPLEVEL); /*gtk_window_set_transient_for(GTK_WINDOW(video_window), GTK_WINDOW(linphone_gtk_get_main_window()));*/ title=g_strdup_printf("%s - Video call with %s",linphone_gtk_get_ui_config("title","Linphone"),remote); ms_free(remote); gtk_window_set_title(GTK_WINDOW(video_window),title); g_free(title); gtk_window_resize(GTK_WINDOW(video_window),vsize.width,vsize.height); gdk_color_parse("black",&color); gtk_widget_modify_bg(video_window,GTK_STATE_NORMAL,&color); gtk_drag_dest_set(video_window, GTK_DEST_DEFAULT_ALL, targets, sizeof(targets)/sizeof(GtkTargetEntry), GDK_ACTION_COPY); gtk_widget_show(video_window); gdk_window_set_events(gtk_widget_get_window(video_window), gdk_window_get_events(gtk_widget_get_window(video_window)) | GDK_POINTER_MOTION_MASK); timeout=g_timeout_add(500,(GSourceFunc)resize_video_window,call); g_signal_connect(video_window,"destroy",(GCallback)on_video_window_destroy,GINT_TO_POINTER(timeout)); g_signal_connect(video_window,"key-press-event",(GCallback)on_video_window_key_press,NULL); g_signal_connect_swapped(video_window,"motion-notify-event",(GCallback)show_video_controls,video_window); g_signal_connect(video_window,"configure-event",(GCallback)video_window_moved,NULL); g_signal_connect(video_window, "drag-data-received",(GCallback)drag_data_received, NULL); g_signal_connect(video_window, "drag-drop",(GCallback)drag_drop, NULL); g_object_set_data(G_OBJECT(video_window),"call",call); return video_window; } void linphone_gtk_in_call_show_video(LinphoneCall *call){ GtkWidget *callview=(GtkWidget*)linphone_call_get_user_pointer(call); GtkWidget *video_window=(GtkWidget*)g_object_get_data(G_OBJECT(callview),"video_window"); const LinphoneCallParams *params=linphone_call_get_current_params(call); LinphoneCore *lc=linphone_gtk_get_core(); if (((bool_t)lp_config_get_int(linphone_core_get_config(lc), "video", "rtp_io", FALSE)) == FALSE) { if (linphone_call_get_state(call)!=LinphoneCallPaused && params && linphone_call_params_video_enabled(params)){ if (video_window==NULL){ video_window=create_video_window(call); g_object_set_data(G_OBJECT(callview),"video_window",video_window); } linphone_core_set_native_video_window_id(lc,get_native_handle(gtk_widget_get_window(video_window))); gtk_window_present(GTK_WINDOW(video_window)); }else{ if (video_window){ gtk_widget_destroy(video_window); g_object_set_data(G_OBJECT(callview),"video_window",NULL); } } } } static void on_video_preview_destroyed(GtkWidget *video_preview, GtkWidget *mw){ LinphoneCore *lc=linphone_gtk_get_core(); guint timeout_id=GPOINTER_TO_INT(g_object_get_data(G_OBJECT(video_preview),"timeout-id")); g_object_set_data(G_OBJECT(mw),"video_preview",NULL); linphone_core_enable_video_preview(lc,FALSE); linphone_core_set_native_preview_window_id(lc,(void *)(unsigned long)-1); g_source_remove(timeout_id); } GtkWidget *linphone_gtk_get_camera_preview_window(void){ return (GtkWidget *)g_object_get_data(G_OBJECT(linphone_gtk_get_main_window()),"video_preview"); } static gboolean check_preview_size(GtkWidget *video_preview){ MSVideoSize vsize=linphone_core_get_current_preview_video_size(linphone_gtk_get_core()); if (vsize.width && vsize.height){ MSVideoSize cur; gtk_window_get_size(GTK_WINDOW(video_preview),&cur.width,&cur.height); if (cur.width!=vsize.width || cur.height!=vsize.height){ gtk_window_resize(GTK_WINDOW(video_preview),vsize.width,vsize.height); } } return TRUE; } void linphone_gtk_show_camera_preview_clicked(GtkButton *button){ GtkWidget *mw=linphone_gtk_get_main_window(); GtkWidget *video_preview=(GtkWidget *)g_object_get_data(G_OBJECT(mw),"video_preview"); if (!video_preview){ gchar *title; LinphoneCore *lc=linphone_gtk_get_core(); GdkColor color; guint tid; video_preview=gtk_window_new(GTK_WINDOW_TOPLEVEL); title=g_strdup_printf("%s - Video preview",linphone_gtk_get_ui_config("title","Linphone")); gtk_window_set_title(GTK_WINDOW(video_preview),title); gdk_color_parse("black",&color); gtk_widget_modify_bg(video_preview,GTK_STATE_NORMAL,&color); g_free(title); g_object_set_data(G_OBJECT(mw),"video_preview",video_preview); g_signal_connect(video_preview,"destroy",(GCallback)on_video_preview_destroyed,mw); gtk_widget_show(video_preview); linphone_core_set_native_preview_window_id(lc,get_native_handle(gtk_widget_get_window(video_preview))); linphone_core_enable_video_preview(lc,TRUE); tid=g_timeout_add(100,(GSourceFunc)check_preview_size,video_preview); g_object_set_data(G_OBJECT(video_preview),"timeout-id",GINT_TO_POINTER(tid)); } }