main.c 75.9 KB
Newer Older
aymeric's avatar
aymeric committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
/*
linphone, gtk-glade interface.
Copyright (C) 2008  Simon MORLAT (simon.morlat@linphone.org)

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.
*/


21
#define VIDEOSELFVIEW_DEFAULT 0
22

23
#include "linphone.h"
aymeric's avatar
aymeric committed
24
#include "lpconfig.h"
25
#include "liblinphone_gitversion.h"
aymeric's avatar
aymeric committed
26

27

aymeric's avatar
aymeric committed
28 29
#include <sys/types.h>
#include <sys/stat.h>
30
#ifndef WIN32
aymeric's avatar
aymeric committed
31
#include <unistd.h>
32
#endif
aymeric's avatar
aymeric committed
33

jehan's avatar
jehan committed
34
#ifdef HAVE_GTK_OSX
35 36 37
#include <gtkosxapplication.h>
#endif

38 39
#ifdef WIN32
#define chdir _chdir
40
#include "direct.h"
Simon Morlat's avatar
Simon Morlat committed
41 42 43
#ifndef F_OK
#define F_OK 00 /*visual studio does not define F_OK*/
#endif
44 45
#endif

46
#if defined(HAVE_NOTIFY1) || defined(HAVE_NOTIFY4)
47 48 49
#define HAVE_NOTIFY
#endif

50 51 52 53
#ifdef HAVE_NOTIFY
#include <libnotify/notify.h>
#endif

54 55 56
#ifdef ENABLE_NLS
#include <locale.h>
#endif
57

58

aymeric's avatar
aymeric committed
59

smorlat's avatar
smorlat committed
60 61
const char *this_program_ident_string="linphone_ident_string=" LINPHONE_VERSION;

aymeric's avatar
aymeric committed
62 63
static LinphoneCore *the_core=NULL;
static GtkWidget *the_ui=NULL;
64
static LinphoneLDAPContactProvider* ldap_provider = NULL;
aymeric's avatar
aymeric committed
65

66
static void linphone_gtk_global_state_changed(LinphoneCore *lc, LinphoneGlobalState state, const char*str);
Simon Morlat's avatar
Simon Morlat committed
67
static void linphone_gtk_registration_state_changed(LinphoneCore *lc, LinphoneProxyConfig *cfg, LinphoneRegistrationState rs, const char *msg);
68
static void linphone_gtk_notify_recv(LinphoneCore *lc, LinphoneFriend * fid);
aymeric's avatar
aymeric committed
69
static void linphone_gtk_new_unknown_subscriber(LinphoneCore *lc, LinphoneFriend *lf, const char *url);
70
static void linphone_gtk_auth_info_requested(LinphoneCore *lc, const char *realm, const char *username, const char *domain);
aymeric's avatar
aymeric committed
71
static void linphone_gtk_display_status(LinphoneCore *lc, const char *status);
72
static void linphone_gtk_configuring_status(LinphoneCore *lc, LinphoneConfiguringState status, const char *message);
aymeric's avatar
aymeric committed
73 74 75 76
static void linphone_gtk_display_message(LinphoneCore *lc, const char *msg);
static void linphone_gtk_display_warning(LinphoneCore *lc, const char *warning);
static void linphone_gtk_display_url(LinphoneCore *lc, const char *msg, const char *url);
static void linphone_gtk_call_log_updated(LinphoneCore *lc, LinphoneCallLog *cl);
Simon Morlat's avatar
Simon Morlat committed
77
static void linphone_gtk_call_state_changed(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState cs, const char *msg);
Simon Morlat's avatar
Simon Morlat committed
78
static void linphone_gtk_call_encryption_changed(LinphoneCore *lc, LinphoneCall *call, bool_t enabled, const char *token);
79
static void linphone_gtk_transfer_state_changed(LinphoneCore *lc, LinphoneCall *call, LinphoneCallState cstate);
80
static void linphone_gtk_dtmf_received(LinphoneCore *lc, LinphoneCall *call, int dtmf);
Margaux Clerc's avatar
Margaux Clerc committed
81
void linphone_gtk_save_main_window_position(GtkWindow* mw, GdkEvent *event, gpointer data);
82
static gboolean linphone_gtk_auto_answer(LinphoneCall *call);
83
void linphone_gtk_status_icon_set_blinking(gboolean val);
84
void _linphone_gtk_enable_video(gboolean val);
85
void linphone_gtk_on_uribar_changed(GtkEditable *uribar, gpointer user_data);
86
static void linphone_gtk_init_ui(void);
87
static void linphone_gtk_quit(void);
aymeric's avatar
aymeric committed
88

jehan's avatar
jehan committed
89
#ifndef HAVE_GTK_OSX
Margaux Clerc's avatar
Margaux Clerc committed
90 91
static gint main_window_x=0;
static gint main_window_y=0;
jehan's avatar
jehan committed
92
#endif
aymeric's avatar
aymeric committed
93
static gboolean verbose=0;
94
static gboolean quit_done=FALSE;
95 96
static gboolean auto_answer = 0;
static gchar * addr_to_call = NULL;
Margaux Clerc's avatar
Margaux Clerc committed
97
static int start_option = START_LINPHONE;
98
static gboolean no_video=FALSE;
smorlat's avatar
smorlat committed
99
static gboolean iconified=FALSE;
Margaux Clerc's avatar
Margaux Clerc committed
100
static gboolean run_audio_assistant=FALSE;
101
static gboolean selftest=FALSE;
102
static gchar *workingdir=NULL;
103
static char *progpath=NULL;
104
gchar *linphone_logfile=NULL;
105
static gboolean workaround_gtk_entry_chinese_bug=FALSE;
106
static gchar *custom_config_file=NULL;
107
static gboolean restart=FALSE;
108
static GtkWidget *config_fetching_dialog=NULL;
smorlat's avatar
smorlat committed
109

110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
#if _MSC_VER

#define LINPHONE_OPTION(optlname, optsname, optarg, optargdata, optdesc) \
{ \
	optlname, \
	optsname, \
	0, \
	optarg, \
	optargdata, \
	optdesc, \
	NULL \
}

