main.c 77.1 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
#ifdef ENABLE_NLS
55 56
#include <locale.h>
#endif
57

58
#include "status_icon.h"
59

aymeric's avatar
aymeric committed
60

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

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

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

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

107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
#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

133
static GOptionEntry linphone_options[]={
134
	LINPHONE_OPTION("verbose",             '\0', G_OPTION_ARG_NONE,     (gpointer)&verbose,              N_("log to stdout some debug information while running.")),
135
	LINPHONE_OPTION("version",             '\0', G_OPTION_ARG_NONE,     (gpointer)&version,              N_("display version and exit.")),
136 137 138 139 140 141 142 143
	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("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")),
144
	{0}
aymeric's avatar
aymeric committed
145 146 147
};

#define INSTALLED_XML_DIR PACKAGE_DATA_DIR "/linphone"
148
#define RELATIVE_XML_DIR
149
#define BUILD_TREE_XML_DIR "gtk"
smorlat's avatar
smorlat committed
150 151

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

Simon Morlat's avatar
Simon Morlat committed
161 162 163 164
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;
165
	if (g_path_is_absolute(filename)) {
166 167
		snprintf(config_file,path_max,"%s",filename);
	} else{
aymeric's avatar
aymeric committed
168 169 170
#ifdef WIN32
		const char *appdata=getenv("APPDATA");
		if (appdata){
Simon Morlat's avatar
Simon Morlat committed
171 172 173
			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
174 175
		}
#else
176 177
		const char *home=getenv("HOME");
		if (home==NULL) home=".";
Simon Morlat's avatar
Simon Morlat committed
178
		snprintf(config_file,path_max,"%s/%s",home,filename);
aymeric's avatar
aymeric committed
179 180
#endif
	}
Simon Morlat's avatar
Simon Morlat committed
181
	return config_file;
smorlat's avatar
smorlat committed
182 183
}

184 185
#define FACTORY_CONFIG_FILE "linphonerc.factory"
static char _factory_config_file[1024];
186
static const char *linphone_gtk_get_factory_config_file(void){
187
	char* path = NULL;
188 189
	/*try accessing a local file first if exists*/
	if (access(FACTORY_CONFIG_FILE,F_OK)==0){
190
		path = ms_strdup(FACTORY_CONFIG_FILE);
191 192
	} else {
		char *progdir;
193

194 195 196 197 198 199 200 201
		if (progpath != NULL) {
			char *basename;
			progdir = strdup(progpath);
#ifdef WIN32
			basename = strrchr(progdir, '\\');
			if (basename != NULL) {
				basename ++;
				*basename = '\0';
202 203 204
				path = ms_strdup_printf("%s\\..\\%s", progdir, FACTORY_CONFIG_FILE);
			} else if (workingdir!=NULL) {
				path = ms_strdup_printf("%s\\%s", workingdir, FACTORY_CONFIG_FILE);
205 206 207 208 209 210
			}
#else
			basename = strrchr(progdir, '/');
			if (basename != NULL) {
				basename ++;
				*basename = '\0';
211
				path = ms_strdup_printf("%s/../share/linphone/%s", progdir, FACTORY_CONFIG_FILE);
212 213 214 215 216
			}
#endif
			free(progdir);
		}
	}
217 218 219 220 221 222 223 224 225 226
	if (path) {
		//use factory file only if it exists
		if (access(path,F_OK)==0){
			snprintf(_factory_config_file, sizeof(_factory_config_file), "%s", path);
			ms_free(path);
			return _factory_config_file;
		}
		ms_free(path);
	}
	return NULL;
227 228
}

229
LinphoneLDAPContactProvider* linphone_gtk_get_ldap(void){
230 231 232
	return ldap_provider;
}

233 234 235 236
int linphone_gtk_is_ldap_supported(void){
	return linphone_ldap_contact_provider_available();
}

237 238 239
void linphone_gtk_set_ldap(LinphoneLDAPContactProvider* ldap)
{
	if( ldap_provider )
240
		linphone_contact_provider_unref(ldap_provider);
241

242
	ldap_provider = ldap ? linphone_ldap_contact_provider_ref( ldap )
243
						 : NULL;
244 245
}

246 247 248 249
void linphone_gtk_schedule_restart(void){
	restart=TRUE;
}

Margaux Clerc's avatar
Margaux Clerc committed
250 251 252 253
gboolean linphone_gtk_get_audio_assistant_option(void){
	return run_audio_assistant;
}

