/*
 * Copyright 2020 Mihai Bazon
 * 
 * Permission to use, copy, modify, and/or distribute this software
 * for any purpose with or without fee is hereby granted, provided that
 * the above copyright notice and this permission notice appear in all copies.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR
 * BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR
 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 * 
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#if ENABLE_NLS
#include <locale.h>
#include <libintl.h>
#define _(x) gettext(x)
#define N_(x) (x)
#else
#define _(x)  (x)
#define N_(x) (x)
#endif

#include <gtk/gtk.h>
#include <unistd.h>
#include <errno.h>

#include "gtkcompletionline.h"
#include "config_prefs.h"

enum
{
   W_TEXT_STYLE_NORMAL,
   W_TEXT_STYLE_NOTFOUND,
   W_TEXT_STYLE_NOTUNIQUE,
   W_TEXT_STYLE_UNIQUE,
};

GtkApplication * gmrun_app = NULL;

char * gmrun_text = NULL;
static void gmrun_exit (void);
static gint signal_connect_id_cb_dialog_size_allocate;
GtkAllocation window_geom = { -1, -1, -1, -1 };
/* widgets that are used in several functions */
GtkWidget * compline = NULL;
GtkWidget * wlabel = NULL;
GtkWidget * wlabel_search = NULL;

/* preferences */
int USE_GLIB_XDG = 0;
int SHELL_RUN = 1;

/// BEGIN: TIMEOUT MANAGEMENT

static gboolean search_off_timeout ();
static guint g_search_off_timeout_id = 0;

static void remove_search_off_timeout (void)
{
   if (g_search_off_timeout_id) {
      g_source_remove(g_search_off_timeout_id);
      g_search_off_timeout_id = 0;
   }
}

static void add_search_off_timeout (guint32 timeout, GSourceFunc func)
{
   remove_search_off_timeout();
   if (!func)
      func = (GSourceFunc) search_off_timeout;
   g_search_off_timeout_id = g_timeout_add (timeout, func, NULL);
}

/// END: TIMEOUT MANAGEMENT

// https://unix.stackexchange.com/questions/457584/gtk3-change-text-color-in-a-label-raspberry-pi
static void set_info_text_color (GtkWidget *w, const char *text, int spec)
{
   char *markup = NULL;
   if (spec == W_TEXT_STYLE_NORMAL) {
       gtk_label_set_text (GTK_LABEL(w), text);
       return;
   }
   static const char * colors[] = {
      "black", /* W_TEXT_STYLE_NORMAL */
      "red",   /* W_TEXT_STYLE_NOTFOUND */
      "blue",  /* W_TEXT_STYLE_NOTUNIQUE */
      "green", /* W_TEXT_STYLE_UNIQUE */
   };
   markup = g_markup_printf_escaped ("<span foreground=\"%s\">%s</span>",
                                     colors[spec], text);
   if (markup) {
      gtk_label_set_markup (GTK_LABEL (w), markup);
      g_free(markup);
   }
}


static void run_the_command (char * cmd)
{
#if DEBUG
   fprintf (stderr, "cmd: %s\n", cmd);
#endif
 if (SHELL_RUN)
 {
   /* need to add extra &   */
   char * cmd2 = g_strconcat (cmd, " &", NULL);
   int ret = system (cmd2);
#if DEBUG
   fprintf (stderr, "cmd2: %s\n", cmd2);
#endif
   g_free (cmd2);
   if (ret != -1) {
      gmrun_exit ();
   } else {
      gchar *errmsg = g_strconcat("ERROR: ", strerror(errno), NULL);
      set_info_text_color (wlabel, errmsg, W_TEXT_STYLE_NOTFOUND);
      add_search_off_timeout (3000, NULL);
      g_free(errmsg);
   }
 }
 else // glib - more conservative approach and robust error reporting
 {
   GError * error = NULL;
   gboolean success;
   int argc;
   char ** argv;
   success = g_shell_parse_argv (cmd, &argc, &argv, &error);
   if (!success) {
      set_info_text_color (wlabel, error->message, W_TEXT_STYLE_NOTFOUND);
      g_error_free (error);
      add_search_off_timeout (3000, NULL);
      return;
   }
   success = g_spawn_async (NULL, argv, NULL,
                            G_SPAWN_SEARCH_PATH, NULL, NULL, NULL, &error);
   if (argv) {
      g_strfreev (argv);
   }
   if (success) {
      gmrun_exit ();
   } else {
      set_info_text_color (wlabel, error->message, W_TEXT_STYLE_NOTFOUND);
      g_error_free (error);
      add_search_off_timeout (3000, NULL);
   }
 }
}