#else

#define LINPHONE_OPTION(optlname, optsname, optarg, optargdata, optdesc) \
{ \
	.long_name = optlname, \
	.short_name = optsname, \
	.arg = optarg, \
	.arg_data = optargdata, \
	.description = optdesc, \
}

#endif

136
static GOptionEntry linphone_options[]={
137 138 139 140 141 142 143 144 145 146
	LINPHONE_OPTION("verbose",             '\0', G_OPTION_ARG_NONE,     (gpointer)&verbose,              N_("log to stdout some debug information while running.")),
	LINPHONE_OPTION("logfile",             'l',  G_OPTION_ARG_STRING,   &linphone_logfile,               N_("path to a file to write logs into.")),
	LINPHONE_OPTION("no-video",            '\0', G_OPTION_ARG_NONE,     (gpointer)&no_video,             N_("Start linphone with video disabled.")),
	LINPHONE_OPTION("iconified",           '\0', G_OPTION_ARG_NONE,     (gpointer)&iconified,            N_("Start only in the system tray, do not show the main interface.")),
	LINPHONE_OPTION("call",                'c',  G_OPTION_ARG_STRING,   &addr_to_call,                   N_("address to call right now")),
	LINPHONE_OPTION("auto-answer",         'a',  G_OPTION_ARG_NONE,     (gpointer) & auto_answer,        N_("if set automatically answer incoming calls")),
	LINPHONE_OPTION("workdir",             '\0', G_OPTION_ARG_STRING,   (gpointer) & workingdir,         N_("Specifiy a working directory (should be the base of the installation, eg: c:\\Program Files\\Linphone)")),
	LINPHONE_OPTION("config",              '\0', G_OPTION_ARG_FILENAME, (gpointer) &custom_config_file,  N_("Configuration file")),
	LINPHONE_OPTION("run-audio-assistant", '\0', G_OPTION_ARG_NONE,     (gpointer) &run_audio_assistant, N_("Run the audio assistant")),
	LINPHONE_OPTION("selftest",            '\0', G_OPTION_ARG_NONE,     (gpointer) &selftest,            N_("Run self test and exit 0 if succeed")),
147
	{0}
aymeric's avatar
aymeric committed
148 149 150
};

#define INSTALLED_XML_DIR PACKAGE_DATA_DIR "/linphone"
151
#define RELATIVE_XML_DIR
152
#define BUILD_TREE_XML_DIR "gtk"
smorlat's avatar
smorlat committed
153 154

#ifndef WIN32
aymeric's avatar
aymeric committed
155
#define CONFIG_FILE ".linphonerc"
Simon Morlat's avatar
Simon Morlat committed
156
#define SECRETS_FILE ".linphone-zidcache"
johan's avatar
johan committed
157
#define CERTIFICATES_PATH ".linphone-usr-crt"
smorlat's avatar
smorlat committed
158 159
#else
#define CONFIG_FILE "linphonerc"
Simon Morlat's avatar
Simon Morlat committed
160
#define SECRETS_FILE "linphone-zidcache"
johan's avatar
johan committed
161
#define CERTIFICATES_PATH "linphone-usr-crt"
smorlat's avatar
smorlat committed
162 163
#endif

Simon Morlat's avatar
Simon Morlat committed
164 165 166 167
char *linphone_gtk_get_config_file(const char *filename){
	const int path_max=1024;
	char *config_file=g_malloc0(path_max);
	if (filename==NULL) filename=CONFIG_FILE;
aymeric's avatar
aymeric committed
168 169
	/*try accessing a local file first if exists*/
	if (access(CONFIG_FILE,F_OK)==0){
170
		snprintf(config_file,path_max,"%s",filename);
171 172 173
	} else if (g_path_is_absolute(filename)) {
		snprintf(config_file,path_max,"%s",filename);
	} else{
aymeric's avatar
aymeric committed
174 175 176
#ifdef WIN32
		const char *appdata=getenv("APPDATA");
		if (appdata){
Simon Morlat's avatar
Simon Morlat committed
177 178 179
			snprintf(config_file,path_max,"%s\\%s",appdata,LINPHONE_CONFIG_DIR);
			CreateDirectory(config_file,NULL);
			snprintf(config_file,path_max,"%s\\%s\\%s",appdata,LINPHONE_CONFIG_DIR,filename);
aymeric's avatar
aymeric committed
180 181
		}
#else
182 183
		const char *home=getenv("HOME");
		if (home==NULL) home=".";
Simon Morlat's avatar
Simon Morlat committed
184
		snprintf(config_file,path_max,"%s/%s",home,filename);
aymeric's avatar
aymeric committed
185 186
#endif
	}
Simon Morlat's avatar
Simon Morlat committed
187
	return config_file;
smorlat's avatar
smorlat committed
188 189
}

190 191 192 193 194 195 196 197 198
#define FACTORY_CONFIG_FILE "linphonerc.factory"
static char _factory_config_file[1024];
static const char *linphone_gtk_get_factory_config_file(){
	/*try accessing a local file first if exists*/
	if (access(FACTORY_CONFIG_FILE,F_OK)==0){
		snprintf(_factory_config_file,sizeof(_factory_config_file),
						 "%s",FACTORY_CONFIG_FILE);
	} else {
		char *progdir;
199

200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
		if (progpath != NULL) {
			char *basename;
			progdir = strdup(progpath);
#ifdef WIN32
			basename = strrchr(progdir, '\\');
			if (basename != NULL) {
				basename ++;
				*basename = '\0';
				snprintf(_factory_config_file, sizeof(_factory_config_file),
								 "%s\\..\\%s", progdir, FACTORY_CONFIG_FILE);
			} else {
				if (workingdir!=NULL) {
					snprintf(_factory_config_file, sizeof(_factory_config_file),
									 "%s\\%s", workingdir, FACTORY_CONFIG_FILE);
				} else {
					free(progdir);
					return NULL;
				}
			}
#else
			basename = strrchr(progdir, '/');
			if (basename != NULL) {
				basename ++;
				*basename = '\0';
				snprintf(_factory_config_file, sizeof(_factory_config_file),
								 "%s/../share/Linphone/%s", progdir, FACTORY_CONFIG_FILE);
			} else {
				free(progdir);
				return NULL;
			}
#endif
			free(progdir);
		}
	}
	return _factory_config_file;
}

