From e7ef61e1b7c79f7cd9f9fcbbf08b2513e39911d4 Mon Sep 17 00:00:00 2001
From: Bob Ham <bob.ham@puri.sm>
Date: Mon, 17 Feb 2020 16:01:43 +0000
Subject: [PATCH] application: Add "--dial" command-line option

This option allows the specification of a much broader set of numbers
that tel: URIs, which are limited to global phone numbers in
international form and prohibit local numbers and other useful strings
like "*100#".

This functionality is implemented as a command-line option rather than
a "dial:" or "callto:" URI scheme for the same reason given in RFC
3966, section 7.3:

  '"Callto" was discarded because URI schemes locate a resource and do
  not specify an action to be taken.'
---
 src/calls-application.c | 127 ++++++++++++++++++++++++++++++++++++++--
 1 file changed, 123 insertions(+), 4 deletions(-)

diff --git a/src/calls-application.c b/src/calls-application.c
index d0dfad9..2ba5e2d 100644
--- a/src/calls-application.c
+++ b/src/calls-application.c
@@ -66,7 +66,10 @@ struct _CallsApplication
   CallsCallWindow  *call_window;
 };
 
-G_DEFINE_TYPE (CallsApplication, calls_application, GTK_TYPE_APPLICATION)
+G_DEFINE_TYPE (CallsApplication, calls_application, GTK_TYPE_APPLICATION);
+
+
+static gboolean start_proper (CallsApplication *self);
 
 
 static gint
@@ -75,7 +78,7 @@ handle_local_options (GApplication *application,
 {
   gboolean ok;
   g_autoptr(GError) error = NULL;
-  const gchar *name;
+  const gchar *arg;
 
   g_debug ("Registering application");
   ok = g_application_register (application, NULL, &error);
@@ -85,12 +88,12 @@ handle_local_options (GApplication *application,
                error->message);
     }
 
-  ok = g_variant_dict_lookup (options, "provider", "&s", &name);
+  ok = g_variant_dict_lookup (options, "provider", "&s", &arg);
   if (ok)
     {
       g_action_group_activate_action (G_ACTION_GROUP (application),
                                       "set-provider-name",
-                                      g_variant_new_string (name));
+                                      g_variant_new_string (arg));
     }
 
   ok = g_variant_dict_contains (options, "daemon");
@@ -101,6 +104,14 @@ handle_local_options (GApplication *application,
                                       NULL);
     }
 
+  ok = g_variant_dict_lookup (options, "dial", "&s", &arg);
+  if (ok)
+    {
+      g_action_group_activate_action (G_ACTION_GROUP (application),
+                                      "dial",
+                                      g_variant_new_string (arg));
+    }
+
   return -1; // Continue processing signal
 }
 
@@ -151,10 +162,112 @@ set_daemon_action (GSimpleAction *action,
 }
 
 
+#define DIALLING    "0-9*#+ABCD"
+#define SIGNALLING  ",TP!W@X"
+#define VISUAL      "[:space:]\\-.()t/"
+#define REJECT_RE   "[^" DIALLING SIGNALLING VISUAL "]"
+#define VISUAL_RE   "[" VISUAL "]"
+
+static gboolean
+check_dial_number (const gchar *number)
+{
+  GError *error = NULL;
+  GRegex *reject;
+  gboolean matches;
+
+  reject = g_regex_new (REJECT_RE, 0, 0, &error);
+  if (!reject)
+    {
+      g_warning ("Could not compile regex for"
+                 " dial number checking: %s",
+                 error->message);
+      g_error_free (error);
+      return FALSE;
+    }
+
+  matches = g_regex_match (reject, number, 0, NULL);
+
+  g_regex_unref (reject);
+
+  return !matches;
+}
+
+
+static gchar *
+extract_dial_string (const gchar *number)
+{
+  g_autoptr(GError) error = NULL;
+  g_autoptr(GRegex) replace_visual;
+  gchar *dial_string;
+
+  replace_visual = g_regex_new (VISUAL_RE, 0, 0, &error);
+  if (!replace_visual)
+    {
+      g_warning ("Could not compile regex for"
+                 " dial number extracting: %s",
+                 error->message);
+      return NULL;
+    }
+
+  dial_string = g_regex_replace_literal
+    (replace_visual, number, -1, 0, "", 0, &error);
+
+  if (!dial_string)
+    {
+      g_warning ("Error replacing visual separators"
+                 " in dial number: %s",
+                 error->message);
+      return NULL;
+    }
+
+  return dial_string;
+}
+
+
+static void
+dial_action (GSimpleAction *action,
+             GVariant      *parameter,
+             gpointer       user_data)
+{
+  CallsApplication *self = CALLS_APPLICATION (user_data);
+  const gchar *number;
+  gboolean number_ok;
+  gchar *dial_string;
+
+  number = g_variant_get_string (parameter, NULL);
+  g_return_if_fail (number != NULL);
+
+  number_ok = check_dial_number (number);
+  if (!number_ok)
+    {
+      g_warning ("Dial number `%s' is not a valid dial string",
+                 number);
+      return;
+    }
+
+  dial_string = extract_dial_string (number);
+  if (!dial_string)
+    {
+      return;
+    }
+
+  g_debug ("Dialing dial string `%s' extracted from number `%s'",
+           dial_string, number);
+
+
+  start_proper (self);
+
+  calls_main_window_dial (self->main_window,
+                          dial_string);
+  g_free (dial_string);
+}
+
+
 static const GActionEntry actions[] =
 {
   { "set-provider-name", set_provider_name_action, "s" },
   { "set-daemon", set_daemon_action, NULL },
+  { "dial", dial_action, "s" },
 };
 
 
@@ -503,6 +616,12 @@ calls_application_init (CallsApplication *self)
       _("Whether to present the main window on startup"),
       NULL
     },
+    {
+      "dial", 'l', G_OPTION_FLAG_NONE,
+      G_OPTION_ARG_STRING, NULL,
+      _("Dial a number"),
+      _("NUMBER")
+    },
     {
       NULL
     }