static void on_ext_handler (GtkCompletionLine *cl, const char * filename)
{
 if (USE_GLIB_XDG) // GLib XDG handling (freedesktop specification)
 {
   gchar * content_type, * mime_type, * msg;
   const gchar * handler;
   GAppInfo * app_info;

   if (filename) {
      content_type = g_content_type_guess (filename, NULL, 0, NULL);
      if (content_type) {
         mime_type = g_content_type_get_mime_type (content_type);
         g_free (content_type);
         app_info = g_app_info_get_default_for_type (mime_type, FALSE);
         g_free (mime_type);
         if (app_info) {
            handler = g_app_info_get_commandline (app_info);
            msg = g_strconcat(_("Program: "), handler, NULL);
            gtk_label_set_text (GTK_LABEL (wlabel_search), msg);
            gtk_widget_show (wlabel_search);

            g_object_unref(app_info);
            g_free(msg);
            return;
         }
      }
   }
   search_off_timeout();
 }
 else // custom EXT handlers
 {
   const char * ext = strrchr (filename, '.');
   if (!ext) {
      search_off_timeout ();
      return;
   }
   const char * handler = config_get_handler_for_extension (ext);
   if (handler) {
      char * tmp = g_strconcat (_("Program: "), handler, NULL);
      gtk_label_set_text (GTK_LABEL (wlabel_search), tmp);
      gtk_widget_show (wlabel_search);
      g_free (tmp);
   }
 }
}


static void on_compline_runwithterm (GtkCompletionLine *cl)
{
   gchar *cmd;
   char * term;
   char * entry_text = g_strdup (gtk_entry_get_text (GTK_ENTRY(cl)));
   g_strstrip (entry_text);

   if (*entry_text) {
      if (config_get_string_expanded ("TermExec", &term)) {
         cmd = g_strconcat( term, " ", entry_text, NULL);
         g_free (term);
      } else {
         cmd = g_strconcat( "xterm -e ", entry_text, NULL);
      }
   } else {
      if (config_get_string ("Terminal", &term)) {
         cmd = g_strdup(term);
      } else {
         cmd = g_strdup("xterm");
      }
   }

   history_append (cl->hist, cmd);
   run_the_command (cmd);
   g_free (entry_text);
   g_free (cmd);
}

static gboolean search_off_timeout ()
{
   set_info_text_color (wlabel, _("Run:"), W_TEXT_STYLE_NORMAL);
   gtk_widget_hide (wlabel_search);
   g_search_off_timeout_id = 0;
   return G_SOURCE_REMOVE;
}

static void on_compline_unique (GtkCompletionLine *cl)
{
   set_info_text_color (wlabel, _("unique"), W_TEXT_STYLE_UNIQUE);
   add_search_off_timeout (1000, NULL);
}

static void on_compline_notunique (GtkCompletionLine *cl)
{
   set_info_text_color (wlabel, _("not unique"), W_TEXT_STYLE_NOTUNIQUE);
   add_search_off_timeout (1000, NULL);
}

static void on_compline_incomplete (GtkCompletionLine *cl)
{
   set_info_text_color (wlabel, _("not found"), W_TEXT_STYLE_NOTFOUND);
   add_search_off_timeout (1000, NULL);
}