237
LinphoneLDAPContactProvider* linphone_gtk_get_ldap(void){
238 239 240
	return ldap_provider;
}

241 242 243 244
int linphone_gtk_is_ldap_supported(void){
	return linphone_ldap_contact_provider_available();
}

245 246 247
void linphone_gtk_set_ldap(LinphoneLDAPContactProvider* ldap)
{
	if( ldap_provider )
248
		linphone_contact_provider_unref(ldap_provider);
249

250
	ldap_provider = ldap ? linphone_ldap_contact_provider_ref( ldap )
251
						 : NULL;
252 253
}

254 255 256 257
void linphone_gtk_schedule_restart(void){
	restart=TRUE;
}

Margaux Clerc's avatar
Margaux Clerc committed
258 259 260 261
gboolean linphone_gtk_get_audio_assistant_option(void){
	return run_audio_assistant;
}

262
static void linphone_gtk_init_liblinphone(const char *config_file,
Simon Morlat's avatar
Simon Morlat committed
263
		const char *factory_config_file, const char *db_file) {
Simon Morlat's avatar
Simon Morlat committed
264
	LinphoneCoreVTable vtable={0};
Simon Morlat's avatar
Simon Morlat committed
265
	gchar *secrets_file=linphone_gtk_get_config_file(SECRETS_FILE);
johan's avatar
johan committed
266
	gchar *user_certificates_dir=linphone_gtk_get_config_file(CERTIFICATES_PATH);
Simon Morlat's avatar
Simon Morlat committed
267

268
	vtable.global_state_changed=linphone_gtk_global_state_changed;
Simon Morlat's avatar
Simon Morlat committed
269
	vtable.call_state_changed=linphone_gtk_call_state_changed;
Simon Morlat's avatar
Simon Morlat committed
270
	vtable.registration_state_changed=linphone_gtk_registration_state_changed;
271 272
	vtable.notify_presence_received=linphone_gtk_notify_recv;
	vtable.new_subscription_requested=linphone_gtk_new_unknown_subscriber;
Simon Morlat's avatar
Simon Morlat committed
273 274 275 276 277 278
	vtable.auth_info_requested=linphone_gtk_auth_info_requested;
	vtable.display_status=linphone_gtk_display_status;
	vtable.display_message=linphone_gtk_display_message;
	vtable.display_warning=linphone_gtk_display_warning;
	vtable.display_url=linphone_gtk_display_url;
	vtable.call_log_updated=linphone_gtk_call_log_updated;
279 280
	//vtable.text_received=linphone_gtk_text_received;
	vtable.message_received=linphone_gtk_text_received;
281
	vtable.is_composing_received=linphone_gtk_is_composing_received;
Simon Morlat's avatar
Simon Morlat committed
282 283
	vtable.refer_received=linphone_gtk_refer_received;
	vtable.buddy_info_updated=linphone_gtk_buddy_info_updated;
Simon Morlat's avatar
Simon Morlat committed
284
	vtable.call_encryption_changed=linphone_gtk_call_encryption_changed;
285
	vtable.transfer_state_changed=linphone_gtk_transfer_state_changed;
286
	vtable.dtmf_received=linphone_gtk_dtmf_received;
287
	vtable.configuring_status=linphone_gtk_configuring_status;
Simon Morlat's avatar
Simon Morlat committed
288

289
	the_core=linphone_core_new(&vtable,config_file,factory_config_file,NULL);
290
	linphone_core_migrate_to_multi_transport(the_core);
291
	//lp_config_set_int(linphone_core_get_config(the_core), "sip", "store_auth_info", 0);
292 293 294 295 296


	if( lp_config_has_section(linphone_core_get_config(the_core),"ldap") ){
		LpConfig* cfg = linphone_core_get_config(the_core);
		LinphoneDictionary* ldap_cfg = lp_config_section_to_dict(cfg, "ldap");
297
		linphone_gtk_set_ldap( linphone_ldap_contact_provider_create(the_core, ldap_cfg) );
298 299
	}

300
	linphone_core_set_user_agent(the_core,"Linphone", LINPHONE_VERSION);
301
	linphone_core_set_waiting_callback(the_core,linphone_gtk_wait,NULL);
Simon Morlat's avatar
Simon Morlat committed
302 303
	linphone_core_set_zrtp_secrets_file(the_core,secrets_file);
	g_free(secrets_file);
johan's avatar
johan committed
304 305
	linphone_core_set_user_certificates_path(the_core,user_certificates_dir);
	g_free(user_certificates_dir);
306 307
	linphone_core_enable_video_capture(the_core, TRUE);
	linphone_core_enable_video_display(the_core, TRUE);
308
	linphone_core_set_native_video_window_id(the_core,-1);/*don't create the window*/
309 310 311 312
	if (no_video) {
		_linphone_gtk_enable_video(FALSE);
		linphone_gtk_set_ui_config_int("videoselfview",0);
	}
Simon Morlat's avatar
Simon Morlat committed
313
	if (db_file) linphone_core_set_chat_database_path(the_core,db_file);
aymeric's avatar
aymeric committed
314 315 316 317 318 319 320 321 322 323
}

LinphoneCore *linphone_gtk_get_core(void){
	return the_core;
}

GtkWidget *linphone_gtk_get_main_window(){
	return the_ui;
}

324
void linphone_gtk_destroy_main_window() {
325
	linphone_gtk_destroy_window(the_ui);
326 327 328
	the_ui = NULL;
}