254
static void linphone_gtk_init_liblinphone(const char *config_file,
255 256
		const char *factory_config_file, const char *chat_messages_db_file, 
		const char *call_logs_db_file, const char *friends_db_file) {
Simon Morlat's avatar
Simon Morlat committed
257
	LinphoneCoreVTable vtable={0};
Simon Morlat's avatar
Simon Morlat committed
258
	gchar *secrets_file=linphone_gtk_get_config_file(SECRETS_FILE);
johan's avatar
johan committed
259
	gchar *user_certificates_dir=linphone_gtk_get_config_file(CERTIFICATES_PATH);
Simon Morlat's avatar
Simon Morlat committed
260

261
	vtable.global_state_changed=linphone_gtk_global_state_changed;
Simon Morlat's avatar
Simon Morlat committed
262
	vtable.call_state_changed=linphone_gtk_call_state_changed;
Simon Morlat's avatar
Simon Morlat committed
263
	vtable.registration_state_changed=linphone_gtk_registration_state_changed;
264 265
	vtable.notify_presence_received=linphone_gtk_notify_recv;
	vtable.new_subscription_requested=linphone_gtk_new_unknown_subscriber;
Simon Morlat's avatar
Simon Morlat committed
266 267
	vtable.auth_info_requested=linphone_gtk_auth_info_requested;
	vtable.call_log_updated=linphone_gtk_call_log_updated;
268
	vtable.message_received=linphone_gtk_text_received;
269
	vtable.is_composing_received=linphone_gtk_is_composing_received;
Simon Morlat's avatar
Simon Morlat committed
270 271
	vtable.refer_received=linphone_gtk_refer_received;
	vtable.buddy_info_updated=linphone_gtk_buddy_info_updated;
Simon Morlat's avatar
Simon Morlat committed
272
	vtable.call_encryption_changed=linphone_gtk_call_encryption_changed;
273
	vtable.transfer_state_changed=linphone_gtk_transfer_state_changed;
274
	vtable.dtmf_received=linphone_gtk_dtmf_received;
275
	vtable.configuring_status=linphone_gtk_configuring_status;
Simon Morlat's avatar
Simon Morlat committed
276

277
	the_core=linphone_core_new(&vtable,config_file,factory_config_file,NULL);
278
	linphone_core_migrate_to_multi_transport(the_core);
279
	//lp_config_set_int(linphone_core_get_config(the_core), "sip", "store_auth_info", 0);
280 281 282 283 284


	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");
285
		linphone_gtk_set_ldap( linphone_ldap_contact_provider_create(the_core, ldap_cfg) );
286 287
	}

288
	linphone_core_set_user_agent(the_core,"Linphone", LINPHONE_VERSION);
289
	linphone_core_set_waiting_callback(the_core,linphone_gtk_wait,NULL);
Simon Morlat's avatar
Simon Morlat committed
290 291
	linphone_core_set_zrtp_secrets_file(the_core,secrets_file);
	g_free(secrets_file);
johan's avatar
johan committed
292 293
	linphone_core_set_user_certificates_path(the_core,user_certificates_dir);
	g_free(user_certificates_dir);
294 295
	linphone_core_enable_video_capture(the_core, TRUE);
	linphone_core_enable_video_display(the_core, TRUE);
296
	linphone_core_set_native_video_window_id(the_core,LINPHONE_VIDEO_DISPLAY_NONE);/*don't create the window*/
297 298 299 300
	if (no_video) {
		_linphone_gtk_enable_video(FALSE);
		linphone_gtk_set_ui_config_int("videoselfview",0);
	}
301 302
	if (chat_messages_db_file) linphone_core_set_chat_database_path(the_core,chat_messages_db_file);
	if (call_logs_db_file) linphone_core_set_call_logs_database_path(the_core, call_logs_db_file);
303
	if (friends_db_file) linphone_core_set_friends_database_path(the_core, friends_db_file);
aymeric's avatar
aymeric committed
304 305 306 307 308 309
}

LinphoneCore *linphone_gtk_get_core(void){
	return the_core;
}

310
GtkWidget *linphone_gtk_get_main_window(void){
aymeric's avatar
aymeric committed
311 312 313
	return the_ui;
}

314
void linphone_gtk_destroy_main_window(void) {
315
	linphone_gtk_destroy_window(the_ui);
316 317 318
	the_ui = NULL;
}