static void on_search_mode (GtkCompletionLine *cl)
{
   if (cl->hist_search_mode == TRUE) {
      gtk_widget_show (wlabel_search);
      gtk_label_set_text (GTK_LABEL (wlabel), _("Search:"));
      gtk_label_set_text (GTK_LABEL (wlabel_search), cl->hist_word);
   } else {
      gtk_widget_hide (wlabel_search);
      gtk_label_set_text (GTK_LABEL (wlabel), _("Search OFF"));
      add_search_off_timeout (1000, NULL);
   }
}

static void on_search_letter (GtkCompletionLine *cl, GtkWidget *label)
{
   gtk_label_set_text (GTK_LABEL(label), cl->hist_word);
}

static gboolean search_fail_timeout (gpointer user_data)
{
   set_info_text_color (wlabel, _("Search:"), W_TEXT_STYLE_NOTUNIQUE);
   g_search_off_timeout_id = 0;
   return G_SOURCE_REMOVE;
}

static void on_search_not_found(GtkCompletionLine *cl)
{
   set_info_text_color (wlabel, _("Not Found!"), W_TEXT_STYLE_NOTFOUND);
   add_search_off_timeout (1000, (GSourceFunc) search_fail_timeout);
}


// =============================================================

static void xdg_app_run_command (GAppInfo *app, const gchar *args)
{
   // get
   char * cmd, * exe;
   GRegex * regex;

   regex = g_regex_new (".%[fFuUdDnNickvm]", G_REGEX_OPTIMIZE, G_REGEX_MATCH_NOTEMPTY, NULL);
   exe = g_regex_replace_literal (regex, // Remove xdg desktop files fields from app
                                  g_app_info_get_commandline (app),
                                  -1, 0, "", G_REGEX_MATCH_NOTEMPTY, NULL);
   cmd = g_strconcat (exe, " ", args, NULL);
   run_the_command (cmd); /* Launch the command */
   g_regex_unref (regex);
   g_free (exe);
   g_free (cmd);
}

/* Handler for URLs  */
static gboolean url_check (GtkCompletionLine *cl, char * entry_text)
{
 if (USE_GLIB_XDG) // GLib XDG handling (freedesktop specification)
 {
   char * delim;
   const char * url, * protocol;
   GAppInfo * app;
   delim = strchr (entry_text, ':');
   if (!delim || !*(delim+1)) {
      return FALSE;
   }
   url = delim + 1;
   if (url[0] == '/' && url[1] == '/')
   {
      protocol = entry_text;
      *delim = 0;
      app = g_app_info_get_default_for_uri_scheme (protocol);
      *delim = ':';
      if (app)
      { // found known uri handler for protocol
         xdg_app_run_command (app, entry_text);
         history_append (cl->hist, entry_text);
         g_object_unref (app);
         return TRUE;
      }
   }
   return FALSE;

 }
 else //-------- custom URL handlers
 {
   // <url_type> <delim>  <url>
   // http          :     //www.fsf.org
   //  <f  u  l  l     u  r  l>
   // config: URL_<url_type>
   // handler %s (format 1) = run handler with <url>
   // handler %u (format 2) = run handler with <full url>
   char * cmd;
   char * tmp, * delim, * p;
   char * url, * url_type, * full_url, * chosen_url;
   char * url_handler;
   char * config_key;
   cmd = tmp = delim = p = url_handler = config_key = NULL;
   delim = strchr (entry_text, ':');
   if (!delim || !*(delim+1)) {
      return FALSE;
   }
   tmp    = g_strdup (entry_text);
   delim  = strchr (tmp, ':');
   *delim = 0;
   url_type = tmp;       // http
   url      = delim + 1; // //www.fsf.org
   full_url = entry_text;

   config_key = g_strconcat ("URL_", url_type, NULL);
   if (config_get_string_expanded (config_key, &url_handler))
   {
      chosen_url = url;
      p = strchr (url_handler, '%');
      if (p) { // handler %s
         p++;
         if (*p == 'u') { // handler %u
            *p = 's';    // convert %u to %s (for printf)
            chosen_url = full_url;
         }
         cmd = g_strdup_printf (url_handler, chosen_url);
      } else {
         cmd = g_strconcat (url_handler, " ", url, NULL);
      }
      g_free (url_handler);
   }

   g_free (config_key);
   g_free (tmp);
   if (cmd) {
      history_append (cl->hist, entry_text);
      run_the_command (cmd);
      g_free (cmd);
      return TRUE;
   }
   return FALSE;
 }
}