smorlat's avatar
smorlat committed
329
static void linphone_gtk_configure_window(GtkWidget *w, const char *window_name){
smorlat's avatar
smorlat committed
330 331 332
	static const char *icon_path=NULL;
	static const char *hiddens=NULL;
	static const char *shown=NULL;
333
	static bool_t config_loaded=FALSE;
smorlat's avatar
smorlat committed
334
	if (linphone_gtk_get_core()==NULL) return;
335 336
	if (config_loaded==FALSE){
		hiddens=linphone_gtk_get_ui_config("hidden_widgets",NULL);
smorlat's avatar
smorlat committed
337
		shown=linphone_gtk_get_ui_config("shown_widgets",NULL);
338
		icon_path=linphone_gtk_get_ui_config("icon",LINPHONE_ICON);
339 340
		config_loaded=TRUE;
	}
smorlat's avatar
smorlat committed
341
	if (hiddens)
Simon Morlat's avatar
Simon Morlat committed
342
		linphone_gtk_visibility_set(hiddens,window_name,w,FALSE);
smorlat's avatar
smorlat committed
343
	if (shown)
Simon Morlat's avatar
Simon Morlat committed
344
		linphone_gtk_visibility_set(shown,window_name,w,TRUE);
345 346
	if (icon_path) {
		GdkPixbuf *pbuf=create_pixbuf(icon_path);
Yann Diorcet's avatar
Yann Diorcet committed
347 348 349 350
		if(pbuf != NULL) {
			gtk_window_set_icon(GTK_WINDOW(w),pbuf);
			g_object_unref(G_OBJECT(pbuf));
		}
351
	}
smorlat's avatar
smorlat committed
352 353
}

354 355
static int get_ui_file(const char *name, char *path, int pathsize){
	snprintf(path,pathsize,"%s/%s.ui",BUILD_TREE_XML_DIR,name);
356
	if (access(path,F_OK)!=0){
357
		snprintf(path,pathsize,"%s/%s.ui",INSTALLED_XML_DIR,name);
358
		if (access(path,F_OK)!=0){
Simon Morlat's avatar
Simon Morlat committed
359
			g_error("Could not locate neither %s/%s.ui nor %s/%s.ui",BUILD_TREE_XML_DIR,name,
360 361
				INSTALLED_XML_DIR,name);
			return -1;
362 363
		}
	}
364 365 366
	return 0;
}

367 368 369 370 371 372
void linphone_gtk_destroy_window(GtkWidget *widget) {
	GtkBuilder* builder = g_object_get_data(G_OBJECT(widget), "builder");
	gtk_widget_destroy(widget);
	g_object_unref (G_OBJECT (builder));
}

373 374 375 376 377 378 379
GtkWidget *linphone_gtk_create_window(const char *window_name){
	GError* error = NULL;
	GtkBuilder* builder = gtk_builder_new ();
	char path[512];
	GtkWidget *w;

	if (get_ui_file(window_name,path,sizeof(path))==-1) return NULL;
380

Simon Morlat's avatar
Simon Morlat committed
381 382
	gtk_builder_set_translation_domain(builder,GETTEXT_PACKAGE);

383 384 385
	if (!gtk_builder_add_from_file (builder, path, &error)){
		g_error("Couldn't load builder file: %s", error->message);
		g_error_free (error);
386
		return NULL;
387 388 389 390 391 392
	}
	w=GTK_WIDGET(gtk_builder_get_object (builder,window_name));
	if (w==NULL){
		g_error("Could not retrieve '%s' window from xml file",window_name);
		return NULL;
	}
393
	g_object_set_data(G_OBJECT(w), "builder",builder);
394 395 396
	gtk_builder_connect_signals(builder,w);
	linphone_gtk_configure_window(w,window_name);
	return w;
aymeric's avatar
aymeric committed
397 398
}

399 400 401 402 403 404 405 406
GtkWidget *linphone_gtk_create_widget(const char *filename, const char *widget_name){
	char path[2048];
	GtkWidget *w;
	GtkBuilder* builder = gtk_builder_new ();
	GError *error=NULL;
	gchar *object_ids[2];
	object_ids[0]=g_strdup(widget_name);
	object_ids[1]=NULL;
407

408
	if (get_ui_file(filename,path,sizeof(path))==-1) return NULL;
409

Simon Morlat's avatar
Simon Morlat committed
410
	gtk_builder_set_translation_domain(builder,GETTEXT_PACKAGE);
411

412 413 414 415 416 417 418 419 420 421 422 423 424
	if (!gtk_builder_add_objects_from_file(builder,path,object_ids,&error)){
		g_error("Couldn't load %s from builder file %s: %s", widget_name,path,error->message);
		g_error_free (error);
		g_free(object_ids[0]);
		return NULL;
	}
	g_free(object_ids[0]);
	w=GTK_WIDGET(gtk_builder_get_object (builder,widget_name));
	if (w==NULL){
		g_error("Could not retrieve '%s' window from xml file",widget_name);
		return NULL;
	}
	g_object_set_data(G_OBJECT(w),"builder",builder);
425
	g_signal_connect_swapped(G_OBJECT(w),"destroy",(GCallback)g_object_unref,builder);
426 427 428 429
	gtk_builder_connect_signals(builder,w);
	return w;
}

430 431 432
static void entry_unmapped(GtkWidget *widget){
	ms_message("%s is unmapped, calling unrealize to workaround chinese bug.",G_OBJECT_TYPE_NAME(widget));
	gtk_widget_unrealize(widget);
433 434
}

aymeric's avatar
aymeric committed
435
GtkWidget *linphone_gtk_get_widget(GtkWidget *window, const char *name){
436
	GtkBuilder *builder;
437
	GObject *w;
438 439
	if (window==NULL) return NULL;
	builder=(GtkBuilder*)g_object_get_data(G_OBJECT(window),"builder");
440 441 442 443 444
	if (builder==NULL){
		g_error("Fail to retrieve builder from window !");
		return NULL;
	}
	w=gtk_builder_get_object(builder,name);
aymeric's avatar
aymeric committed
445 446 447
	if (w==NULL){
		g_error("No widget named %s found in xml interface.",name);
	}
448
	if (workaround_gtk_entry_chinese_bug){
449
		if (strcmp(G_OBJECT_TYPE_NAME(w),"GtkEntry")==0 || strcmp(G_OBJECT_TYPE_NAME(w),"GtkTextView")==0){
450 451
			if (g_object_get_data(G_OBJECT(w),"entry_bug_workaround")==NULL){
				g_object_set_data(G_OBJECT(w),"entry_bug_workaround",GINT_TO_POINTER(1));
452
				ms_message("%s is a %s",name,G_OBJECT_TYPE_NAME(w));
453 454 455 456
				g_signal_connect(G_OBJECT(w),"unmap",(GCallback)entry_unmapped,NULL);
			}
		}
	}
aymeric's avatar
aymeric committed
457 458 459 460 461 462 463
	return GTK_WIDGET(w);
}