smorlat's avatar
smorlat committed
319
static void linphone_gtk_configure_window(GtkWidget *w, const char *window_name){
smorlat's avatar
smorlat committed
320 321
	static const char *hiddens=NULL;
	static const char *shown=NULL;
322
	static bool_t config_loaded=FALSE;
smorlat's avatar
smorlat committed
323
	if (linphone_gtk_get_core()==NULL) return;
324 325
	if (config_loaded==FALSE){
		hiddens=linphone_gtk_get_ui_config("hidden_widgets",NULL);
smorlat's avatar
smorlat committed
326
		shown=linphone_gtk_get_ui_config("shown_widgets",NULL);
327 328
		config_loaded=TRUE;
	}
329 330
	if (hiddens) linphone_gtk_visibility_set(hiddens,window_name,w,FALSE);
	if (shown) linphone_gtk_visibility_set(shown,window_name,w,TRUE);
smorlat's avatar
smorlat committed
331 332
}

333 334
static int get_ui_file(const char *name, char *path, int pathsize){
	snprintf(path,pathsize,"%s/%s.ui",BUILD_TREE_XML_DIR,name);
335
	if (access(path,F_OK)!=0){
336
		snprintf(path,pathsize,"%s/%s.ui",INSTALLED_XML_DIR,name);
337
		if (access(path,F_OK)!=0){
Simon Morlat's avatar
Simon Morlat committed
338
			g_error("Could not locate neither %s/%s.ui nor %s/%s.ui",BUILD_TREE_XML_DIR,name,
339 340
				INSTALLED_XML_DIR,name);
			return -1;
341 342
		}
	}
343 344 345
	return 0;
}

346 347 348 349 350 351
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));
}

352
GtkWidget *linphone_gtk_create_widget(const char *widget_name) {
353 354 355 356
	char path[2048];
	GtkBuilder *builder = gtk_builder_new();
	GError *error = NULL;
	GObject *obj;
357

358
	if(get_ui_file(widget_name, path, sizeof(path)) == -1) goto fail;
359

360
	gtk_builder_set_translation_domain(builder, GETTEXT_PACKAGE);
361

362 363 364
	if(gtk_builder_add_from_file(builder, path, &error) == 0) {
		g_error("Couldn't load builder file: %s", error->message);
		g_error_free(error);
365
		goto fail;
366
	}
367

368 369 370
	obj = gtk_builder_get_object(builder, widget_name);
	if(obj == NULL) {
		g_error("'%s' widget not found", widget_name);
371
		goto fail;
372
	}
373 374
	g_object_set_data(G_OBJECT(obj), "builder", builder);
	g_signal_connect_data(G_OBJECT(obj),"destroy",(GCallback)g_object_unref,builder, NULL, G_CONNECT_AFTER|G_CONNECT_SWAPPED);
375
	gtk_builder_connect_signals(builder, obj);
376

377
	return GTK_WIDGET(obj);
378

379 380 381 382 383 384 385 386 387 388 389 390 391 392
fail:
	g_object_unref(builder);
	return NULL;
}

GtkWidget *linphone_gtk_create_window(const char *window_name, GtkWidget *parent){
	GtkWidget *w = linphone_gtk_create_widget(window_name);
	if(w) {
		linphone_gtk_configure_window(w,window_name);
		if(parent) {
			gtk_window_set_transient_for(GTK_WINDOW(w), GTK_WINDOW(parent));
			gtk_window_set_position(GTK_WINDOW(w), GTK_WIN_POS_CENTER_ON_PARENT);
		}
	}
393 394 395
	return w;
}

396 397 398
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);
399 400
}

aymeric's avatar
aymeric committed
401
GtkWidget *linphone_gtk_get_widget(GtkWidget *window, const char *name){
402
	GtkBuilder *builder;
403
	GObject *w;
404 405
	if (window==NULL) return NULL;
	builder=(GtkBuilder*)g_object_get_data(G_OBJECT(window),"builder");
406 407 408 409 410
	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
411 412 413
	if (w==NULL){
		g_error("No widget named %s found in xml interface.",name);
	}
414
	if (workaround_gtk_entry_chinese_bug){
415
		if (strcmp(G_OBJECT_TYPE_NAME(w),"GtkEntry")==0 || strcmp(G_OBJECT_TYPE_NAME(w),"GtkTextView")==0){
416 417
			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));
418
				ms_message("%s is a %s",name,G_OBJECT_TYPE_NAME(w));
419 420 421 422
				g_signal_connect(G_OBJECT(w),"unmap",(GCallback)entry_unmapped,NULL);
			}
		}
	}