static char * escape_spaces (char * entry_text)
{  // run file with glib: replace " " with "\ "
   GRegex * regex;
   char * quoted;
   if (!strstr (entry_text, "\\ ")) {
      regex = g_regex_new (" ", G_REGEX_OPTIMIZE, G_REGEX_MATCH_NOTEMPTY, NULL);
      quoted = g_regex_replace_literal (regex, entry_text, -1, 0, "\\ ", G_REGEX_MATCH_NOTEMPTY, NULL);
      g_regex_unref (regex);
   } else {
      quoted = strdup (entry_text); // already scaped, just duplicate text
   }
   return (quoted);
}

/* Handler for extensions */
static gboolean ext_check (GtkCompletionLine *cl, char * entry_text)
{
 if (USE_GLIB_XDG) // GLib XDG handling (freedesktop specification)
 {
   char *quoted, *content_type, *mime_type;
   GAppInfo *app_info;
   gboolean sure;
   quoted = escape_spaces (entry_text);
   /* File is executable: launch it (fail silently if file isn't really executable) */
   if (g_file_test (quoted, G_FILE_TEST_IS_EXECUTABLE)) {
      run_the_command (quoted);
      history_append (cl->hist, entry_text);
      g_free (quoted);
      return TRUE;
   }
   /* Check mime type through extension */
   if (quoted[0] == '/' && strchr (quoted, '.')) {
      content_type = g_content_type_guess (quoted, NULL, 0, &sure);
      if (content_type) {
         mime_type = g_content_type_get_mime_type (content_type);
         g_free (content_type);
         app_info = g_app_info_get_default_for_type (mime_type, FALSE);
         g_free (mime_type);
         if (app_info) { // found mime
            xdg_app_run_command (app_info, quoted);
            history_append (cl->hist, entry_text);
            g_free (quoted);
            g_object_unref(app_info);
            return TRUE;
         }
      }
   }
   g_free (quoted);
   return FALSE;

 }
 else //-------- custom EXTension handlers
 {
   // example: file.html | xdg-open '%s' -> xdg-open 'file.html'
   char * cmd;
   char * unescaped = g_strcompress (entry_text); /* unescape chars */
   char * ext = strrchr (entry_text, '.');
   char * handler_format = NULL;
   if (access (unescaped, F_OK) == -1) {
      // entry_text must be a valid filename
      g_free (unescaped);
      return FALSE;
   }
   if (ext) {
      handler_format = config_get_handler_for_extension (ext);
   }
   if (handler_format) {
      if (strstr (handler_format, "%s")) {
         cmd = g_strdup_printf (handler_format, unescaped);
      }
      else { // xdg-open
         cmd = g_strconcat (handler_format, " '", unescaped, "'", NULL);
      }
      history_append (cl->hist, entry_text);
      run_the_command (cmd);
      g_free (cmd);
      g_free (unescaped);
      return TRUE;
   }

   g_free (unescaped);
   return FALSE;
 }
}

// =============================================================

static void on_compline_activated (GtkCompletionLine *cl)
{
   char * entry_text = g_strdup (gtk_entry_get_text (GTK_ENTRY(cl)));
   g_strstrip (entry_text);

   if (url_check(cl,entry_text) == TRUE || ext_check(cl,entry_text) == TRUE)
   {
      g_free (entry_text);
      return;
   }

   gchar * cmd;
   char * AlwaysInTerm = NULL;
   char ** term_progs = NULL;
   char * selected_term_prog = NULL;

   if (config_get_string ("AlwaysInTerm", &AlwaysInTerm))
   {
      term_progs = g_strsplit (AlwaysInTerm, " ", 0);
      int i;
      for (i = 0; term_progs[i]; i++) {
         if (strcmp (term_progs[i], entry_text) == 0) {
            selected_term_prog = g_strdup (term_progs[i]);
            break;
         }
      }
      g_strfreev (term_progs);
   }

   if (selected_term_prog) {
      char * TermExec;
      config_get_string_expanded ("TermExec", &TermExec);
      cmd = g_strconcat(TermExec, " ", selected_term_prog, NULL);
      g_free (selected_term_prog);
      g_free (TermExec);
   } else {
      cmd = g_strdup(entry_text);
   }
   g_free (entry_text);

   history_append (cl->hist, cmd);
   run_the_command (cmd);
   g_free(cmd);
}