void linphone_gtk_display_something(GtkMessageType type,const gchar *message){
	GtkWidget *dialog;
	GtkWidget *main_window=linphone_gtk_get_main_window();
464

aymeric's avatar
aymeric committed
465 466 467 468 469 470 471 472 473
	gtk_widget_show(main_window);
	if (type==GTK_MESSAGE_QUESTION)
	{
		/* draw a question box. link to dialog_click callback */
		dialog = gtk_message_dialog_new (
				GTK_WINDOW(main_window),
                                GTK_DIALOG_DESTROY_WITH_PARENT,
				GTK_MESSAGE_QUESTION,
                                GTK_BUTTONS_YES_NO,
smorlat's avatar
smorlat committed
474
                                "%s",
aymeric's avatar
aymeric committed
475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490
				(const gchar*)message);
		/* connect to some callback : REVISIT */
		/*
		g_signal_connect_swapped (G_OBJECT (dialog), "response",
                           G_CALLBACK (dialog_click),
                           G_OBJECT (dialog));
		*/
		/* actually show the box */
		gtk_widget_show(dialog);
	}
	else
	{
		dialog = gtk_message_dialog_new (GTK_WINDOW(main_window),
                                  GTK_DIALOG_DESTROY_WITH_PARENT,
                                  type,
                                  GTK_BUTTONS_CLOSE,
smorlat's avatar
smorlat committed
491
                                  "%s",
aymeric's avatar
aymeric committed
492 493 494 495 496 497 498 499 500 501 502 503 504 505 506
                                  (const gchar*)message);
		/* Destroy the dialog when the user responds to it (e.g. clicks a button) */
		g_signal_connect_swapped (G_OBJECT (dialog), "response",
                           G_CALLBACK (gtk_widget_destroy),
                           G_OBJECT (dialog));
		gtk_widget_show(dialog);
	}
}

void linphone_gtk_about_response(GtkDialog *dialog, gint id){
	if (id==GTK_RESPONSE_CANCEL){
		gtk_widget_destroy(GTK_WIDGET(dialog));
	}
}

507 508 509 510 511
static void about_url_clicked(GtkAboutDialog *dialog, const char *url, gpointer data){
	g_message("About url clicked");
	linphone_gtk_open_browser(url);
}

512
void linphone_gtk_show_about(void){
aymeric's avatar
aymeric committed
513
	struct stat filestat;
514
	const char *license_file=PACKAGE_DATA_DIR "/linphone/COPYING";
aymeric's avatar
aymeric committed
515
	GtkWidget *about;
516
	const char *tmp;
517 518
	GdkPixbuf *logo=create_pixbuf(
	    linphone_gtk_get_ui_config("logo","linphone-banner.png"));
519
	static const char *defcfg="defcfg";
520

aymeric's avatar
aymeric committed
521
	about=linphone_gtk_create_window("about");
522
	gtk_about_dialog_set_url_hook(about_url_clicked,NULL,NULL);
aymeric's avatar
aymeric committed
523 524 525 526 527 528 529 530
	memset(&filestat,0,sizeof(filestat));
	if (stat(license_file,&filestat)!=0){
		license_file="COPYING";
		stat(license_file,&filestat);
	}
	if (filestat.st_size>0){
		char *license=g_malloc(filestat.st_size+1);
		FILE *f=fopen(license_file,"r");
531
		if (f && fread(license,1,filestat.st_size,f)>0){
aymeric's avatar
aymeric committed
532 533 534 535 536
			license[filestat.st_size]='\0';
			gtk_about_dialog_set_license(GTK_ABOUT_DIALOG(about),license);
		}
		g_free(license);
	}
537
	gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(about),LIBLINPHONE_GIT_VERSION);
538 539
	gtk_about_dialog_set_program_name(GTK_ABOUT_DIALOG(about),linphone_gtk_get_ui_config("title","Linphone"));
	gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(about),linphone_gtk_get_ui_config("home","http://www.linphone.org"));
540
	if (logo)	gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(about),logo);
541 542 543 544 545 546 547 548 549 550 551 552 553
	tmp=linphone_gtk_get_ui_config("artists",defcfg);
	if (tmp!=defcfg){
		const char *tmp2[2];
		tmp2[0]=tmp;
		tmp2[1]=NULL;
		gtk_about_dialog_set_artists(GTK_ABOUT_DIALOG(about),tmp2);
	}
	tmp=linphone_gtk_get_ui_config("translators",defcfg);
	if (tmp!=defcfg)
		gtk_about_dialog_set_translator_credits (GTK_ABOUT_DIALOG(about),tmp);
	tmp=linphone_gtk_get_ui_config("comments",defcfg);
	if (tmp!=defcfg)
		gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(about),tmp);
aymeric's avatar
aymeric committed
554 555 556
	gtk_widget_show(about);
}

557 558 559 560 561 562 563 564 565 566 567 568 569 570 571