aymeric's avatar
aymeric committed
423 424 425 426 427 428 429
	return GTK_WIDGET(w);
}


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

aymeric's avatar
aymeric committed
431 432 433 434 435 436
	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),
437
								GTK_DIALOG_DESTROY_WITH_PARENT,
aymeric's avatar
aymeric committed
438
				GTK_MESSAGE_QUESTION,
439 440
								GTK_BUTTONS_YES_NO,
								"%s",
aymeric's avatar
aymeric committed
441 442 443 444
				(const gchar*)message);
		/* connect to some callback : REVISIT */
		/*
		g_signal_connect_swapped (G_OBJECT (dialog), "response",
445 446
						   G_CALLBACK (dialog_click),
						   G_OBJECT (dialog));
aymeric's avatar
aymeric committed
447 448 449 450 451 452 453
		*/
		/* actually show the box */
		gtk_widget_show(dialog);
	}
	else
	{
		dialog = gtk_message_dialog_new (GTK_WINDOW(main_window),
454 455 456 457 458
								  GTK_DIALOG_DESTROY_WITH_PARENT,
								  type,
								  GTK_BUTTONS_CLOSE,
								  "%s",
								  (const gchar*)message);
aymeric's avatar
aymeric committed
459 460
		/* Destroy the dialog when the user responds to it (e.g. clicks a button) */
		g_signal_connect_swapped (G_OBJECT (dialog), "response",
461 462
						   G_CALLBACK (gtk_widget_destroy),
						   G_OBJECT (dialog));
aymeric's avatar
aymeric committed
463 464 465 466 467 468 469 470 471 472
		gtk_widget_show(dialog);
	}
}

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

473 474 475 476 477
static void about_url_clicked(GtkAboutDialog *dialog, const char *url, gpointer data){
	g_message("About url clicked");
	linphone_gtk_open_browser(url);
}

478
void linphone_gtk_show_about(void){
aymeric's avatar
aymeric committed
479
	struct stat filestat;
480
	const char *license_file=PACKAGE_DATA_DIR "/linphone/COPYING";
aymeric's avatar
aymeric committed
481
	GtkWidget *about;
482
	const char *tmp;
483
	GdkPixbuf *logo=create_pixbuf(
484
		linphone_gtk_get_ui_config("logo","linphone-banner.png"));
485
	static const char *defcfg="defcfg";
486

487
	about=linphone_gtk_create_window("about", the_ui);
488

489
	gtk_about_dialog_set_url_hook(about_url_clicked,NULL,NULL);
490

aymeric's avatar
aymeric committed
491 492 493 494 495 496 497 498
	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");
499
		if (f && fread(license,1,filestat.st_size,f)>0){
aymeric's avatar
aymeric committed
500 501 502 503 504
			license[filestat.st_size]='\0';
			gtk_about_dialog_set_license(GTK_ABOUT_DIALOG(about),license);
		}
		g_free(license);
	}
505
	gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(about),LIBLINPHONE_GIT_VERSION);
506 507
	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"));
508 509 510 511
	if (logo) {
		gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(about), logo);
		g_object_unref(logo);
	}
512 513 514 515 516 517 518 519 520 521 522 523 524
	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
525 526 527
	gtk_widget_show(about);
}

528 529 530 531 532 533 534 535 536 537 538 539 540 541 542

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;
	}

543
	if (addr_to_call!=NULL){
smorlat's avatar
smorlat committed
544 545
		/*make sure we are not showing the login screen*/
		GtkWidget *mw=linphone_gtk_get_main_window();
546
		if (g_object_get_data(G_OBJECT(mw), "login_frame") == NULL){
smorlat's avatar
smorlat committed
547 548 549 550 551
			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);
		}
552
	}
smorlat's avatar
smorlat committed
553
	in_iterate=FALSE;
aymeric's avatar
aymeric committed
554 555 556
	return TRUE;
}

557 558 559 560 561
static gboolean uribar_completion_matchfunc(GtkEntryCompletion *completion, const gchar *key, GtkTreeIter *iter, gpointer user_data){
	char* address = NULL;
	gboolean ret  = FALSE;
	gtk_tree_model_get(gtk_entry_completion_get_model(completion),iter,0,&address,-1);

François Grisez's avatar
François Grisez committed
562 563 564
	if(address) {
		gchar *tmp = g_utf8_casefold(address,-1);
		if (strstr(tmp,key)) ret=TRUE;
565
		g_free(tmp);
566
		g_free(address);
François Grisez's avatar
François Grisez committed
567
	}
568

569 570 571
	return ret;
}