static void cb_dialog_size_allocate (GtkWidget *w, GdkRectangle *alloc, gpointer udata)
{
    // https://stackoverflow.com/questions/4886420/gtk-forbid-vertical-resize-of-gtkwindow
    GdkGeometry hints;
    g_signal_handler_disconnect (G_OBJECT(w), signal_connect_id_cb_dialog_size_allocate);
    /* dummy values for minx/max to no restrict vertical resizing */
    hints.min_width = 0;
    hints.max_width = G_MAXINT;
    /* do not allow vertical resizing */
    hints.min_height = alloc->height;
    hints.max_height = alloc->height;
    gtk_window_set_geometry_hints (GTK_WINDOW(w), NULL, &hints,
                                   GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE);
}

// =============================================================


static void gmrun_activate(void)
{
   GtkWidget *dialog, * main_vbox;
   GtkWidget *label_search;

   GtkWidget * window = gtk_application_window_new (gmrun_app);
   dialog = gtk_dialog_new();
   gtk_window_set_transient_for( (GtkWindow*)dialog, (GtkWindow*)window );
   gtk_window_set_title (GTK_WINDOW(window), "gmrun");
   gtk_window_set_title (GTK_WINDOW(dialog), "gmrun");
   main_vbox = gtk_dialog_get_content_area (GTK_DIALOG (dialog));

   // this removes the title bar..
   gtk_widget_realize (dialog);
   GdkWindow *gwin = gtk_widget_get_window (GTK_WIDGET(dialog));
   gdk_window_set_decorations (gwin, GDK_DECOR_BORDER);

   gtk_container_set_border_width(GTK_CONTAINER(dialog), 4);
   g_signal_connect (G_OBJECT(dialog), "destroy",
                     G_CALLBACK(gmrun_exit), NULL);
   signal_connect_id_cb_dialog_size_allocate = 
      g_signal_connect (G_OBJECT(dialog), "size-allocate",
                        G_CALLBACK(cb_dialog_size_allocate), NULL);

   GtkWidget *hhbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
   gtk_box_pack_start (GTK_BOX (main_vbox), hhbox, FALSE, FALSE, 0);

   GtkWidget *label = gtk_label_new (_("Run:"));
   gtk_box_pack_start (GTK_BOX(hhbox), label, FALSE, FALSE, 10);
   gtkcompat_widget_set_halign_left (GTK_WIDGET (label));
   wlabel = label;

   label_search = gtk_label_new("");
   gtk_box_pack_start (GTK_BOX (hhbox), label_search, FALSE, TRUE, 0);
   wlabel_search = label_search;

   compline = gtk_completion_line_new();

   gtk_box_pack_start (GTK_BOX (main_vbox), compline, TRUE, TRUE, 0);

   if (!config_get_int ("SHELL_RUN", &SHELL_RUN)) {
      SHELL_RUN = 1;
   }

   // don't show files starting with "." by default
   if (!config_get_int ("ShowDotFiles", &(GTK_COMPLETION_LINE(compline)->show_dot_files))) {
      GTK_COMPLETION_LINE(compline)->show_dot_files = 0;
   }
   int tmp;
   if (!config_get_int ("TabTimeout", &tmp)) {
      ((GtkCompletionLine*)compline)->tabtimeout = tmp;
   }
   if (!config_get_int ("USE_GLIB_XDG", &USE_GLIB_XDG)) {
      USE_GLIB_XDG = 0;
   }

   g_signal_connect(G_OBJECT(compline), "cancel",
                    G_CALLBACK(gmrun_exit), NULL);
   g_signal_connect(G_OBJECT(compline), "activate",
                    G_CALLBACK (on_compline_activated), NULL);
   g_signal_connect(G_OBJECT(compline), "runwithterm",
                    G_CALLBACK (on_compline_runwithterm), NULL);

   g_signal_connect(G_OBJECT(compline), "unique",
                    G_CALLBACK (on_compline_unique), NULL);
   g_signal_connect(G_OBJECT(compline), "notunique",
                    G_CALLBACK (on_compline_notunique), NULL);
   g_signal_connect(G_OBJECT(compline), "incomplete",
                    G_CALLBACK (on_compline_incomplete), NULL);

   g_signal_connect(G_OBJECT(compline), "search_mode",
                    G_CALLBACK (on_search_mode), NULL);
   g_signal_connect(G_OBJECT(compline), "search_not_found",
                    G_CALLBACK (on_search_not_found), NULL);
   g_signal_connect(G_OBJECT(compline), "search_letter",
                    G_CALLBACK(on_search_letter), label_search);

   g_signal_connect(G_OBJECT(compline), "ext_handler",
                    G_CALLBACK (on_ext_handler), NULL);

   int shows_last_history_item;
   if (!config_get_int ("ShowLast", &shows_last_history_item)) {
      shows_last_history_item = 0;
   }
   if (gmrun_text) {
      gtk_entry_set_text (GTK_ENTRY(compline), gmrun_text);
   } else if (shows_last_history_item) {
      gtk_completion_line_last_history_item (GTK_COMPLETION_LINE(compline));
   }

   // geometry: window position
   if (window_geom.x > -1 || window_geom.y > -1) {
      gtk_window_move (GTK_WINDOW (dialog), window_geom.x, window_geom.y);
   } else {
      /* default: centered */
      gtk_window_set_position (GTK_WINDOW(dialog), GTK_WIN_POS_CENTER_ALWAYS);
   }

   // geometry: window size
   if (window_geom.height > -1 || window_geom.width > -1) {
      gtk_window_set_default_size (GTK_WINDOW (dialog), window_geom.width,
                                   window_geom.height);
   } else {
      /* default width = 500 */
      gtk_window_set_default_size (GTK_WINDOW (dialog), 500, -1);
   }

   // window icon
   GError * error = NULL;
   GtkIconTheme * theme = gtk_icon_theme_get_default ();
   GdkPixbuf * icon = gtk_icon_theme_load_icon (theme, "gmrun", 48, GTK_ICON_LOOKUP_USE_BUILTIN, &error);
   if (error) {
      g_object_set (dialog, "icon-name", "gtk-execute", NULL);
      g_error_free (error);
   } else {
      gtk_window_set_icon (GTK_WINDOW (dialog), icon);
      g_object_unref (icon);
   }

   gtk_widget_show_all (dialog);

   gtk_window_set_focus (GTK_WINDOW(dialog), compline);
   if (gmrun_text) {
      // clear selection if command (text) is supplied as a parameter
      compline_clear_selection (GTK_COMPLETION_LINE (compline));
      // free memory
      free (gmrun_text);
   }
}