static gboolean linphone_gtk_iterate(LinphoneCore *lc){
	static gboolean first_time=TRUE;
	static gboolean in_iterate=FALSE;

	/*avoid reentrancy*/
	if (in_iterate) return TRUE;
	in_iterate=TRUE;
	linphone_core_iterate(lc);
	if (first_time){
		/*after the first call to iterate, SipSetupContexts should be ready, so take actions:*/
		linphone_gtk_show_directory_search();
		first_time=FALSE;
	}

572
	if (addr_to_call!=NULL){
smorlat's avatar
smorlat committed
573 574 575 576 577 578 579 580 581
		/*make sure we are not showing the login screen*/
		GtkWidget *mw=linphone_gtk_get_main_window();
		GtkWidget *login_frame=linphone_gtk_get_widget(mw,"login_frame");
		if (!GTK_WIDGET_VISIBLE(login_frame)){
			GtkWidget *uri_bar=linphone_gtk_get_widget(mw,"uribar");
			gtk_entry_set_text(GTK_ENTRY(uri_bar),addr_to_call);
			addr_to_call=NULL;
			linphone_gtk_start_call(uri_bar);
		}
582
	}
smorlat's avatar
smorlat committed
583
	in_iterate=FALSE;
aymeric's avatar
aymeric committed
584 585 586
	return TRUE;
}

587 588 589 590 591 592 593 594 595 596 597 598
static gboolean uribar_completion_matchfunc(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter, gpointer user_data){
	char* address = NULL;
	gboolean ret  = FALSE;
	gchar *tmp= NULL;
	gtk_tree_model_get(gtk_entry_completion_get_model(completion),iter,0,&address,-1);

	tmp = g_utf8_casefold(address,-1);
	if (tmp){
		if (strstr(tmp,key))
			ret=TRUE;
		g_free(tmp);
	}
599 600 601 602

	if( address)
		g_free(address);

603 604 605
	return ret;
}

aymeric's avatar
aymeric committed
606 607 608 609 610
static void load_uri_history(){
	GtkEntry *uribar=GTK_ENTRY(linphone_gtk_get_widget(linphone_gtk_get_main_window(),"uribar"));
	char key[20];
	int i;
	GtkEntryCompletion *gep=gtk_entry_completion_new();
611
	GtkListStore *model=gtk_list_store_new(2,G_TYPE_STRING,G_TYPE_INT);
aymeric's avatar
aymeric committed
612 613 614
	for (i=0;;i++){
		const char *uri;
		snprintf(key,sizeof(key),"uri%i",i);
615
		uri=linphone_gtk_get_ui_config(key,NULL);
aymeric's avatar
aymeric committed
616 617 618
		if (uri!=NULL) {
			GtkTreeIter iter;
			gtk_list_store_append(model,&iter);
619
			gtk_list_store_set(model,&iter,0,uri,1,COMPLETION_HISTORY,-1);
aymeric's avatar
aymeric committed
620 621 622 623 624 625
			if (i==0) gtk_entry_set_text(uribar,uri);
		}
		else break;
	}
	gtk_entry_completion_set_model(gep,GTK_TREE_MODEL(model));
	gtk_entry_completion_set_text_column(gep,0);
626
	gtk_entry_completion_set_popup_completion(gep, TRUE);
627
	gtk_entry_completion_set_match_func(gep,uribar_completion_matchfunc, NULL, NULL);
628
	gtk_entry_completion_set_minimum_key_length(gep,3);
aymeric's avatar
aymeric committed
629
	gtk_entry_set_completion(uribar,gep);
630
	g_signal_connect (G_OBJECT (uribar), "changed", G_CALLBACK(linphone_gtk_on_uribar_changed), NULL);
aymeric's avatar
aymeric committed
631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659
}

static void save_uri_history(){
	LinphoneCore *lc=linphone_gtk_get_core();
	LpConfig *cfg=linphone_core_get_config(lc);
	GtkEntry *uribar=GTK_ENTRY(linphone_gtk_get_widget(linphone_gtk_get_main_window(),"uribar"));
	char key[20];
	int i=0;
	char *uri=NULL;
	GtkTreeIter iter;
	GtkTreeModel *model=gtk_entry_completion_get_model(gtk_entry_get_completion(uribar));

	if (!gtk_tree_model_get_iter_first(model,&iter)) return;
	do {
		gtk_tree_model_get(model,&iter,0,&uri,-1);
		if (uri) {
			snprintf(key,sizeof(key),"uri%i",i);
			lp_config_set_string(cfg,"GtkUi",key,uri);
			g_free(uri);
		}else break;
		i++;
		if (i>5) break;
	}while(gtk_tree_model_iter_next(model,&iter));
	lp_config_sync(cfg);
}

static void completion_add_text(GtkEntry *entry, const char *text){
	GtkTreeIter iter;
	GtkTreeModel *model=gtk_entry_completion_get_model(gtk_entry_get_completion(entry));
660 661

	if (gtk_tree_model_get_iter_first(model,&iter)){
aymeric's avatar
aymeric committed
662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677
		do {
			gchar *uri=NULL;
			gtk_tree_model_get(model,&iter,0,&uri,-1);
			if (uri!=NULL){
				if (strcmp(uri,text)==0) {
					/*remove text */
					gtk_list_store_remove(GTK_LIST_STORE(model),&iter);
					g_free(uri);
					break;
				}
				g_free(uri);
			}
		}while (gtk_tree_model_iter_next(model,&iter));
	}
	/* and prepend it on top of the list */
	gtk_list_store_prepend(GTK_LIST_STORE(model),&iter);
678
	gtk_list_store_set(GTK_LIST_STORE(model),&iter,0,text,1,COMPLETION_HISTORY,-1);
aymeric's avatar
aymeric committed
679 680 681
	save_uri_history();
}

