Blob Blame History Raw
From 68fd6b29d34380f74ea69b32cf055639ae2b7a35 Mon Sep 17 00:00:00 2001
From: mbattista <m0battista@gmail.com>
Date: Sat, 24 Apr 2021 09:18:38 +0200
Subject: [PATCH] GTK caller history (#1350)

* caller history

* other icons

* rejected and []

* limit caller history

* fix ccheck

* take better icons if available

* symbolic icons suffix

* check against theme if icons exists

* symbolic for status icon

* dialog-close is not in all icons

* fixing crash when calling same person multiple times

* check against null safty

Co-authored-by: Marcel Battista <marcel.battista@etes.de>
---
 modules/gtk/gtk_mod.c | 135 ++++++++++++++++++++++++++++++++++++++++++
 modules/gtk/gtk_mod.h |   5 ++
 2 files changed, 140 insertions(+)

diff --git a/modules/gtk/gtk_mod.c b/modules/gtk/gtk_mod.c
index afbbc3806..55a806620 100644
--- a/modules/gtk/gtk_mod.c
+++ b/modules/gtk/gtk_mod.c
@@ -6,6 +6,7 @@
  */
 #include <re.h>
 #include <rem.h>
+#include <time.h>
 #include <baresip.h>
 #include <stdlib.h>
 #include <pthread.h>
@@ -43,11 +44,13 @@ struct gtk_mod {
 	bool run;
 	bool contacts_inited;
 	struct mqueue *mq;
+	int call_history_length;
 	GApplication *app;
 	GtkStatusIcon *status_icon;
 	GtkWidget *app_menu;
 	GtkWidget *contacts_menu;
 	GtkWidget *accounts_menu;
+	GtkWidget *history_menu;
 	GtkWidget *status_menu;
 	GSList *accounts_menu_group;
 	struct dial_dialog *dial_dialog;
@@ -55,6 +58,9 @@ struct gtk_mod {
 	GSList *incoming_call_menus;
 	bool clean_number;
 	struct ua *ua_cur;
+	bool icon_call_missed;
+	bool icon_call_outgoing;
+	bool icon_call_incoming;
 };
 
 static struct gtk_mod mod_obj;
@@ -150,6 +156,25 @@ static void menu_on_dial_contact(GtkMenuItem *menuItem, gpointer arg)
 	gtk_mod_connect(mod, uri);
 }
 
+static void menu_on_dial_history(GtkMenuItem *menuItem, gpointer arg)
+{
+	struct gtk_mod *mod = arg;
+	const char *label = gtk_menu_item_get_label(menuItem);
+	char *label_1;
+	char buf[256];
+	char *uri;
+
+	str_ncpy(buf, label, sizeof(buf));
+	label_1 = strchr(buf, '[');
+	if (!label_1)
+		return;
+
+	label_1[0] = ' ';
+
+	uri = strtok(label_1, "]");
+	gtk_mod_connect(mod, uri);
+}
+
 
 static void init_contacts_menu(struct gtk_mod *mod)
 {
@@ -170,6 +195,78 @@ static void init_contacts_menu(struct gtk_mod *mod)
 }
 
 
+static void add_history_menu_item(struct gtk_mod *mod, const char *uri,
+					int call_type, const char *info)
+{
+	GtkWidget *item, *history_item;
+	GtkMenuShell *history_menu = GTK_MENU_SHELL(mod->history_menu);
+	char buf[256];
+	time_t rawtime = time(NULL);
+	struct tm *ptm = localtime(&rawtime);
+	GList *list;
+
+	if (mod->call_history_length < 20) {
+		mod->call_history_length++;
+	}
+	else {
+		/* Remove old call history */
+		list = gtk_container_get_children(GTK_CONTAINER(history_menu));
+		history_item = GTK_WIDGET(list->data);
+		gtk_widget_destroy(history_item);
+
+	}
+
+	re_snprintf(buf, sizeof buf,
+			"%s [%s]\n%04d-%02d-%02d %02d:%02d:%02d",
+			info, uri, ptm->tm_year + 1900, ptm->tm_mon + 1,
+			ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec);
+
+	item = gtk_image_menu_item_new_with_label(buf);
+	switch (call_type) {
+		case CALL_INCOMING:
+			gtk_image_menu_item_set_image(
+				GTK_IMAGE_MENU_ITEM(item),
+				gtk_image_new_from_icon_name(
+                                        (mod->icon_call_incoming) ?
+					"call-incoming-symbolic" : "go-next",
+					GTK_ICON_SIZE_MENU));
+			break;
+		case CALL_OUTGOING:
+			gtk_image_menu_item_set_image(
+				GTK_IMAGE_MENU_ITEM(item),
+				gtk_image_new_from_icon_name(
+					(mod->icon_call_outgoing) ?
+					"call-outgoing-symbolic"
+						 : "go-previous",
+				GTK_ICON_SIZE_MENU));
+			break;
+		case CALL_MISSED:
+			gtk_image_menu_item_set_image(
+				GTK_IMAGE_MENU_ITEM(item),
+				gtk_image_new_from_icon_name(
+					(mod->icon_call_missed) ?
+					"call-missed-symbolic" : "call-stop",
+					GTK_ICON_SIZE_MENU));
+			break;
+		case CALL_REJECTED:
+			gtk_image_menu_item_set_image(
+				GTK_IMAGE_MENU_ITEM(item),
+				gtk_image_new_from_icon_name(
+					"window-close", GTK_ICON_SIZE_MENU));
+			break;
+		default:
+			gtk_image_menu_item_set_image(
+				GTK_IMAGE_MENU_ITEM(item),
+				gtk_image_new_from_icon_name(
+					"call-start", GTK_ICON_SIZE_MENU));
+			break;
+	}
+	gtk_menu_shell_append(history_menu, item);
+	g_signal_connect(G_OBJECT(item), "activate",
+		G_CALLBACK(menu_on_dial_history), mod);
+}
+
+
 static void menu_on_account_toggled(GtkCheckMenuItem *menu_item,
 				    struct gtk_mod *mod)
 {
@@ -207,6 +304,8 @@ static void menu_on_incoming_call_reject(GtkMenuItem *menuItem,
 		struct gtk_mod *mod)
 {
 	struct call *call = g_object_get_data(G_OBJECT(menuItem), "call");
+	add_history_menu_item(mod,call_peeruri(call), CALL_REJECTED,
+						call_peername(call));
 	denotify_incoming_call(mod, call);
 	mqueue_push(mod->mq, MQ_HANGUP, call);
 }
@@ -448,6 +547,8 @@ static void reject_activated(GSimpleAction *action, GVariant *parameter,
 
 	if (call) {
 		denotify_incoming_call(mod, call);
+		add_history_menu_item(mod,call_peeruri(call), CALL_REJECTED,
+					call_peername(call));
 		mqueue_push(mod->mq, MQ_HANGUP, call);
 	}
 }
@@ -530,6 +631,18 @@ static void ua_event_handler(struct ua *ua,
 		if (win)
 			call_window_closed(win, prm);
 		denotify_incoming_call(mod, call);
+		if (!call_is_outgoing(call)
+			&& call_state(call) != CALL_STATE_TERMINATED
+			&& call_state(call) != CALL_STATE_ESTABLISHED) {
+			add_history_menu_item(mod,
+				call_peeruri(call),
+				CALL_MISSED, call_peername(call));
+
+			gtk_status_icon_set_from_icon_name(
+				mod->status_icon,
+				(mod->icon_call_missed) ?
+					"call-missed-symbolic" : "call-stop");
+		}
 		break;
 
 	case UA_EVENT_CALL_RINGING:
@@ -637,6 +750,7 @@ static gboolean status_icon_on_button_press(GtkStatusIcon *status_icon,
 {
 	popup_menu(mod, gtk_status_icon_position_menu, status_icon,
 			event->button, event->time);
+	gtk_status_icon_set_from_icon_name(status_icon, "call-start");
 	return TRUE;
 }
 
@@ -719,6 +833,7 @@ static void mqueue_handler(int id, void *data, void *arg)
 	case MQ_CONNECT:
 		uri = data;
 		err = ua_connect(ua, &call, NULL, uri, VIDMODE_ON);
+		add_history_menu_item(mod, uri, CALL_OUTGOING, "");
 		if (err) {
 			gdk_threads_enter();
 			warning_dialog("Call failed",
@@ -748,6 +863,8 @@ static void mqueue_handler(int id, void *data, void *arg)
 	case MQ_ANSWER:
 		call = data;
 		err = ua_answer(ua, call, VIDMODE_ON);
+		add_history_menu_item(mod, call_peeruri(call),
+				CALL_INCOMING, call_peername(call));
 		if (err) {
 			gdk_threads_enter();
 			warning_dialog("Call failed",
@@ -782,6 +899,8 @@ static void *gtk_thread(void *arg)
 	GtkWidget *item;
 	GError *err = NULL;
 	struct le *le;
+	GtkIconTheme *theme;
+
 
 	gdk_threads_init();
 	gtk_init(0, NULL);
@@ -814,6 +933,7 @@ static void *gtk_thread(void *arg)
 	mod->dial_dialog = NULL;
 	mod->call_windows = NULL;
 	mod->incoming_call_menus = NULL;
+	mod->call_history_length = 0;
 
 	/* App menu */
 	mod->app_menu = gtk_menu_new();
@@ -872,8 +992,23 @@ static void *gtk_thread(void *arg)
 	gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
 			mod->contacts_menu);
 
+	/* Caller history */
+	mod->history_menu = gtk_menu_new();
+	item = gtk_menu_item_new_with_mnemonic("Call _history");
+	gtk_menu_shell_append(app_menu, item);
+	gtk_menu_item_set_submenu(GTK_MENU_ITEM(item),
+			mod->history_menu);
+
 	gtk_menu_shell_append(app_menu, gtk_separator_menu_item_new());
 
+	theme = gtk_icon_theme_get_default();
+	mod->icon_call_incoming = gtk_icon_theme_has_icon(theme,
+						"call-incoming-symbolic");
+	mod->icon_call_outgoing = gtk_icon_theme_has_icon(theme,
+						"call-outgoing-symbolic");
+	mod->icon_call_missed = gtk_icon_theme_has_icon(theme,
+						"call-missed-symbolic");
+
 	/* About */
 	item = gtk_menu_item_new_with_mnemonic("A_bout");
 	g_signal_connect(G_OBJECT(item), "activate",
diff --git a/modules/gtk/gtk_mod.h b/modules/gtk/gtk_mod.h
index 50f6a9a98..2f3bfb4f4 100644
--- a/modules/gtk/gtk_mod.h
+++ b/modules/gtk/gtk_mod.h
@@ -4,6 +4,11 @@
  * Copyright (C) 2015 Charles E. Lehner
  */
 
+#define CALL_INCOMING  0
+#define CALL_OUTGOING  1
+#define CALL_MISSED    2
+#define CALL_REJECTED  3
+
 struct gtk_mod;
 struct call_window;
 struct dial_dialog;