// =============================================================

static void parse_command_line (int argc, char ** argv)
{
   // --geometry / parse commandline options
   static char *geometry_str = NULL, *tmp = NULL;
   int i;
   static gboolean show_version;
   GError *error = NULL;
   GOptionContext *context = NULL;
   static GOptionEntry entries[] =
   {
      { "geometry", 'g', 0, G_OPTION_ARG_STRING, &geometry_str, "This option specifies the initial size and location of the window.", NULL, },
      { "version",  'v', 0, G_OPTION_ARG_NONE,   &show_version, "Show version", NULL, },
      { NULL },
   };

   context = g_option_context_new (NULL);
   g_option_context_add_main_entries (context, entries, NULL);
   g_option_context_add_group (context, gtk_get_option_group (TRUE));
   if (!g_option_context_parse (context, &argc, &argv, &error))
   {
      g_print ("option parsing failed: %s\n", error->message);
      if (context)      g_option_context_free (context);
      if (error)        g_error_free (error);
      if (geometry_str) g_free (geometry_str);
      exit (1);
   }
   if (context) g_option_context_free (context);
   if (error)   g_error_free (error);
   if (argc >= 2) {
      for (i = 1; i < argc; i++)
      { // all cli arguments are part of the same line
        if (tmp) {
            gmrun_text = g_strconcat (tmp, " ", argv[i], NULL);
            free (tmp);
            tmp = gmrun_text;
        } else {
            tmp = strdup (argv[i]);
            gmrun_text = tmp;
        }
      }
   }
   // --

   if (show_version) {
#ifdef HAVE_CONFIG_H
      puts (VERSION);
#endif
      gmrun_exit ();
      exit (0);
   }

   if (!geometry_str)
   {
      // --geometry was not specified, see config file
      char * geomstr;
      if (config_get_string ("Geometry", &geomstr)) {
         geometry_str = g_strdup (geomstr);
      }
   }

   if (geometry_str)
   {
      // --geometry WxH+X+Y
      // width x height + posX + posY
      int width, height, posX, posY;
      char *Wstr, *Hstr, *Xstr, *Ystr;
      Wstr = Hstr = Xstr = Ystr = NULL;

      Xstr = strchr (geometry_str, '+');
      if (Xstr) { // +posX+posY
         *Xstr = 0;
         Xstr++; // posX+posY
         Ystr = strchr (Xstr, '+');
         if (Ystr) { // +posY
            *Ystr = 0;
            Ystr++; // posY
         }
      }
      if (Xstr && Ystr && *Xstr && *Ystr) {
         posX = strtoll (Xstr, NULL, 0);
         posY = strtoll (Ystr, NULL, 0);
         ///fprintf (stderr, "x: %" G_GINT64_FORMAT "\ny: %" G_GINT64_FORMAT "\n", posX, posY);
         window_geom.x = posX;
         window_geom.y = posY;
      }

      Hstr = strchr (geometry_str, 'x');
      if (Hstr) { // WxH
         *Hstr = 0;
         Hstr++; // H
         Wstr = geometry_str;
         width  = strtoll (Wstr, NULL, 0);
         height = strtoll (Hstr, NULL, 0);
         ///fprintf (stderr, "w: %" G_GINT64_FORMAT "\nh: %" G_GINT64_FORMAT "\n", width, height);
         window_geom.width = width;
         window_geom.height = height;
      }

      g_free (geometry_str);
   }
}