682 683 684 685 686 687 688
void on_contact_provider_search_results( LinphoneContactSearch* req, MSList* friends, void* data )
{
	GtkTreeIter    iter;
	GtkEntry*    uribar = GTK_ENTRY(data);
	GtkEntryCompletion* compl = gtk_entry_get_completion(uribar);
	GtkTreeModel* model = gtk_entry_completion_get_model(compl);
	GtkListStore*  list = GTK_LIST_STORE(model);
689
	LinphoneLDAPContactSearch* search = linphone_ldap_contact_search_cast(req);
690 691
	gboolean valid;

692
	// clear completion list from previous non-history entries
693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729
	valid = gtk_tree_model_get_iter_first(model,&iter);
	while(valid)
	{
		char* url;
		int type;
		gtk_tree_model_get(model,&iter, 0,&url, 1,&type, -1);

		if (type != COMPLETION_HISTORY) {
			valid = gtk_list_store_remove(list, &iter);
		} else {
			valid = gtk_tree_model_iter_next(model,&iter);
		}

		if( url ) g_free(url);
		if( !valid ) break;
	}

	// add new non-history related matches
	while( friends ){
		LinphoneFriend* lf = friends->data;
		if( lf ) {
			const LinphoneAddress* la = linphone_friend_get_address(lf);
			if( la ){
				char *addr = linphone_address_as_string(la);

				if( addr ){
					ms_message("[LDAP]Insert match: %s",  addr);
					gtk_list_store_insert_with_values(list, &iter, -1,
													  0, addr,
													  1, COMPLETION_LDAP, -1);
					ms_free(addr);
				}
			}
		}
		friends = friends->next;
	}
	gtk_entry_completion_complete(compl);
730 731 732
	// save the number of LDAP results to better decide if new results should be fetched when search predicate gets bigger
	gtk_object_set_data(GTK_OBJECT(uribar), "ldap_res_cout",
						GINT_TO_POINTER(
733
							linphone_ldap_contact_search_result_count(search)
734 735
							)
						);
736 737 738 739 740 741 742 743 744 745 746 747 748 749

	// Gtk bug? we need to emit a "changed" signal so that the completion appears if
	// the list of results was previously empty
	g_signal_handlers_block_by_func(uribar, linphone_gtk_on_uribar_changed, NULL);
	g_signal_emit_by_name(uribar, "changed");
	g_signal_handlers_unblock_by_func(uribar, linphone_gtk_on_uribar_changed, NULL);
}

struct CompletionTimeout {
	guint timeout_id;
};

static gboolean launch_contact_provider_search(void *userdata)
{
750
	LinphoneLDAPContactProvider* ldap = linphone_gtk_get_ldap();
751
	GtkWidget*      uribar = GTK_WIDGET(userdata);
752 753
	const gchar* predicate = gtk_entry_get_text(GTK_ENTRY(uribar));
	gchar* previous_search = gtk_object_get_data(GTK_OBJECT(uribar), "previous_search");
754
	unsigned int prev_res_count = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(uribar), "ldap_res_cout"));
755

756
	if( ldap && strlen(predicate) >= 3 ){ // don't search too small predicates
757
		unsigned int max_res_count = linphone_ldap_contact_provider_get_max_result(ldap);
758
		LinphoneContactSearch* search;
759 760 761 762 763 764
		if( previous_search  &&
			(strstr(predicate, previous_search) == predicate) && // last search contained results from this one
			(prev_res_count != max_res_count) ){ // and we didn't reach the max result limit

			ms_message("Don't launch search on already searched data (current: %s, old search: %s), (%d/%d results)",
					   predicate, previous_search, prev_res_count, max_res_count);
765 766 767 768 769 770
			return FALSE;
		}

		// save current search
		if( previous_search ) ms_free(previous_search);
		gtk_object_set_data(GTK_OBJECT(uribar), "previous_search", ms_strdup(predicate));
771

772
		ms_message("launch_contact_provider_search");
773
		search =linphone_contact_provider_begin_search(
774 775 776
					linphone_contact_provider_cast(ldap_provider),
					predicate, on_contact_provider_search_results, uribar
					);
777 778

		if(search)
779
			linphone_contact_search_ref(search);
780 781 782 783 784 785
	}
	return FALSE;
}

void linphone_gtk_on_uribar_changed(GtkEditable *uribar, gpointer user_data)
{
786 787 788 789
	if( linphone_gtk_get_ldap() ) {
		gchar* text = gtk_editable_get_chars(uribar, 0,-1);
		gint timeout = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(uribar), "complete_timeout"));
		if( text ) g_free(text);
790

791 792 793
		if( timeout != 0 ) {
			g_source_remove(timeout);
		}
794

795
		timeout = g_timeout_add_seconds(1,(GSourceFunc)launch_contact_provider_search, uribar);
796

797 798
		gtk_object_set_data(GTK_OBJECT(uribar),"complete_timeout", GINT_TO_POINTER(timeout) );
	}
799 800
}

801 802 803 804 805
bool_t linphone_gtk_video_enabled(void){
	const LinphoneVideoPolicy *vpol=linphone_core_get_video_policy(linphone_gtk_get_core());
	return vpol->automatically_accept && vpol->automatically_initiate;
}

806
void linphone_gtk_show_main_window(){
807 808 809 810 811
	GtkWidget *w=linphone_gtk_get_main_window();
	gtk_widget_show(w);
	gtk_window_present(GTK_WINDOW(w));
}

812
void linphone_gtk_call_terminated(LinphoneCall *call, const char *error){
smorlat's avatar
smorlat committed
813
	GtkWidget *mw=linphone_gtk_get_main_window();
814 815 816
	if (linphone_core_get_calls(linphone_gtk_get_core())==NULL){
	    gtk_widget_set_sensitive(linphone_gtk_get_widget(mw,"start_call"),TRUE);
	}
817 818
	if (linphone_gtk_use_in_call_view() && call)
		linphone_gtk_in_call_view_terminate(call,error);
smorlat's avatar
smorlat committed
819 820
}