572
static void load_uri_history(void){
aymeric's avatar
aymeric committed
573 574 575 576
	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();
577
	GtkListStore *model=gtk_list_store_new(2,G_TYPE_STRING,G_TYPE_INT);
aymeric's avatar
aymeric committed
578 579 580
	for (i=0;;i++){
		const char *uri;
		snprintf(key,sizeof(key),"uri%i",i);
581
		uri=linphone_gtk_get_ui_config(key,NULL);
aymeric's avatar
aymeric committed
582 583 584
		if (uri!=NULL) {
			GtkTreeIter iter;
			gtk_list_store_append(model,&iter);
585
			gtk_list_store_set(model,&iter,0,uri,1,COMPLETION_HISTORY,-1);
aymeric's avatar
aymeric committed
586 587 588 589 590 591
			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);
592
	gtk_entry_completion_set_popup_completion(gep, TRUE);
593
	gtk_entry_completion_set_match_func(gep,uribar_completion_matchfunc, NULL, NULL);
594
	gtk_entry_completion_set_minimum_key_length(gep,3);
aymeric's avatar
aymeric committed
595
	gtk_entry_set_completion(uribar,gep);
596
	g_signal_connect (G_OBJECT (uribar), "changed", G_CALLBACK(linphone_gtk_on_uribar_changed), NULL);
aymeric's avatar
aymeric committed
597 598
}

599
static void save_uri_history(void){
aymeric's avatar
aymeric committed
600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625
	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));
626 627

	if (gtk_tree_model_get_iter_first(model,&iter)){
aymeric's avatar
aymeric committed
628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643
		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);
644
	gtk_list_store_set(GTK_LIST_STORE(model),&iter,0,text,1,COMPLETION_HISTORY,-1);
aymeric's avatar
aymeric committed
645 646 647
	save_uri_history();
}

648 649 650 651 652 653 654
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);
655
	LinphoneLDAPContactSearch* search = linphone_ldap_contact_search_cast(req);
656 657
	gboolean valid;

658
	// clear completion list from previous non-history entries
659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695
	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);
696 697 698
	// 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(
699
							linphone_ldap_contact_search_result_count(search)
700 701
							)
						);
702 703 704 705 706 707 708 709 710 711 712 713 714 715

	// 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)
{
716
	LinphoneLDAPContactProvider* ldap = linphone_gtk_get_ldap();
717
	GtkWidget*      uribar = GTK_WIDGET(userdata);
718 719
	const gchar* predicate = gtk_entry_get_text(GTK_ENTRY(uribar));
	gchar* previous_search = gtk_object_get_data(GTK_OBJECT(uribar), "previous_search");
720
	unsigned int prev_res_count = GPOINTER_TO_INT(gtk_object_get_data(GTK_OBJECT(uribar), "ldap_res_cout"));
721

722
	if( ldap && strlen(predicate) >= 3 ){ // don't search too small predicates
723
		unsigned int max_res_count = linphone_ldap_contact_provider_get_max_result(ldap);
724
		LinphoneContactSearch* search;
725 726 727 728 729 730
		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);
731 732 733 734 735 736
			return FALSE;
		}

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

738
		ms_message("launch_contact_provider_search");
739
		search =linphone_contact_provider_begin_search(
740 741 742
					linphone_contact_provider_cast(ldap_provider),
					predicate, on_contact_provider_search_results, uribar
					);
743 744

		if(search)
745
			linphone_contact_search_ref(search);
746 747 748 749 750 751
	}
	return FALSE;
}

void linphone_gtk_on_uribar_changed(GtkEditable *uribar, gpointer user_data)
{
752 753 754 755
	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);
756

757 758 759
		if( timeout != 0 ) {
			g_source_remove(timeout);
		}
760

761
		timeout = g_timeout_add_seconds(1,(GSourceFunc)launch_contact_provider_search, uribar);
762

763 764
		gtk_object_set_data(GTK_OBJECT(uribar),"complete_timeout", GINT_TO_POINTER(timeout) );
	}
765 766
}

767 768 769 770 771
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;
}