// =============================================================
//                           MAIN

void gmrun_exit(void)
{
   if (compline) {
      gtk_widget_destroy (compline);
   }
   config_destroy ();
   if (gmrun_app) {
      g_application_quit (G_APPLICATION (gmrun_app));
   }
}

int main(int argc, char **argv)
{
   int status = 0;

#ifdef ENABLE_NLS
   bindtextdomain (GETTEXT_PACKAGE, PACKAGE_LOCALEDIR);
   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
   textdomain (GETTEXT_PACKAGE);
#endif

   config_init ();
   parse_command_line (argc, argv);

#if GTK_CHECK_VERSION(3, 4, 0)
   // Handling cmd line args with GApplication is a nightmare
   // follow this: https://developer.gnome.org/gtkmm-tutorial/stable/sec-multi-item-containers.html.en#boxes-command-line-options
   argc = 1; /* hide args from GApplication */
   gmrun_app = gtk_application_new ("org.gtk.gmrun", G_APPLICATION_NON_UNIQUE);
   g_signal_connect (gmrun_app, "activate", gmrun_activate, NULL);
   status = g_application_run (G_APPLICATION (gmrun_app), argc, argv);
   g_object_unref (gmrun_app);

#elif GTK_MAJOR_VERSION > 2
#  error "Gtk >= 3.4 is required"

#else /* gtk2 */
   // gmrun_app must point to *something*
   gmrun_app = (void *) &SHELL_RUN; //gtkcompat.h: #define g_application_quit(app) gtk_main_quit()
   gtk_init (&argc, &argv);
   gmrun_activate ();
   gtk_main ();
#endif

   return (status);
}