821 822 823 824 825 826
static void linphone_gtk_update_call_buttons(LinphoneCall *call){
	LinphoneCore *lc=linphone_gtk_get_core();
	GtkWidget *mw=linphone_gtk_get_main_window();
	const MSList *calls=linphone_core_get_calls(lc);
	GtkWidget *button;
	bool_t start_active=TRUE;
827
	//bool_t stop_active=FALSE;
828
	bool_t add_call=FALSE;
829
	int call_list_size=ms_list_size(calls);
830
	GtkWidget *conf_frame;
831

832 833
	if (calls==NULL){
		start_active=TRUE;
834
		//stop_active=FALSE;
Simon Morlat's avatar
Simon Morlat committed
835
	}else{
836
		//stop_active=TRUE;
837 838
		start_active=TRUE;
		add_call=TRUE;
839 840 841 842
	}
	button=linphone_gtk_get_widget(mw,"start_call");
	gtk_widget_set_sensitive(button,start_active);
	gtk_widget_set_visible(button,!add_call);
843

844
	button=linphone_gtk_get_widget(mw,"add_call");
845 846 847 848 849
	if (linphone_core_sound_resources_locked(lc) || (call && linphone_call_get_state(call)==LinphoneCallIncomingReceived)) {
		gtk_widget_set_sensitive(button,FALSE);
	} else {
		gtk_widget_set_sensitive(button,start_active);
	}
850
	gtk_widget_set_visible(button,add_call);
851

852
	//gtk_widget_set_sensitive(linphone_gtk_get_widget(mw,"terminate_call"),stop_active);
853
	conf_frame=(GtkWidget *)g_object_get_data(G_OBJECT(mw),"conf_frame");
854 855 856 857 858 859 860
	if(conf_frame==NULL){
		linphone_gtk_enable_transfer_button(lc,call_list_size>1);
		linphone_gtk_enable_conference_button(lc,call_list_size>1);
	} else {
		linphone_gtk_enable_transfer_button(lc,FALSE);
		linphone_gtk_enable_conference_button(lc,FALSE);
	}
Margaux Clerc's avatar
Margaux Clerc committed
861 862 863
	if (call) {
		linphone_gtk_update_video_button(call);
	}
smorlat's avatar
smorlat committed
864 865
}

866
gchar *linphone_gtk_get_record_path(const LinphoneAddress *address, gboolean is_conference){
Simon Morlat's avatar
Simon Morlat committed
867
	const char *dir=g_get_user_special_dir(G_USER_DIRECTORY_MUSIC);
868
	const char *id="unknown";
Simon Morlat's avatar
Simon Morlat committed
869
	char filename[256]={0};
870 871 872
	char date[64]={0};
	time_t curtime=time(NULL);
	struct tm loctime;
Simon Morlat's avatar
Simon Morlat committed
873 874 875
	const char **fmts=linphone_core_get_supported_file_formats(linphone_gtk_get_core());
	int i;
	const char *ext="wav";
876

877 878 879 880 881 882
#ifdef WIN32
	loctime=*localtime(&curtime);
#else
	localtime_r(&curtime,&loctime);
#endif
	snprintf(date,sizeof(date)-1,"%i%02i%02i-%02i%02i",loctime.tm_year+1900,loctime.tm_mon+1,loctime.tm_mday, loctime.tm_hour, loctime.tm_min);
883

Simon Morlat's avatar
Simon Morlat committed
884 885 886 887 888 889
	for (i=0;fmts[i]!=NULL;++i){
		if (strcmp(fmts[i],"mkv")==0){
			ext="mkv";
			break;
		}
	}
890

891 892 893 894 895
	if (address){
		id=linphone_address_get_username(address);
		if (id==NULL) id=linphone_address_get_domain(address);
	}
	if (is_conference){
Simon Morlat's avatar
Simon Morlat committed
896
		snprintf(filename,sizeof(filename)-1,"%s-conference-%s.%s",
897
			linphone_gtk_get_ui_config("title","Linphone"),
Simon Morlat's avatar
Simon Morlat committed
898
			date,ext);
899
	}else{
Simon Morlat's avatar
Simon Morlat committed
900
		snprintf(filename,sizeof(filename)-1,"%s-call-%s-%s.%s",
901 902
			linphone_gtk_get_ui_config("title","Linphone"),
			date,
Simon Morlat's avatar
Simon Morlat committed
903
			id,ext);
904
	}
905 906 907
	if (!dir) {
		ms_message ("No directory for music, using [%s] instead",dir=getenv("HOME"));
	}
Simon Morlat's avatar
Simon Morlat committed
908 909 910
	return g_build_filename(dir,filename,NULL);
}

smorlat's avatar
smorlat committed
911 912
static gboolean linphone_gtk_start_call_do(GtkWidget *uri_bar){
	const char *entered=gtk_entry_get_text(GTK_ENTRY(uri_bar));
Simon Morlat's avatar
Simon Morlat committed
913 914
	LinphoneCore *lc=linphone_gtk_get_core();
	LinphoneAddress *addr=linphone_core_interpret_url(lc,entered);
915

Simon Morlat's avatar
Simon Morlat committed
916 917
	if (addr!=NULL){
		LinphoneCallParams *params=linphone_core_create_default_call_parameters(lc);
918
		gchar *record_file=linphone_gtk_get_record_path(addr,FALSE);
Simon Morlat's avatar
Simon Morlat committed
919 920
		linphone_call_params_set_record_file(params,record_file);
		linphone_core_invite_address_with_params(lc,addr,params);
smorlat's avatar
smorlat committed
921
		completion_add_text(GTK_ENTRY(uri_bar),entered);
Simon Morlat's avatar
Simon Morlat committed
922 923 924
		linphone_address_destroy(addr);
		linphone_call_params_destroy(params);
		g_free(record_file);
smorlat's avatar
smorlat committed
925
	}else{
926
		linphone_gtk_call_terminated(NULL,NULL);
smorlat's avatar
smorlat committed
927 928
	}
	return FALSE;
aymeric's avatar
aymeric committed
929 930
}

931 932 933 934 935 936 937 938 939 940

static void accept_incoming_call(LinphoneCall *call){
	LinphoneCore *lc=linphone_gtk_get_core();
	LinphoneCallParams *params=linphone_core_create_default_call_parameters(lc);
	gchar *record_file=linphone_gtk_get_record_path(linphone_call_get_remote_address(call),FALSE);
	linphone_call_params_set_record_file(params,record_file);
	linphone_core_accept_call_with_params(lc,call,params);
	linphone_call_params_destroy(params);
}

941
static gboolean linphone_gtk_auto_answer(LinphoneCall *call){
942 943 944
	LinphoneCallState state=linphone_call_get_state(call);
	if (state==LinphoneCallIncomingReceived || state==LinphoneCallIncomingEarlyMedia){
		accept_incoming_call(call);
smorlat's avatar
smorlat committed
945
	}
946
	return FALSE;
smorlat's avatar
smorlat committed
947