772
void linphone_gtk_show_main_window(){
773
	GtkWidget *w=linphone_gtk_get_main_window();
774 775 776 777 778 779
#ifdef HAVE_GTK_OSX
	GtkWidget *icon = linphone_gtk_get_widget(w, "history_tab_icon");
	GtkWidget *label = linphone_gtk_get_widget(w, "history_tab_label");
	gtk_misc_set_alignment(GTK_MISC(icon), 0.5f, 0.25f);
	gtk_misc_set_alignment(GTK_MISC(label), 0.5f, 0.f);
#endif
780 781 782 783
	gtk_widget_show(w);
	gtk_window_present(GTK_WINDOW(w));
}

784
void linphone_gtk_call_terminated(LinphoneCall *call, const char *error){
smorlat's avatar
smorlat committed
785
	GtkWidget *mw=linphone_gtk_get_main_window();
786
	if (linphone_core_get_calls(linphone_gtk_get_core())==NULL){
787
		gtk_widget_set_sensitive(linphone_gtk_get_widget(mw,"start_call"),TRUE);
788
	}
789 790
	if (linphone_gtk_use_in_call_view() && call)
		linphone_gtk_in_call_view_terminate(call,error);
smorlat's avatar
smorlat committed
791 792
}

793 794 795 796 797
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;
798
	bool_t add_call=(calls!=NULL);
799
	int call_list_size=ms_list_size(calls);
800
	GtkWidget *conf_frame;
801

802
	button=linphone_gtk_get_widget(mw,"start_call");
803
	gtk_widget_set_sensitive(button,TRUE);
804
	gtk_widget_set_visible(button,!add_call);
805

806
	button=linphone_gtk_get_widget(mw,"add_call");
807

808 809 810
	if (linphone_core_sound_resources_locked(lc) || (call && linphone_call_get_state(call)==LinphoneCallIncomingReceived)) {
		gtk_widget_set_sensitive(button,FALSE);
	} else {
811
		gtk_widget_set_sensitive(button,TRUE);
812
	}
813
	gtk_widget_set_visible(button,add_call);
814

815
	//gtk_widget_set_sensitive(linphone_gtk_get_widget(mw,"terminate_call"),stop_active);
816
	conf_frame=(GtkWidget *)g_object_get_data(G_OBJECT(mw),"conf_frame");
817 818 819 820 821 822 823
	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
824 825 826
	if (call) {
		linphone_gtk_update_video_button(call);
	}
smorlat's avatar
smorlat committed
827 828
}

829
gchar *linphone_gtk_get_record_path(const LinphoneAddress *address, gboolean is_conference){
Simon Morlat's avatar
Simon Morlat committed
830
	const char *dir=g_get_user_special_dir(G_USER_DIRECTORY_MUSIC);
831
	const char *id="unknown";
Simon Morlat's avatar
Simon Morlat committed
832
	char filename[256]={0};
833 834 835
	char date[64]={0};
	time_t curtime=time(NULL);
	struct tm loctime;
Simon Morlat's avatar
Simon Morlat committed
836 837 838
	const char **fmts=linphone_core_get_supported_file_formats(linphone_gtk_get_core());
	int i;
	const char *ext="wav";
839

840 841 842 843 844 845
#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);
846

Simon Morlat's avatar
Simon Morlat committed
847 848 849 850 851 852
	for (i=0;fmts[i]!=NULL;++i){
		if (strcmp(fmts[i],"mkv")==0){
			ext="mkv";
			break;
		}
	}
853

854 855 856 857 858
	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
859
		snprintf(filename,sizeof(filename)-1,"%s-conference-%s.%s",
860
			linphone_gtk_get_ui_config("title","Linphone"),
Simon Morlat's avatar
Simon Morlat committed
861
			date,ext);
862
	}else{
Simon Morlat's avatar
Simon Morlat committed
863
		snprintf(filename,sizeof(filename)-1,"%s-call-%s-%s.%s",
864 865
			linphone_gtk_get_ui_config("title","Linphone"),
			date,
Simon Morlat's avatar
Simon Morlat committed
866
			id,ext);
867
	}
868 869 870
	if (!dir) {
		ms_message ("No directory for music, using [%s] instead",dir=getenv("HOME"));
	}
Simon Morlat's avatar
Simon Morlat committed
871 872 873
	return g_build_filename(dir,filename,NULL);
}

smorlat's avatar
smorlat committed
874 875
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
876 877
	LinphoneCore *lc=linphone_gtk_get_core();
	LinphoneAddress *addr=linphone_core_interpret_url(lc,entered);
878