Commits

jjacky committed 67cfa7d Merge

merges CLI; updates man page

Adds 2 options (--auto-checks and --manual-checks) to run the auto/manual checks and print the results on stdout. This can be done without the need for a DISPLAY (no GTK init), thus works from a tty or through SSH.

Also adds a --disable-gui configure option, to make kalu a small CLI-only binary.

Comments (0)

Files changed (8)

 dist_doc_DATA = AUTHORS COPYING HISTORY LICENSE README.md
 doc_DATA = index.html
 
+if ! DISABLE_GUI
 logodir = $(datadir)/pixmaps
 logo_DATA = kalu.png
 
 desktopdir = /usr/share/applications
 dist_desktop_DATA = kalu.desktop
+endif
 
 if ! DISABLE_UPDATER
 policydir = /usr/share/polkit-1/actions
 		-Wuninitialized -Wconversion -Wstrict-prototypes
 AM_CFLAGS += -D_BSD_SOURCE
 
-kalu_CFLAGS = ${AM_CFLAGS} @GTK_CFLAGS@ @NOTIFY_CFLAGS@
-kalu_LDADD = @GTK_LIBS@ @NOTIFY_LIBS@ -lalpm -lm @LIBCURL@
+kalu_CFLAGS = ${AM_CFLAGS}
+kalu_LDADD = -lalpm -lm @LIBCURL@
 kalu_SOURCES = main.c arch_linux.h kalu.h \
 		conf.h conf.c \
-		util.h util.c util-gtk.h util-gtk.c \
+		util.h util.c \
 		kalu-alpm.h kalu-alpm.c \
-		watched.h watched.c  \
 		curl.h curl.c \
 		cJSON.h cJSON.c \
 		aur.h aur.c \
-		news.h news.c \
+		news.h news.c
+if ! DISABLE_GUI
+kalu_CFLAGS += @GTK_CFLAGS@ @NOTIFY_CFLAGS@
+kalu_LDADD += @GTK_LIBS@ @NOTIFY_LIBS@
+kalu_SOURCES += gui.h gui.c \
+		util-gtk.h util-gtk.c \
+		watched.h watched.c  \
 		preferences.h preferences.c
+else
+kalu_CFLAGS += @GLIB2_CFLAGS@
+kalu_LDADD += @GLIB2_LIBS@
+endif
 if ! DISABLE_UPDATER
 kalu_SOURCES += closures.h closures.c \
 		updater-dbus.h kupdater.h \
 		[set the prefix before each package for the AUR URL]),
 	[AUR_URL_PREFIX_PKG=$withval], [AUR_URL_PREFIX_PKG="&arg[[]]="])
 
-# Feature: updater
-AC_ARG_ENABLE([updater],
-	AC_HELP_STRING([--disable-updater],
-		[disable kalu's updater (GTK GUI for system upgrade)]),
+# Feature: GUI
+AC_ARG_ENABLE([gui],
+	AC_HELP_STRING([--disable-gui],
+		[disable kalu's GUI (make a CLI-only binary))]),
 	if test $enableval = "yes"; then
-		with_updater=yes
+		with_gui=yes
 	elif test $enableval = "no"; then
-		with_updater=no
+		with_gui=no
 	else
-		AC_MSG_ERROR([Invalid value given to --enable-updater; must be yes or no])
+		AC_MSG_ERROR([Invalid value given to --enable-gui; must be yes or no])
 	fi
 	,
-	with_updater=yes)
+	with_gui=yes)
+AM_CONDITIONAL([DISABLE_GUI], [test "x$with_gui" = "xno"])
+
+# Feature: updater (unless GUI was disabled)
+if test "x$with_gui" = "xyes"; then
+    AC_ARG_ENABLE([updater],
+        AC_HELP_STRING([--disable-updater],
+            [disable kalu's updater (GTK GUI for system upgrade)]),
+        if test $enableval = "yes"; then
+            with_updater=yes
+        elif test $enableval = "no"; then
+            with_updater=no
+        else
+            AC_MSG_ERROR([Invalid value given to --enable-updater; must be yes or no])
+        fi
+        ,
+        with_updater=yes)
+else
+    with_updater=no
+fi
 AM_CONDITIONAL([DISABLE_UPDATER], [test "x$with_updater" = "xno"])
 
 # Checks for libraries.
 	AC_MSG_ERROR([libalpm is required]))
 AC_CHECK_LIB([m], [fabs], ,
 	AC_MSG_ERROR([libm is required]))
-PKG_CHECK_MODULES(NOTIFY, [libnotify], ,
-	AC_MSG_ERROR([libnotify is required]))
-if test "x$with_updater" = "xyes"; then
-	PKG_CHECK_MODULES(POLKIT, [polkit-gobject-1], ,
-		AC_MSG_ERROR([PolicyKit is required (for kalu's updater)]))
-else
-	        AC_DEFINE([DISABLE_UPDATER], 1, [Disable kalu's udpater])
-fi
-
-# Checks for GTK+3
-PKG_CHECK_MODULES(GTK, [gtk+-3.0], , AC_MSG_ERROR([GTK+3 is required]))
+AS_IF([test "x$with_gui" = "xyes"], [
+    PKG_CHECK_MODULES(NOTIFY, [libnotify], ,
+        AC_MSG_ERROR([libnotify is required]))
+    if test "x$with_updater" = "xyes"; then
+        PKG_CHECK_MODULES(POLKIT, [polkit-gobject-1], ,
+            AC_MSG_ERROR([PolicyKit is required (for kalu's updater)]))
+    else
+        AC_DEFINE([DISABLE_UPDATER], 1, [Disable kalu's udpater])
+    fi
+    
+    # Checks for GTK+3
+    PKG_CHECK_MODULES(GTK, [gtk+-3.0], , AC_MSG_ERROR([GTK+3 is required]))
+], [
+    AC_DEFINE([DISABLE_GUI], 1, [Disable GUI])
+    AC_DEFINE([DISABLE_UPDATER], 1, [Disable kalu's udpater])
+    # No GTK, check for Glib
+    PKG_CHECK_MODULES(GLIB2, [glib-2.0 gobject-2.0 gthread-2.0], , AC_MSG_ERROR([glib2 is required]))
+])
 
 # Check for libcurl
 LIBCURL_CHECK_CONFIG([yes], , , AC_MSG_ERROR([libcurl is required]))
  Build information:
    source code location		: ${srcdir}
    prefix			: ${prefix}
+   GUI				: ${with_gui}
    kalu's updater		: ${with_updater}
 
    Arch Linux News RSS URL	: ${NEWS_RSS_URL}
    binaries			: $(eval echo $(eval echo ${bindir}))
    documentation		: $(eval echo $(eval echo ${docdir}))
    man pages			: $(eval echo $(eval echo ${mandir}))
-
 "
-
+/**
+ * kalu - Copyright (C) 2012 Olivier Brunel
+ *
+ * gui.c
+ * Copyright (C) 2012 Olivier Brunel <i.am.jack.mail@gmail.com>
+ * 
+ * This file is part of kalu.
+ *
+ * kalu 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 3 of the License, or (at your option) any later
+ * version.
+ *
+ * kalu 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
+ * kalu. If not, see http://www.gnu.org/licenses/
+ */
+
+#include <config.h>
+
+/* kalu */
+#include "kalu.h"
+#include "gui.h"
+#include "util-gtk.h"
+#include "watched.h"
+#include "preferences.h"
+#include "conf.h"
+#include "news.h"
+#ifndef DISABLE_UPDATER
+#include "kalu-updater.h"
+#include "updater.h"
+#endif
+
+#ifndef DISABLE_UPDATER
+#define run_updater()   do {                \
+        set_kalpm_busy (TRUE);              \
+        updater_run (config->pacmanconf,    \
+                     config->cmdline_post); \
+    } while (0)
+#endif
+
+static void menu_check_cb (GtkMenuItem *item, gpointer data);
+static void menu_quit_cb (GtkMenuItem *item, gpointer data);
+
+extern kalpm_state_t kalpm_state;
+
+gboolean
+show_error_cmdline (gchar *arg[])
+{
+    GtkWidget *dialog;
+    
+    dialog = gtk_message_dialog_new (NULL,
+                                     GTK_DIALOG_DESTROY_WITH_PARENT,
+                                     GTK_MESSAGE_ERROR,
+                                     GTK_BUTTONS_OK,
+                                     "%s",
+                                     "Unable to start process");
+    gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
+            "Error while trying to run command line: %s\n\nThe error was: <b>%s</b>",
+            arg[0], arg[1]);
+    gtk_window_set_title (GTK_WINDOW (dialog), "kalu: Unable to start process");
+    gtk_window_set_decorated (GTK_WINDOW (dialog), FALSE);
+    gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dialog), FALSE);
+    gtk_window_set_skip_pager_hint (GTK_WINDOW (dialog), FALSE);
+    gtk_dialog_run (GTK_DIALOG (dialog));
+    gtk_widget_destroy (dialog);
+    free (arg[0]);
+    free (arg[1]);
+    free (arg);
+    return FALSE;
+}
+
+static void
+run_cmdline (const char *cmdline)
+{
+    GError *error = NULL;
+    
+    set_kalpm_busy (TRUE);
+    if (!g_spawn_command_line_sync (cmdline, NULL, NULL, NULL, &error))
+    {
+        /* we can't just show the error message from here, because this is ran
+         * from another thread, hence no gtk_* functions can be used. */
+        char **arg;
+        arg = malloc (2 * sizeof (char *));
+        arg[0] = strdup (cmdline);
+        arg[1] = strdup (error->message);
+        g_main_context_invoke (NULL, (GSourceFunc) show_error_cmdline, (gpointer) arg);
+    }
+    set_kalpm_busy (FALSE);
+    if (G_UNLIKELY (error))
+    {
+        g_clear_error (&error);
+    }
+    else
+    {
+        /* check again, to refresh the state (since an upgrade was probably just done) */
+        kalu_check (TRUE);
+    }
+}
+
+void
+action_upgrade (NotifyNotification *notification, const char *action, gpointer data _UNUSED_)
+{
+    char *cmdline = NULL;
+    
+    notify_notification_close (notification, NULL);
+    
+    if (strcmp ("do_updates", action) == 0)
+    {
+        #ifndef DISABLE_UPDATER
+        if (config->action == UPGRADE_ACTION_KALU)
+        {
+            run_updater ();
+        }
+        else /* if (config->action == UPGRADE_ACTION_CMDLINE) */
+        {
+        #endif
+            cmdline = config->cmdline;
+        #ifndef DISABLE_UPDATER
+        }
+        #endif
+    }
+    else /* if (strcmp ("do_updates_aur", action) == 0) */
+    {
+        cmdline = config->cmdline_aur;
+    }
+    
+    if (cmdline)
+    {
+        /* run in a separate thread, to not block/make GUI unresponsive */
+        g_thread_create ((GThreadFunc) run_cmdline, (gpointer) cmdline, FALSE, NULL);
+    }
+}
+
+void
+action_watched (NotifyNotification *notification, char *action _UNUSED_,
+    alpm_list_t *packages)
+{
+    notify_notification_close (notification, NULL);
+    watched_update (packages, FALSE);
+}
+
+void
+action_watched_aur (NotifyNotification *notification, char *action _UNUSED_,
+    alpm_list_t *packages)
+{
+    notify_notification_close (notification, NULL);
+    watched_update (packages, TRUE);
+}
+
+void
+action_news (NotifyNotification *notification, char *action _UNUSED_,
+    gchar *xml_news)
+{
+    GError *error = NULL;
+    
+    notify_notification_close (notification, NULL);
+    set_kalpm_busy (TRUE);
+    if (!news_show (xml_news, TRUE, &error))
+    {
+        show_error ("Unable to show the news", error->message, NULL);
+        g_clear_error (&error);
+    }
+}
+
+void
+notification_closed_cb (NotifyNotification *notification, gpointer data _UNUSED_)
+{
+    g_object_unref (notification);
+}
+
+gboolean
+is_pacman_conflicting (alpm_list_t *packages)
+{
+    gboolean ret = FALSE;
+    alpm_list_t *i;
+    kalu_package_t *pkg;
+    char *s, *ss, *old, *new, *so, *sn;
+    
+    for (i = packages; i; i = alpm_list_next (i))
+    {
+        pkg = i->data;
+        if (strcmp ("pacman", pkg->name) == 0)
+        {
+            /* because we'll mess with it */
+            old = strdup (pkg->old_version);
+            /* locate begining of (major) version number (might have epoch: before) */
+            s = strchr (old, ':');
+            if (s)
+            {
+                so = s + 1;
+            }
+            else
+            {
+                so = old;
+            }
+            
+            s = strrchr (so, '-');
+            if (!s)
+            {
+                /* should not be possible */
+                free (old);
+                break;
+            }
+            *s = '.';
+            
+            /* because we'll mess with it */
+            new = strdup (pkg->new_version);
+            /* locate begining of (major) version number (might have epoch: before) */
+            s = strchr (new, ':');
+            if (s)
+            {
+                sn = s + 1;
+            }
+            else
+            {
+                sn = new;
+            }
+            
+            s = strrchr (sn, '-');
+            if (!s)
+            {
+                /* should not be possible */
+                free (old);
+                free (new);
+                break;
+            }
+            *s = '.';
+            
+            int nb = 0; /* to know which part (major/minor) we're dealing with */
+            while ((s = strchr (so, '.')) && (ss = strchr (sn, '.')))
+            {
+                *s = '\0';
+                *ss = '\0';
+                ++nb;
+                
+                /* if major or minor goes up, API changes is likely and kalu's
+                 * dependency will kick in */
+                if (atoi (sn) > atoi (so))
+                {
+                    ret = TRUE;
+                    break;
+                }
+                
+                /* if nb is 2 this was the minor number, past this we don't care */
+                if (nb == 2)
+                {
+                    break;
+                }
+                so = s + 1;
+                sn = ss + 1;
+            }
+            
+            free (old);
+            free (new);
+            break;
+        }
+    }
+    
+    return ret;
+}
+
+inline void
+kalu_check (gboolean is_auto)
+{
+    /* in case e.g. the menu was shown (sensitive) before an auto-check started */
+    if (kalpm_state.is_busy)
+    {
+        return;
+    }
+    set_kalpm_busy (TRUE);
+    
+    /* run in a separate thread, to not block/make GUI unresponsive */
+    g_thread_create ((GThreadFunc) kalu_check_work, GINT_TO_POINTER (is_auto), FALSE, NULL);
+}
+
+void
+kalu_auto_check (void)
+{
+    kalu_check (TRUE);
+}
+
+static void
+menu_check_cb (GtkMenuItem *item _UNUSED_, gpointer data _UNUSED_)
+{
+    kalu_check (FALSE);
+}
+
+static void
+menu_quit_cb (GtkMenuItem *item _UNUSED_, gpointer data _UNUSED_)
+{
+    /* in case e.g. the menu was shown (sensitive) before an auto-check started */
+    if (kalpm_state.is_busy)
+    {
+        return;
+    }
+    gtk_main_quit ();
+}
+
+static void
+menu_manage_cb (GtkMenuItem *item _UNUSED_, gboolean is_aur)
+{
+    watched_manage (is_aur);
+}
+
+static inline void
+kalu_sysupgrade (void)
+{
+    /* in case e.g. the menu was shown (sensitive) before an auto-check started */
+    if (kalpm_state.is_busy || config->action == UPGRADE_NO_ACTION)
+    {
+        return;
+    }
+    
+    #ifndef DISABLE_UPDATER
+    if (config->action == UPGRADE_ACTION_KALU)
+    {
+        run_updater ();
+    }
+    else /* if (config->action == UPGRADE_ACTION_CMDLINE) */
+    {
+    #endif
+        /* run in a separate thread, to not block/make GUI unresponsive */
+        g_thread_create ((GThreadFunc) run_cmdline, (gpointer) config->cmdline, FALSE, NULL);
+    #ifndef DISABLE_UPDATER
+    }
+    #endif
+}
+
+static void
+menu_news_cb (GtkMenuItem *item _UNUSED_, gpointer data _UNUSED_)
+{
+    GError *error = NULL;
+    /* in case e.g. the menu was shown (sensitive) before an auto-check started */
+    if (kalpm_state.is_busy)
+    {
+        return;
+    }
+    set_kalpm_busy (TRUE);
+    
+    if (!news_show (NULL, FALSE, &error))
+    {
+        show_error ("Unable to show the recent Arch Linux news", error->message, NULL);
+        g_clear_error (&error);
+    }
+}
+
+static void
+menu_help_cb (GtkMenuItem *item _UNUSED_, gpointer data _UNUSED_)
+{
+    GError *error = NULL;
+    
+    if (!show_help (&error))
+    {
+        show_error ("Unable to show help", error->message, NULL);
+        g_clear_error (&error);
+    }
+}
+
+static void
+menu_history_cb (GtkMenuItem *item _UNUSED_, gpointer data _UNUSED_)
+{
+    GError *error = NULL;
+    
+    if (!show_history (&error))
+    {
+        show_error ("Unable to show change log", error->message, NULL);
+        g_clear_error (&error);
+    }
+}
+
+static void
+menu_prefs_cb (GtkMenuItem *item _UNUSED_, gpointer data _UNUSED_)
+{
+    show_prefs ();
+}
+
+static void
+menu_about_cb (GtkMenuItem *item _UNUSED_, gpointer data _UNUSED_)
+{
+    GtkAboutDialog *about;
+    GdkPixbuf *pixbuf;
+    const char *authors[] = {"Olivier Brunel", "Dave Gamble", "Pacman Development Team", NULL};
+    const char *artists[] = {"Painless Rob", NULL};
+    
+    about = GTK_ABOUT_DIALOG (gtk_about_dialog_new ());
+    pixbuf = gtk_widget_render_icon_pixbuf (GTK_WIDGET (about), "kalu-logo",
+                                            GTK_ICON_SIZE_DIALOG);
+    gtk_window_set_icon (GTK_WINDOW (about), pixbuf);
+    gtk_about_dialog_set_logo (about, pixbuf);
+    g_object_unref (G_OBJECT (pixbuf));
+    
+    gtk_about_dialog_set_program_name (about, PACKAGE_NAME);
+    gtk_about_dialog_set_version (about, PACKAGE_VERSION);
+    gtk_about_dialog_set_comments (about, PACKAGE_TAG);
+    gtk_about_dialog_set_website (about, "https://bitbucket.org/jjacky/kalu");
+    gtk_about_dialog_set_website_label (about, "https://bitbucket.org/jjacky/kalu");
+    gtk_about_dialog_set_copyright (about, "Copyright (C) 2012 Olivier Brunel");
+    gtk_about_dialog_set_license_type (about, GTK_LICENSE_GPL_3_0);
+    gtk_about_dialog_set_authors (about, authors);
+    gtk_about_dialog_set_artists (about, artists);
+    
+    gtk_dialog_run (GTK_DIALOG (about));
+    gtk_widget_destroy (GTK_WIDGET (about));
+}
+
+static gboolean
+menu_unmap_cb (GtkWidget *menu, GdkEvent *event _UNUSED_, gpointer data _UNUSED_)
+{
+    gtk_widget_destroy (menu);
+    g_object_unref (menu);
+    return TRUE;
+}
+
+void
+icon_popup_cb (GtkStatusIcon *_icon _UNUSED_, guint button, guint activate_time,
+               gpointer data _UNUSED_)
+{
+    GtkWidget   *menu;
+    GtkWidget   *item;
+    GtkWidget   *image;
+    guint        pos = 0;
+    
+    menu = gtk_menu_new();
+    
+    item = gtk_image_menu_item_new_with_label ("Check for Upgrades...");
+    gtk_widget_set_sensitive (item, !kalpm_state.is_busy);
+    image = gtk_image_new_from_stock ("kalu-logo", GTK_ICON_SIZE_MENU);
+    gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+    gtk_widget_set_tooltip_text (item, "Check if there are any upgrades available");
+    g_signal_connect (G_OBJECT (item), "activate",
+                      G_CALLBACK (menu_check_cb), NULL);
+    gtk_widget_show (item);
+    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
+    
+    if (config->action != UPGRADE_NO_ACTION)
+    {
+        item = gtk_image_menu_item_new_with_label ("System upgrade...");
+        gtk_widget_set_sensitive (item, !kalpm_state.is_busy);
+        image = gtk_image_new_from_stock ("kalu-logo", GTK_ICON_SIZE_MENU);
+        gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+        gtk_widget_set_tooltip_text (item, "Perform a system upgrade");
+        g_signal_connect (G_OBJECT (item), "activate",
+                          G_CALLBACK (kalu_sysupgrade), NULL);
+        gtk_widget_show (item);
+        gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
+    }
+    
+    item = gtk_image_menu_item_new_with_label ("Show recent Arch Linux news...");
+    gtk_widget_set_sensitive (item, !kalpm_state.is_busy);
+    image = gtk_image_new_from_stock ("kalu-logo", GTK_ICON_SIZE_MENU);
+    gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
+    gtk_widget_set_tooltip_text (item, "Show 10 most recent Arch Linux news");
+    g_signal_connect (G_OBJECT (item), "activate",
+                      G_CALLBACK (menu_news_cb), NULL);
+    gtk_widget_show (item);
+    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
+    
+    item = gtk_separator_menu_item_new ();
+    gtk_widget_show (item);
+    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
+    
+    item = gtk_image_menu_item_new_with_label ("Manage watched packages...");
+    g_signal_connect (G_OBJECT (item), "activate",
+                      G_CALLBACK (menu_manage_cb), (gpointer) FALSE);
+    gtk_widget_show (item);
+    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
+    
+    item = gtk_image_menu_item_new_with_label ("Manage watched AUR packages...");
+    g_signal_connect (G_OBJECT (item), "activate",
+                      G_CALLBACK (menu_manage_cb), (gpointer) TRUE);
+    gtk_widget_show (item);
+    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
+    
+    item = gtk_separator_menu_item_new ();
+    gtk_widget_show (item);
+    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
+    
+    item = gtk_image_menu_item_new_from_stock (GTK_STOCK_PREFERENCES, NULL);
+    gtk_widget_set_tooltip_text (item, "Edit preferences");
+    g_signal_connect (G_OBJECT (item), "activate",
+                      G_CALLBACK (menu_prefs_cb), NULL);
+    gtk_widget_show (item);
+    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
+    
+    item = gtk_separator_menu_item_new ();
+    gtk_widget_show (item);
+    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
+    
+    item = gtk_image_menu_item_new_from_stock (GTK_STOCK_HELP, NULL);
+    gtk_widget_set_tooltip_text (item, "Show help (man page)");
+    g_signal_connect (G_OBJECT (item), "activate",
+                      G_CALLBACK (menu_help_cb), NULL);
+    gtk_widget_show (item);
+    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
+    
+    item = gtk_image_menu_item_new_with_label ("Change log");
+    gtk_widget_set_tooltip_text (item, "Show change log");
+    g_signal_connect (G_OBJECT (item), "activate",
+                      G_CALLBACK (menu_history_cb), (gpointer) TRUE);
+    gtk_widget_show (item);
+    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
+    
+    item = gtk_image_menu_item_new_from_stock (GTK_STOCK_ABOUT, NULL);
+    gtk_widget_set_tooltip_text (item, "Show Copyright & version information");
+    g_signal_connect (G_OBJECT (item), "activate",
+                      G_CALLBACK (menu_about_cb), NULL);
+    gtk_widget_show (item);
+    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
+    
+    item = gtk_separator_menu_item_new ();
+    gtk_widget_show (item);
+    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
+    
+    item = gtk_image_menu_item_new_from_stock (GTK_STOCK_QUIT, NULL);
+    gtk_widget_set_sensitive (item, !kalpm_state.is_busy);
+    gtk_widget_set_tooltip_text (item, "Exit kalu");
+    g_signal_connect (G_OBJECT (item), "activate",
+                      G_CALLBACK (menu_quit_cb), NULL);
+    gtk_widget_show (item);
+    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
+    
+    /* since we don't pack the menu anywhere, we need to "take ownership" of it,
+     * and we'll destroy it when done, i.e. when it's unmapped */
+    g_object_ref_sink (menu);
+    gtk_widget_add_events (menu, GDK_STRUCTURE_MASK);
+    g_signal_connect (G_OBJECT (menu), "unmap-event",
+                      G_CALLBACK (menu_unmap_cb), NULL);
+    
+    gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, activate_time);
+}
+
+GPtrArray *open_windows = NULL;
+static gboolean has_hidden_windows = FALSE;
+
+void
+add_open_window (gpointer window)
+{
+    if (!open_windows)
+    {
+        open_windows = g_ptr_array_new ();
+    }
+    g_ptr_array_add (open_windows, window);
+}
+
+void
+remove_open_window (gpointer window)
+{
+    g_ptr_array_remove (open_windows, window);
+}
+
+static inline void
+toggle_open_windows (void)
+{
+    if (!open_windows || open_windows->len == 0)
+    {
+        return;
+    }
+    
+    g_ptr_array_foreach (open_windows,
+                         (GFunc) ((has_hidden_windows) ? gtk_widget_show : gtk_widget_hide),
+                         NULL);
+    has_hidden_windows = !has_hidden_windows;
+}
+
+static guint icon_press_timeout = 0;
+
+#define process_click_action(on_click)  do {        \
+        if (on_click == DO_SYSUPGRADE)              \
+        {                                           \
+            kalu_sysupgrade ();                     \
+        }                                           \
+        else if (on_click == DO_CHECK)              \
+        {                                           \
+            kalu_check (FALSE);                     \
+        }                                           \
+        else if (on_click == DO_TOGGLE_WINDOWS)     \
+        {                                           \
+            toggle_open_windows ();                 \
+        }                                           \
+    } while (0)
+
+static gboolean
+icon_press_click (gpointer data _UNUSED_)
+{
+    icon_press_timeout = 0;
+    
+    process_click_action (config->on_sgl_click);
+    
+    return FALSE;
+}
+
+gboolean
+icon_press_cb (GtkStatusIcon *icon _UNUSED_, GdkEventButton *event, gpointer data _UNUSED_)
+{
+    /* left button? */
+    if (event->button == 1)
+    {
+        if (event->type == GDK_2BUTTON_PRESS)
+        {
+            /* we probably had a timeout set for the click, remove it */
+            if (icon_press_timeout > 0)
+            {
+                g_source_remove (icon_press_timeout);
+                icon_press_timeout = 0;
+            }
+            
+            process_click_action (config->on_dbl_click);
+        }
+        else if (event->type == GDK_BUTTON_PRESS)
+        {
+            /* As per GTK manual: on a dbl-click, we get GDK_BUTTON_PRESS twice
+             * and then a GDK_2BUTTON_PRESS. Obviously, we want then to ignore
+             * the two GDK_BUTTON_PRESS.
+             * Also per manual, for a double click to occur, the second button
+             * press must occur within 1/4 of a second of the first; so:
+             * - on GDK_BUTTON_PRESS we set a timeout, in 250ms
+             *  - if it expires/gets triggered, it was a click
+             *  - if another click happens within that time, the timeout will be
+             *    removed (see GDK_2BUTTON_PRESS above) and the clicks ignored
+             * - if a GDK_BUTTON_PRESS occurs while a timeout is set, it's a
+             * second click and ca be ignored, GDK_2BUTTON_PRESS will handle it */
+            if (icon_press_timeout == 0)
+            {
+                icon_press_timeout = g_timeout_add (250, icon_press_click, NULL);
+            }
+        }
+    }
+    
+    return FALSE;
+}
+
+#undef process_click_action
+
+#define addstr(...)     do {                            \
+        len = snprintf (s, (size_t) max, __VA_ARGS__);  \
+        max -= len;                                     \
+        s += len;                                       \
+    } while (0)
+gboolean
+icon_query_tooltip_cb (GtkWidget *icon _UNUSED_, gint x _UNUSED_, gint y _UNUSED_,
+                       gboolean keyboard_mode _UNUSED_, GtkTooltip *tooltip,
+                       gpointer data _UNUSED_)
+{
+    GDateTime *current;
+    GTimeSpan timespan;
+    gint nb;
+    gchar buf[420], *s = buf;
+    gint max = 420, len;
+    
+    addstr ("[kalu%s]", (has_hidden_windows) ? " +" : "");
+    
+    if (kalpm_state.is_busy)
+    {
+        addstr (" Checking/updating in progress...");
+        gtk_tooltip_set_text (tooltip, buf);
+        return TRUE;
+    }
+    else if (kalpm_state.last_check == NULL)
+    {
+        gtk_tooltip_set_text (tooltip, buf);
+        return TRUE;
+    }
+    
+    addstr (" Last checked ");
+    
+    current = g_date_time_new_now_local ();
+    timespan = g_date_time_difference (current, kalpm_state.last_check);
+    g_date_time_unref (current);
+    
+    if (timespan < G_TIME_SPAN_MINUTE)
+    {
+        addstr ("just now");
+    }
+    else
+    {
+        if (timespan >= G_TIME_SPAN_DAY)
+        {
+            nb = (gint) (timespan / G_TIME_SPAN_DAY);
+            timespan -= (nb * G_TIME_SPAN_DAY);
+            if (nb > 1)
+            {
+                addstr ("%d days ", nb);
+            }
+            else
+            {
+                addstr ("1 day ");
+            }
+        }
+        if (timespan >= G_TIME_SPAN_HOUR)
+        {
+            nb = (gint) (timespan / G_TIME_SPAN_HOUR);
+            timespan -= (nb * G_TIME_SPAN_HOUR);
+            if (nb > 1)
+            {
+                addstr ("%d hours ", nb);
+            }
+            else
+            {
+                addstr ("1 hour ");
+            }
+        }
+        if (timespan >= G_TIME_SPAN_MINUTE)
+        {
+            nb = (gint) (timespan / G_TIME_SPAN_MINUTE);
+            timespan -= (nb * G_TIME_SPAN_MINUTE);
+            if (nb > 1)
+            {
+                addstr ("%d minutes ", nb);
+            }
+            else
+            {
+                addstr ("1 minute ");
+            }
+        }
+        
+        addstr ("ago");
+    }
+    
+    if (config->syncdbs_in_tooltip && kalpm_state.nb_syncdbs > 0)
+    {
+        addstr ("\nsync possible for %d dbs", kalpm_state.nb_syncdbs);
+    }
+    
+    if (kalpm_state.nb_news > 0)
+    {
+        addstr ("\n%d unread news", kalpm_state.nb_news);
+    }
+    if (kalpm_state.nb_upgrades > 0)
+    {
+        addstr ("\n%d upgrades available", kalpm_state.nb_upgrades);
+    }
+    if (kalpm_state.nb_watched > 0)
+    {
+        addstr ("\n%d watched packages updated", kalpm_state.nb_watched);
+    }
+    if (kalpm_state.nb_aur > 0)
+    {
+        addstr ("\n%d AUR packages updated", kalpm_state.nb_aur);
+    }
+    if (kalpm_state.nb_watched_aur > 0)
+    {
+        addstr ("\n%d watched AUR packages updated", kalpm_state.nb_watched_aur);
+    }
+    
+    if (max <= 0)
+    {
+        sprintf (buf, "kalu: error setting tooltip");
+    }
+    gtk_tooltip_set_text (tooltip, buf);
+    return TRUE;
+}
+#undef addstr
+
+GtkStatusIcon *icon = NULL;
+
+static gboolean
+set_status_icon (gboolean active)
+{
+    if (active)
+    {
+        gtk_status_icon_set_from_stock (icon, "kalu-logo");
+    }
+    else
+    {
+        gtk_status_icon_set_from_stock (icon, "kalu-logo-gray");
+    }
+    /* do NOT get called back */
+    return FALSE;
+}
+
+void
+set_kalpm_nb (check_t type, gint nb)
+{
+    if (type & CHECK_UPGRADES)
+    {
+        kalpm_state.nb_upgrades = nb;
+    }
+    
+    if (type & CHECK_WATCHED)
+    {
+        kalpm_state.nb_watched = nb;
+    }
+    
+    if (type & CHECK_AUR)
+    {
+        kalpm_state.nb_aur = nb;
+    }
+    
+    if (type & CHECK_WATCHED_AUR)
+    {
+        kalpm_state.nb_watched_aur = nb;
+    }
+    
+    if (type & CHECK_NEWS)
+    {
+        kalpm_state.nb_news = nb;
+    }
+    
+    /* thing is, this function can be called from another thread (e.g. from
+     * kalu_check_work, which runs in a separate thread not to block GUI...)
+     * but when that happens, we can't use gtk_* functions, i.e. we can't change
+     * the status icon. so, this will make sure the call to set_status_icon
+     * happens in the main thread */
+    gboolean active = (kalpm_state.nb_upgrades + kalpm_state.nb_watched
+                       + kalpm_state.nb_aur + kalpm_state.nb_watched_aur
+                       + kalpm_state.nb_news > 0);
+    g_main_context_invoke (NULL, (GSourceFunc) set_status_icon, GINT_TO_POINTER (active));
+}
+
+inline void
+set_kalpm_nb_syncdbs (gint nb)
+{
+    kalpm_state.nb_syncdbs = nb;
+}
+
+static gboolean
+switch_status_icon (void)
+{
+    static gboolean active = FALSE;
+    active = !active;
+    set_status_icon (active);
+    /* keep timeout alive */
+    return TRUE;
+}
+
+void
+set_kalpm_busy (gboolean busy)
+{
+    gint old = kalpm_state.is_busy;
+    
+    /* we use an counter because when using a cmdline for both upgrades & AUR,
+     * and both are triggered at the same time (from notifications) then we
+     * should only bo back to not busy when *both* are done; fixes #8 */
+    if (busy)
+    {
+        ++kalpm_state.is_busy;
+    }
+    else if (kalpm_state.is_busy > 0)
+    {
+        --kalpm_state.is_busy;
+    }
+    
+    /* make sure the state changed/there's something to do */
+    if ((old > 0  && kalpm_state.is_busy > 0)
+     || (old == 0 && kalpm_state.is_busy == 0))
+    {
+        return;
+    }
+    
+    if (busy)
+    {
+        /* remove auto-check timeout */
+        if (kalpm_state.timeout > 0)
+        {
+            g_source_remove (kalpm_state.timeout);
+            kalpm_state.timeout = 0;
+        }
+        
+        /* set timeout for status icon */
+        kalpm_state.timeout_icon = g_timeout_add (420,
+            (GSourceFunc) switch_status_icon, NULL);
+    }
+    else
+    {
+        /* set timeout for next auto-check */
+        guint seconds;
+        
+        if (config->has_skip)
+        {
+            GDateTime *now, *begin, *end, *next;
+            gint year, month, day;
+            gboolean is_within_skip = FALSE;
+            
+            now = g_date_time_new_now_local ();
+            /* create GDateTime for begin & end of skip period */
+            /* Note: begin & end are both for the current day, which means we can
+             * have begin > end, with e.g. 18:00-09:00 */
+            g_date_time_get_ymd (now, &year, &month, &day);
+            begin = g_date_time_new_local (year, month, day,
+                config->skip_begin_hour, config->skip_begin_minute, 0);
+            end = g_date_time_new_local (year, month, day,
+                config->skip_end_hour, config->skip_end_minute, 0);
+            
+            /* determine when the next check would take place */
+            next = g_date_time_add_seconds (now, (gdouble) config->interval);
+            
+            /* determine if next within skip period */
+            /* is begin > end ? */
+            if (g_date_time_compare (begin, end) == 1)
+            {
+                /* e.g. 18:00 -> 09:00 */
+                if (g_date_time_compare (next, end) == -1)
+                {
+                    /* before 09:00 */
+                    is_within_skip = TRUE;
+                }
+                else if (g_date_time_compare (next, begin) == 1)
+                {
+                    /* after 18:00 */
+                    is_within_skip = TRUE;
+                    /* we need to switch end to the end for the next day */
+                    g_date_time_unref (end);
+                    end = g_date_time_new_local (year, month, day + 1,
+                        config->skip_end_hour, config->skip_end_minute, 0);
+                }
+            }
+            else
+            {
+                /* e.g. 09:00 -> 18:00 */
+                is_within_skip = (g_date_time_compare (next, begin) == 1
+                    && g_date_time_compare (next, end) == -1);
+            }
+            
+            if (is_within_skip)
+            {
+                /* we'll do the next check at the end of skip period */
+                GTimeSpan timespan;
+                timespan = g_date_time_difference (end, now);
+                seconds = (guint) (timespan / G_TIME_SPAN_SECOND);
+            }
+            else
+            {
+                seconds = (guint) config->interval;
+            }
+            
+            g_date_time_unref (now);
+            g_date_time_unref (begin);
+            g_date_time_unref (end);
+            g_date_time_unref (next);
+        }
+        else
+        {
+            seconds = (guint) config->interval;
+        }
+        kalpm_state.timeout = g_timeout_add_seconds (seconds,
+            (GSourceFunc) kalu_auto_check, NULL);
+        
+        /* remove status icon timeout */
+        if (kalpm_state.timeout_icon > 0)
+        {
+            g_source_remove (kalpm_state.timeout_icon);
+            kalpm_state.timeout_icon = 0;
+            /* ensure icon is right */
+            set_kalpm_nb (0, 0);
+        }
+    }
+}
+
+gboolean
+reload_watched (gboolean is_aur, GError **error)
+{
+    GError *local_err = NULL;
+    gchar file[MAX_PATH];
+    conf_file_t conffile;
+    
+    if (is_aur)
+    {
+        /* clear */
+        FREE_WATCHED_PACKAGE_LIST (config->watched_aur);
+        /* load */
+        snprintf (file, MAX_PATH - 1, "%s/.config/kalu/watched-aur.conf", g_get_home_dir ());
+        conffile = CONF_FILE_WATCHED_AUR;
+    }
+    else
+    {
+        /* clear */
+        FREE_WATCHED_PACKAGE_LIST (config->watched);
+        /* load */
+        snprintf (file, MAX_PATH - 1, "%s/.config/kalu/watched.conf", g_get_home_dir ());
+        conffile = CONF_FILE_WATCHED;
+    }
+    
+    if (!parse_config_file (file, conffile, &local_err))
+    {
+        g_propagate_error (error, local_err);
+        return FALSE;
+    }
+    
+    return TRUE;
+}
+/**
+ * kalu - Copyright (C) 2012 Olivier Brunel
+ *
+ * gui.h
+ * Copyright (C) 2012 Olivier Brunel <i.am.jack.mail@gmail.com>
+ * 
+ * This file is part of kalu.
+ *
+ * kalu 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 3 of the License, or (at your option) any later
+ * version.
+ *
+ * kalu 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
+ * kalu. If not, see http://www.gnu.org/licenses/
+ */
+
+#ifndef _GUI_H
+#define _GUI_H
+
+/* C */
+#include <string.h>
+
+/* alpm */
+#include <alpm_list.h>
+
+/* gtk */
+#include <gtk/gtk.h>
+
+/* notify */
+#include <libnotify/notify.h>
+
+gboolean
+show_error_cmdline (gchar *arg[]);
+
+void
+action_upgrade (NotifyNotification *notification, const char *action, gpointer data);
+
+void
+action_watched (NotifyNotification *notification, char *action, alpm_list_t *packages);
+
+void
+action_watched_aur (NotifyNotification *notification, char *action, alpm_list_t *packages);
+
+void
+action_news (NotifyNotification *notification, char *action, gchar *xml_news);
+
+void
+notification_closed_cb (NotifyNotification *notification, gpointer data);
+
+void
+kalu_check (gboolean is_auto);
+
+void
+kalu_auto_check (void);
+
+gboolean
+is_pacman_conflicting (alpm_list_t *packages);
+
+void
+icon_popup_cb (GtkStatusIcon *_icon, guint button, guint activate_time,
+               gpointer data);
+
+gboolean
+icon_press_cb (GtkStatusIcon *icon, GdkEventButton *event, gpointer data);
+
+gboolean
+icon_query_tooltip_cb (GtkWidget *icon, gint x, gint y, gboolean keyboard_mode,
+                       GtkTooltip *tooltip, gpointer data);
+
+#endif /* _GUI_H */
 void free_package (kalu_package_t *package);
 void free_watched_package (watched_package_t *w_pkg);
 
+#ifndef DISABLE_GUI
 gboolean reload_watched (gboolean is_aur, GError **error);
 
 void set_kalpm_busy (gboolean busy);
 
 void add_open_window (gpointer window);
 void remove_open_window (gpointer window);
+#endif /* DISABLE_GUI */
+void kalu_check_work (gboolean is_auto);
 
 #endif /* _KALU_H */
 
 =over
 
-=item B<-h, --help>
+=item B<-a, --auto-checks>
 
-Show a little help text and exit
+Run automatic checks (no GUI, see L<B<NOTES>|/NOTES> below)
+
+=item B<-m, --manual-checks>
+
+Run manual checks (no GUI, see L<B<NOTES>|/NOTES> below)
+
+=item B<-d, --debug>
+
+Enable debug mode. Debugging messages will then be sent to kalu's stdout,
+prefixed with a timestamp.
 
 =item B<-V, --version>
 
 Show version information and exit
 
-=item B<-d, --debug>
+=item B<-h, --help>
 
-Enable debug mode. Debugging messages will then be sent to kalu's stdout,
-prefixed with a timestamp.
+Show a little help text and exit
 
 =back
 
 specifically all those coming from libalpm directly, such as warnings, errors
 or scriptlet output.
 
+=head1 NOTES
+
+Command-line options B<--auto-checks> and B<--manual-checks> both work without
+any need for GUI. This means any and all output will be show on stdout/stderr,
+and there is no need for a DISPLAY to be available.
+
+In other words, you can run kalu using those options from a tty or through SSH,
+and it will work fine. You can also use kalu in a script that way.
+
+Note that GTK+ and other dependencies are obviously still required, although
+there is a I<configure> option available to disable all GUI completely during
+compilation, in order to produce a CLI-version of kalu.
+
 =head1 BUGS
 
 They're probably crawling somewhere in there... if you happen to catch one,
 #include <string.h>
 #include <time.h> /* for debug() */
 
-/* gtk */
-#include <gtk/gtk.h>
-
 /* alpm */
 #include <alpm.h>
 #include <alpm_list.h>
 
-/* notify */
-#include <libnotify/notify.h>
-
 /* curl */
 #include <curl/curl.h>
 
 /* kalu */
 #include "kalu.h"
+#ifndef DISABLE_GUI
+#include "gui.h"
+#include "util-gtk.h"
+#endif
 #include "kalu-alpm.h"
 #include "conf.h"
 #include "util.h"
-#include "util-gtk.h"
-#include "watched.h"
 #include "arch_linux.h"
-#ifndef DISABLE_UPDATER
-#include "kalu-updater.h"
-#include "updater.h"
-#endif
 #include "aur.h"
 #include "news.h"
-#include "preferences.h"
 
 
 /* global variable */
 config_t *config = NULL;
 
-#ifndef DISABLE_UPDATER
-#define run_updater()   do {                \
-        set_kalpm_busy (TRUE);              \
-        updater_run (config->pacmanconf,    \
-                     config->cmdline_post); \
-    } while (0)
-#endif
-
-static void action_upgrade (NotifyNotification *notification, const char *action, gpointer data);
-static void action_watched (NotifyNotification *notification, char *action, alpm_list_t *packages);
 static void notify_updates (alpm_list_t *packages, check_t type, gchar *xml_news);
-static void kalu_check (gboolean is_auto);
-static void kalu_auto_check (void);
-static inline gboolean is_pacman_conflicting (alpm_list_t *packages);
-static void menu_check_cb (GtkMenuItem *item, gpointer data);
-static void menu_quit_cb (GtkMenuItem *item, gpointer data);
-static void icon_popup_cb (GtkStatusIcon *icon, guint button, guint activate_time, gpointer data);
 static void free_config (void);
 
-static kalpm_state_t kalpm_state = { 0, 0, 0, NULL, 0, 0, 0, 0, 0, 0 };
+#ifdef DISABLE_GUI
 
-static gboolean
-show_error_cmdline (gchar *arg[])
-{
-    GtkWidget *dialog;
-    
-    dialog = gtk_message_dialog_new (NULL,
-                                     GTK_DIALOG_DESTROY_WITH_PARENT,
-                                     GTK_MESSAGE_ERROR,
-                                     GTK_BUTTONS_OK,
-                                     "%s",
-                                     "Unable to start process");
-    gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
-            "Error while trying to run command line: %s\n\nThe error was: <b>%s</b>",
-            arg[0], arg[1]);
-    gtk_window_set_title (GTK_WINDOW (dialog), "kalu: Unable to start process");
-    gtk_window_set_decorated (GTK_WINDOW (dialog), FALSE);
-    gtk_window_set_skip_taskbar_hint (GTK_WINDOW (dialog), FALSE);
-    gtk_window_set_skip_pager_hint (GTK_WINDOW (dialog), FALSE);
-    gtk_dialog_run (GTK_DIALOG (dialog));
-    gtk_widget_destroy (dialog);
-    free (arg[0]);
-    free (arg[1]);
-    free (arg);
-    return FALSE;
-}
+#define do_notify_error(summary, text)  do {    \
+        fprintf (stderr, "%s\n", summary);      \
+        if (text)                               \
+        {                                       \
+            fprintf (stderr, "%s\n", text);     \
+        }                                       \
+    } while (0)
 
-static void
-run_cmdline (const char *cmdline)
-{
-    GError *error = NULL;
-    
-    set_kalpm_busy (TRUE);
-    if (!g_spawn_command_line_sync (cmdline, NULL, NULL, NULL, &error))
-    {
-        /* we can't just show the error message from here, because this is ran
-         * from another thread, hence no gtk_* functions can be used. */
-        char **arg;
-        arg = malloc (2 * sizeof (char *));
-        arg[0] = strdup (cmdline);
-        arg[1] = strdup (error->message);
-        g_main_context_invoke (NULL, (GSourceFunc) show_error_cmdline, (gpointer) arg);
+#define do_show_error(message, submessage, parent)  do {    \
+        fprintf (stderr, "%s\n", message);                  \
+        if (submessage)                                     \
+        {                                                   \
+            fprintf (stderr, "%s\n", submessage);           \
+        }                                                   \
+    } while (0)
+
+#else
+
+kalpm_state_t kalpm_state = { 0, 0, 0, NULL, 0, 0, 0, 0, 0, 0 };
+static gboolean is_cli = FALSE;
+
+#define do_notify_error(summary, text)  if (!is_cli)    \
+    {                                                   \
+        notify_error (summary, text);                   \
+    }                                                   \
+    else                                                \
+    {                                                   \
+        fprintf (stderr, "%s\n", summary);              \
+        if (text)                                       \
+        {                                               \
+            fprintf (stderr, "%s\n", text);             \
+        }                                               \
     }
-    set_kalpm_busy (FALSE);
-    if (G_UNLIKELY (error))
-    {
-        g_clear_error (&error);
+
+#define do_show_error(message, submessage, parent)  if (!is_cli)    \
+    {                                                               \
+        show_error (message, submessage, parent);                   \
+    }                                                               \
+    else                                                            \
+    {                                                               \
+        fprintf (stderr, "%s\n", message);                          \
+        if (submessage)                                             \
+        {                                                           \
+            fprintf (stderr, "%s\n", submessage);                   \
+        }                                                           \
     }
-    else
-    {
-        /* check again, to refresh the state (since an upgrade was probably just done) */
-        kalu_check (TRUE);
-    }
-}
-
-static void
-action_upgrade (NotifyNotification *notification, const char *action, gpointer data _UNUSED_)
-{
-    char *cmdline = NULL;
-    
-    notify_notification_close (notification, NULL);
-    
-    if (strcmp ("do_updates", action) == 0)
-    {
-        #ifndef DISABLE_UPDATER
-        if (config->action == UPGRADE_ACTION_KALU)
-        {
-            run_updater ();
-        }
-        else /* if (config->action == UPGRADE_ACTION_CMDLINE) */
-        {
-        #endif
-            cmdline = config->cmdline;
-        #ifndef DISABLE_UPDATER
-        }
-        #endif
-    }
-    else /* if (strcmp ("do_updates_aur", action) == 0) */
-    {
-        cmdline = config->cmdline_aur;
-    }
-    
-    if (cmdline)
-    {
-        /* run in a separate thread, to not block/make GUI unresponsive */
-        g_thread_create ((GThreadFunc) run_cmdline, (gpointer) cmdline, FALSE, NULL);
-    }
-}
-
-static void
-action_watched (NotifyNotification *notification, char *action _UNUSED_,
-    alpm_list_t *packages)
-{
-    notify_notification_close (notification, NULL);
-    watched_update (packages, FALSE);
-}
-
-static void
-action_watched_aur (NotifyNotification *notification, char *action _UNUSED_,
-    alpm_list_t *packages)
-{
-    notify_notification_close (notification, NULL);
-    watched_update (packages, TRUE);
-}
-
-static void
-action_news (NotifyNotification *notification, char *action _UNUSED_,
-    gchar *xml_news)
-{
-    GError *error = NULL;
-    
-    notify_notification_close (notification, NULL);
-    set_kalpm_busy (TRUE);
-    if (!news_show (xml_news, TRUE, &error))
-    {
-        show_error ("Unable to show the news", error->message, NULL);
-        g_clear_error (&error);
-    }
-}
 
 static void
 free_packages (alpm_list_t *packages)
     FREE_PACKAGE_LIST (packages);
 }
 
-static void
-notification_closed_cb (NotifyNotification *notification, gpointer data _UNUSED_)
-{
-    g_object_unref (notification);
-}
+#endif /* DISABLE_GUI*/
 
 static void
 notify_updates (alpm_list_t *packages, check_t type, gchar *xml_news)
         free (replacements[3]);
     }
     
+    #ifndef DISABLE_GUI
+    if (is_cli)
+    {
+    #endif
+        puts (summary);
+        puts (text);
+        free (summary);
+        free (text);
+        return;
+    #ifndef DISABLE_GUI
+    }
+    
     NotifyNotification *notification;
     
     notification = new_notification (summary, text);
     notify_notification_show (notification, NULL);
     free (summary);
     free (text);
+    #endif /* DISABLE_GUI */
 }
 
-static inline gboolean
-is_pacman_conflicting (alpm_list_t *packages)
-{
-    gboolean ret = FALSE;
-    alpm_list_t *i;
-    kalu_package_t *pkg;
-    char *s, *ss, *old, *new, *so, *sn;
-    
-    for (i = packages; i; i = alpm_list_next (i))
-    {
-        pkg = i->data;
-        if (strcmp ("pacman", pkg->name) == 0)
-        {
-            /* because we'll mess with it */
-            old = strdup (pkg->old_version);
-            /* locate begining of (major) version number (might have epoch: before) */
-            s = strchr (old, ':');
-            if (s)
-            {
-                so = s + 1;
-            }
-            else
-            {
-                so = old;
-            }
-            
-            s = strrchr (so, '-');
-            if (!s)
-            {
-                /* should not be possible */
-                free (old);
-                break;
-            }
-            *s = '.';
-            
-            /* because we'll mess with it */
-            new = strdup (pkg->new_version);
-            /* locate begining of (major) version number (might have epoch: before) */
-            s = strchr (new, ':');
-            if (s)
-            {
-                sn = s + 1;
-            }
-            else
-            {
-                sn = new;
-            }
-            
-            s = strrchr (sn, '-');
-            if (!s)
-            {
-                /* should not be possible */
-                free (old);
-                free (new);
-                break;
-            }
-            *s = '.';
-            
-            int nb = 0; /* to know which part (major/minor) we're dealing with */
-            while ((s = strchr (so, '.')) && (ss = strchr (sn, '.')))
-            {
-                *s = '\0';
-                *ss = '\0';
-                ++nb;
-                
-                /* if major or minor goes up, API changes is likely and kalu's
-                 * dependency will kick in */
-                if (atoi (sn) > atoi (so))
-                {
-                    ret = TRUE;
-                    break;
-                }
-                
-                /* if nb is 2 this was the minor number, past this we don't care */
-                if (nb == 2)
-                {
-                    break;
-                }
-                so = s + 1;
-                sn = ss + 1;
-            }
-            
-            free (old);
-            free (new);
-            break;
-        }
-    }
-    
-    return ret;
-}
-
-static void
+void
 kalu_check_work (gboolean is_auto)
 {
     GError *error = NULL;
         }
         else if (error != NULL)
         {
-            notify_error ("Unable to check the news", error->message);
+            do_notify_error ("Unable to check the news", error->message);
             g_clear_error (&error);
         }
     }
     {
         if (!kalu_alpm_load (config->pacmanconf, &error))
         {
-            notify_error ("Unable to check for updates -- loading alpm library failed",
-                error->message);
+            do_notify_error ("Unable to check for updates -- loading alpm library failed",
+                             error->message);
             g_clear_error (&error);
+            #ifndef DISABLE_GUI
             set_kalpm_busy (FALSE);
+            #endif
             return;
         }
         
         if (checks & (CHECK_UPGRADES | CHECK_WATCHED)
             && !kalu_alpm_syncdbs (&nb_syncdbs, &error))
         {
-            notify_error ("Unable to check for updates -- could not synchronize databases",
-                error->message);
+            do_notify_error ("Unable to check for updates -- could not synchronize databases",
+                             error->message);
             g_clear_error (&error);
             kalu_alpm_free ();
+            #ifndef DISABLE_GUI
             set_kalpm_busy (FALSE);
+            #endif
             return;
         }
         
                 /* means the error is likely to come from a dependency issue/conflict */
                 if (error->code == 2)
                 {
-                    /* we do the notification (instead of calling notify_error) because
-                     * we need to add the "Update system" button/action. */
-                    NotifyNotification *notification;
-                    
-                    notification = new_notification (
-                        "Unable to compile list of packages",
-                        error->message);
-                    if (config->action != UPGRADE_NO_ACTION)
+                    #ifndef DISABLE_GUI
+                    if (!is_cli)
                     {
-                        notify_notification_add_action (notification, "do_updates",
-                            "Update system...", (NotifyActionCallback) action_upgrade,
-                            NULL, NULL);
+                        /* we do the notification (instead of calling notify_error) because
+                         * we need to add the "Update system" button/action. */
+                        NotifyNotification *notification;
+                        
+                        notification = new_notification (
+                            "Unable to compile list of packages",
+                            error->message);
+                        if (config->action != UPGRADE_NO_ACTION)
+                        {
+                            notify_notification_add_action (notification, "do_updates",
+                                "Update system...", (NotifyActionCallback) action_upgrade,
+                                NULL, NULL);
+                        }
+                        /* we use a callback on "closed" to unref it, because when there's an action
+                         * we need to keep a ref, otherwise said action won't work */
+                        g_signal_connect (G_OBJECT (notification), "closed",
+                                          G_CALLBACK (notification_closed_cb), NULL);
+                        notify_notification_show (notification, NULL);
                     }
-                    /* we use a callback on "closed" to unref it, because when there's an action
-                     * we need to keep a ref, otherwise said action won't work */
-                    g_signal_connect (G_OBJECT (notification), "closed",
-                                      G_CALLBACK (notification_closed_cb), NULL);
-                    notify_notification_show (notification, NULL);
+                    else
+                    {
+                    #endif
+                        do_notify_error ("Unable to compile list of packages",
+                                         error->message);
+                    #ifndef DISABLE_GUI
+                    }
+                    #endif
                 }
                 else
                 {
-                    notify_error ("Unable to check for updates", error->message);
+                    do_notify_error ("Unable to check for updates", error->message);
                 }
                 g_clear_error (&error);
             }
             else
             {
                 got_something = TRUE;
-                notify_error ("Unable to check for updates of watched packages",
-                              error->message);
+                do_notify_error ("Unable to check for updates of watched packages",
+                                 error->message);
                 g_clear_error (&error);
             }
         }
                 else
                 {
                     got_something = TRUE;
-                    notify_error ("Unable to check for AUR packages", error->message);
+                    do_notify_error ("Unable to check for AUR packages", error->message);
                     g_clear_error (&error);
                 }
                 alpm_list_free (aur_pkgs);
             else
             {
                 got_something = TRUE;
-                notify_error ("Unable to check for AUR packages", error->message);
+                do_notify_error ("Unable to check for AUR packages", error->message);
                 g_clear_error (&error);
             }
         }
         else
         {
             got_something = TRUE;
-            notify_error ("Unable to check for updates of watched AUR packages",
-                          error->message);
+            do_notify_error ("Unable to check for updates of watched AUR packages",
+                             error->message);
             g_clear_error (&error);
         }
     }
     
     if (!is_auto && !got_something)
     {
-        notify_error ("No upgrades available.", NULL);
+        do_notify_error ("No upgrades available.", NULL);
+    }
+    
+    #ifndef DISABLE_GUI
+    if (is_cli)
+    {
+        return;
     }
     
     /* update state */
         set_kalpm_nb (CHECK_WATCHED_AUR, nb_watched_aur);
     }
     set_kalpm_busy (FALSE);
-}
-
-static inline void
-kalu_check (gboolean is_auto)
-{
-    /* in case e.g. the menu was shown (sensitive) before an auto-check started */
-    if (kalpm_state.is_busy)
-    {
-        return;
-    }
-    set_kalpm_busy (TRUE);
-    
-    /* run in a separate thread, to not block/make GUI unresponsive */
-    g_thread_create ((GThreadFunc) kalu_check_work, GINT_TO_POINTER (is_auto), FALSE, NULL);
-}
-
-static void
-kalu_auto_check (void)
-{
-    kalu_check (TRUE);
-}
-
-static void
-menu_check_cb (GtkMenuItem *item _UNUSED_, gpointer data _UNUSED_)
-{
-    kalu_check (FALSE);
-}
-
-static void
-menu_quit_cb (GtkMenuItem *item _UNUSED_, gpointer data _UNUSED_)
-{
-    /* in case e.g. the menu was shown (sensitive) before an auto-check started */
-    if (kalpm_state.is_busy)
-    {
-        return;
-    }
-    gtk_main_quit ();
-}
-
-static void
-menu_manage_cb (GtkMenuItem *item _UNUSED_, gboolean is_aur)
-{
-    watched_manage (is_aur);
-}
-
-static inline void
-kalu_sysupgrade (void)
-{
-    /* in case e.g. the menu was shown (sensitive) before an auto-check started */
-    if (kalpm_state.is_busy || config->action == UPGRADE_NO_ACTION)
-    {
-        return;
-    }
-    
-    #ifndef DISABLE_UPDATER
-    if (config->action == UPGRADE_ACTION_KALU)
-    {
-        run_updater ();
-    }
-    else /* if (config->action == UPGRADE_ACTION_CMDLINE) */
-    {
-    #endif
-        /* run in a separate thread, to not block/make GUI unresponsive */
-        g_thread_create ((GThreadFunc) run_cmdline, (gpointer) config->cmdline, FALSE, NULL);
-    #ifndef DISABLE_UPDATER
-    }
     #endif
 }
 
 static void
-menu_news_cb (GtkMenuItem *item _UNUSED_, gpointer data _UNUSED_)
-{
-    GError *error = NULL;
-    /* in case e.g. the menu was shown (sensitive) before an auto-check started */
-    if (kalpm_state.is_busy)
-    {
-        return;
-    }
-    set_kalpm_busy (TRUE);
-    
-    if (!news_show (NULL, FALSE, &error))
-    {
-        show_error ("Unable to show the recent Arch Linxu news", error->message, NULL);
-        g_clear_error (&error);
-    }
-}
-
-static void
-menu_help_cb (GtkMenuItem *item _UNUSED_, gpointer data _UNUSED_)
-{
-    GError *error = NULL;
-    
-    if (!show_help (&error))
-    {
-        show_error ("Unable to show help", error->message, NULL);
-        g_clear_error (&error);
-    }
-}
-
-static void
-menu_history_cb (GtkMenuItem *item _UNUSED_, gpointer data _UNUSED_)
-{
-    GError *error = NULL;
-    
-    if (!show_history (&error))
-    {
-        show_error ("Unable to show change log", error->message, NULL);
-        g_clear_error (&error);
-    }
-}
-
-static void
-menu_prefs_cb (GtkMenuItem *item _UNUSED_, gpointer data _UNUSED_)
-{
-    show_prefs ();
-}
-
-static void
-menu_about_cb (GtkMenuItem *item _UNUSED_, gpointer data _UNUSED_)
-{
-    GtkAboutDialog *about;
-    GdkPixbuf *pixbuf;
-    const char *authors[] = {"Olivier Brunel", "Dave Gamble", "Pacman Development Team", NULL};
-    const char *artists[] = {"Painless Rob", NULL};
-    
-    about = GTK_ABOUT_DIALOG (gtk_about_dialog_new ());
-    pixbuf = gtk_widget_render_icon_pixbuf (GTK_WIDGET (about), "kalu-logo",
-                                            GTK_ICON_SIZE_DIALOG);
-    gtk_window_set_icon (GTK_WINDOW (about), pixbuf);
-    gtk_about_dialog_set_logo (about, pixbuf);
-    g_object_unref (G_OBJECT (pixbuf));
-    
-    gtk_about_dialog_set_program_name (about, PACKAGE_NAME);
-    gtk_about_dialog_set_version (about, PACKAGE_VERSION);
-    gtk_about_dialog_set_comments (about, PACKAGE_TAG);
-    gtk_about_dialog_set_website (about, "https://bitbucket.org/jjacky/kalu");
-    gtk_about_dialog_set_website_label (about, "https://bitbucket.org/jjacky/kalu");
-    gtk_about_dialog_set_copyright (about, "Copyright (C) 2012 Olivier Brunel");
-    gtk_about_dialog_set_license_type (about, GTK_LICENSE_GPL_3_0);
-    gtk_about_dialog_set_authors (about, authors);
-    gtk_about_dialog_set_artists (about, artists);
-    
-    gtk_dialog_run (GTK_DIALOG (about));
-    gtk_widget_destroy (GTK_WIDGET (about));
-}
-
-static gboolean
-menu_unmap_cb (GtkWidget *menu, GdkEvent *event _UNUSED_, gpointer data _UNUSED_)
-{
-    gtk_widget_destroy (menu);
-    g_object_unref (menu);
-    return TRUE;
-}
-
-static void
-icon_popup_cb (GtkStatusIcon *_icon _UNUSED_, guint button, guint activate_time,
-               gpointer data _UNUSED_)
-{
-    GtkWidget   *menu;
-    GtkWidget   *item;
-    GtkWidget   *image;
-    guint        pos = 0;
-    
-    menu = gtk_menu_new();
-    
-    item = gtk_image_menu_item_new_with_label ("Check for Upgrades...");
-    gtk_widget_set_sensitive (item, !kalpm_state.is_busy);
-    image = gtk_image_new_from_stock ("kalu-logo", GTK_ICON_SIZE_MENU);
-    gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
-    gtk_widget_set_tooltip_text (item, "Check if there are any upgrades available");
-    g_signal_connect (G_OBJECT (item), "activate",
-                      G_CALLBACK (menu_check_cb), NULL);
-    gtk_widget_show (item);
-    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
-    
-    if (config->action != UPGRADE_NO_ACTION)
-    {
-        item = gtk_image_menu_item_new_with_label ("System upgrade...");
-        gtk_widget_set_sensitive (item, !kalpm_state.is_busy);
-        image = gtk_image_new_from_stock ("kalu-logo", GTK_ICON_SIZE_MENU);
-        gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
-        gtk_widget_set_tooltip_text (item, "Perform a system upgrade");
-        g_signal_connect (G_OBJECT (item), "activate",
-                          G_CALLBACK (kalu_sysupgrade), NULL);
-        gtk_widget_show (item);
-        gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
-    }
-    
-    item = gtk_image_menu_item_new_with_label ("Show recent Arch Linux news...");
-    gtk_widget_set_sensitive (item, !kalpm_state.is_busy);
-    image = gtk_image_new_from_stock ("kalu-logo", GTK_ICON_SIZE_MENU);
-    gtk_image_menu_item_set_image (GTK_IMAGE_MENU_ITEM (item), image);
-    gtk_widget_set_tooltip_text (item, "Show 10 most recent Arch Linux news");
-    g_signal_connect (G_OBJECT (item), "activate",
-                      G_CALLBACK (menu_news_cb), NULL);
-    gtk_widget_show (item);
-    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
-    
-    item = gtk_separator_menu_item_new ();
-    gtk_widget_show (item);
-    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
-    
-    item = gtk_image_menu_item_new_with_label ("Manage watched packages...");
-    g_signal_connect (G_OBJECT (item), "activate",
-                      G_CALLBACK (menu_manage_cb), (gpointer) FALSE);
-    gtk_widget_show (item);
-    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
-    
-    item = gtk_image_menu_item_new_with_label ("Manage watched AUR packages...");
-    g_signal_connect (G_OBJECT (item), "activate",
-                      G_CALLBACK (menu_manage_cb), (gpointer) TRUE);
-    gtk_widget_show (item);
-    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
-    
-    item = gtk_separator_menu_item_new ();
-    gtk_widget_show (item);
-    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
-    
-    item = gtk_image_menu_item_new_from_stock (GTK_STOCK_PREFERENCES, NULL);
-    gtk_widget_set_tooltip_text (item, "Edit preferences");
-    g_signal_connect (G_OBJECT (item), "activate",
-                      G_CALLBACK (menu_prefs_cb), NULL);
-    gtk_widget_show (item);
-    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
-    
-    item = gtk_separator_menu_item_new ();
-    gtk_widget_show (item);
-    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
-    
-    item = gtk_image_menu_item_new_from_stock (GTK_STOCK_HELP, NULL);
-    gtk_widget_set_tooltip_text (item, "Show help (man page)");
-    g_signal_connect (G_OBJECT (item), "activate",
-                      G_CALLBACK (menu_help_cb), NULL);
-    gtk_widget_show (item);
-    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
-    
-    item = gtk_image_menu_item_new_with_label ("Change log");
-    gtk_widget_set_tooltip_text (item, "Show change log");
-    g_signal_connect (G_OBJECT (item), "activate",
-                      G_CALLBACK (menu_history_cb), (gpointer) TRUE);
-    gtk_widget_show (item);
-    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
-    
-    item = gtk_image_menu_item_new_from_stock (GTK_STOCK_ABOUT, NULL);
-    gtk_widget_set_tooltip_text (item, "Show Copyright & version information");
-    g_signal_connect (G_OBJECT (item), "activate",
-                      G_CALLBACK (menu_about_cb), NULL);
-    gtk_widget_show (item);
-    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
-    
-    item = gtk_separator_menu_item_new ();
-    gtk_widget_show (item);
-    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
-    
-    item = gtk_image_menu_item_new_from_stock (GTK_STOCK_QUIT, NULL);
-    gtk_widget_set_sensitive (item, !kalpm_state.is_busy);
-    gtk_widget_set_tooltip_text (item, "Exit kalu");
-    g_signal_connect (G_OBJECT (item), "activate",
-                      G_CALLBACK (menu_quit_cb), NULL);
-    gtk_widget_show (item);
-    gtk_menu_attach (GTK_MENU (menu), item, 0, 1, pos, pos + 1); ++pos;
-    
-    /* since we don't pack the menu anywhere, we need to "take ownership" of it,
-     * and we'll destroy it when done, i.e. when it's unmapped */
-    g_object_ref_sink (menu);
-    gtk_widget_add_events (menu, GDK_STRUCTURE_MASK);
-    g_signal_connect (G_OBJECT (menu), "unmap-event",
-                      G_CALLBACK (menu_unmap_cb), NULL);
-    
-    gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, button, activate_time);
-}
-
-static GPtrArray *open_windows = NULL;
-static gboolean has_hidden_windows = FALSE;
-
-void
-add_open_window (gpointer window)
-{
-    if (!open_windows)
-    {
-        open_windows = g_ptr_array_new ();
-    }
-    g_ptr_array_add (open_windows, window);
-}
-
-void
-remove_open_window (gpointer window)
-{
-    g_ptr_array_remove (open_windows, window);
-}
-
-static inline void
-toggle_open_windows (void)
-{
-    if (!open_windows || open_windows->len == 0)
-    {
-        return;
-    }
-    
-    g_ptr_array_foreach (open_windows,
-                         (GFunc) ((has_hidden_windows) ? gtk_widget_show : gtk_widget_hide),
-                         NULL);
-    has_hidden_windows = !has_hidden_windows;
-}
-
-static guint icon_press_timeout = 0;
-
-#define process_click_action(on_click)  do {        \
-        if (on_click == DO_SYSUPGRADE)              \
-        {                                           \
-            kalu_sysupgrade ();                     \
-        }                                           \
-        else if (on_click == DO_CHECK)              \
-        {                                           \
-            kalu_check (FALSE);                     \
-        }                                           \
-        else if (on_click == DO_TOGGLE_WINDOWS)     \
-        {                                           \
-            toggle_open_windows ();                 \
-        }                                           \
-    } while (0)
-
-static gboolean
-icon_press_click (gpointer data _UNUSED_)
-{
-    icon_press_timeout = 0;
-    
-    process_click_action (config->on_sgl_click);
-    
-    return FALSE;
-}
-
-static gboolean
-icon_press_cb (GtkStatusIcon *icon _UNUSED_, GdkEventButton *event, gpointer data _UNUSED_)
-{
-    /* left button? */
-    if (event->button == 1)
-    {
-        if (event->type == GDK_2BUTTON_PRESS)
-        {
-            /* we probably had a timeout set for the click, remove it */
-            if (icon_press_timeout > 0)
-            {
-                g_source_remove (icon_press_timeout);
-                icon_press_timeout = 0;
-            }
-            
-            process_click_action (config->on_dbl_click);
-        }
-        else if (event->type == GDK_BUTTON_PRESS)
-        {
-            /* As per GTK manual: on a dbl-click, we get GDK_BUTTON_PRESS twice
-             * and then a GDK_2BUTTON_PRESS. Obviously, we want then to ignore
-             * the two GDK_BUTTON_PRESS.
-             * Also per manual, for a double click to occur, the second button
-             * press must occur within 1/4 of a second of the first; so:
-             * - on GDK_BUTTON_PRESS we set a timeout, in 250ms
-             *  - if it expires/gets triggered, it was a click
-             *  - if another click happens within that time, the timeout will be
-             *    removed (see GDK_2BUTTON_PRESS above) and the clicks ignored
-             * - if a GDK_BUTTON_PRESS occurs while a timeout is set, it's a
-             * second click and ca be ignored, GDK_2BUTTON_PRESS will handle it */
-            if (icon_press_timeout == 0)
-            {
-                icon_press_timeout = g_timeout_add (250, icon_press_click, NULL);
-            }
-        }
-    }
-    
-    return FALSE;
-}
-
-#undef process_click_action
-
-#define addstr(...)     do {                            \
-        len = snprintf (s, (size_t) max, __VA_ARGS__);  \
-        max -= len;                                     \
-        s += len;                                       \
-    } while (0)
-static gboolean
-icon_query_tooltip_cb (GtkWidget *icon _UNUSED_, gint x _UNUSED_, gint y _UNUSED_,
-                       gboolean keyboard_mode _UNUSED_, GtkTooltip *tooltip,
-                       gpointer data _UNUSED_)
-{
-    GDateTime *current;
-    GTimeSpan timespan;
-    gint nb;
-    gchar buf[420], *s = buf;
-    gint max = 420, len;
-    
-    addstr ("[kalu%s]", (has_hidden_windows) ? " +" : "");
-    
-    if (kalpm_state.is_busy)
-    {
-        addstr (" Checking/updating in progress...");
-        gtk_tooltip_set_text (tooltip, buf);
-        return TRUE;
-    }
-    else if (kalpm_state.last_check == NULL)
-    {
-        gtk_tooltip_set_text (tooltip, buf);
-        return TRUE;
-    }
-    
-    addstr (" Last checked ");
-    
-    current = g_date_time_new_now_local ();
-    timespan = g_date_time_difference (current, kalpm_state.last_check);
-    g_date_time_unref (current);
-    
-    if (timespan < G_TIME_SPAN_MINUTE)
-    {
-        addstr ("just now");
-    }
-    else
-    {
-        if (timespan >= G_TIME_SPAN_DAY)
-        {
-            nb = (gint) (timespan / G_TIME_SPAN_DAY);
-            timespan -= (nb * G_TIME_SPAN_DAY);
-            if (nb > 1)
-            {
-                addstr ("%d days ", nb);
-            }
-            else
-            {
-                addstr ("1 day ");
-            }
-        }
-        if (timespan >= G_TIME_SPAN_HOUR)
-        {
-            nb = (gint) (timespan / G_TIME_SPAN_HOUR);
-            timespan -= (nb * G_TIME_SPAN_HOUR);
-            if (nb > 1)
-            {
-                addstr ("%d hours ", nb);
-            }
-            else
-            {
-                addstr ("1 hour ");
-            }
-        }
-        if (timespan >= G_TIME_SPAN_MINUTE)
-        {
-            nb = (gint) (timespan / G_TIME_SPAN_MINUTE);
-            timespan -= (nb * G_TIME_SPAN_MINUTE);
-            if (nb > 1)
-            {
-                addstr ("%d minutes ", nb);
-            }
-            else
-            {
-                addstr ("1 minute ");
-            }
-        }
-        
-        addstr ("ago");
-    }
-    
-    if (config->syncdbs_in_tooltip && kalpm_state.nb_syncdbs > 0)
-    {
-        addstr ("\nsync possible for %d dbs", kalpm_state.nb_syncdbs);
-    }
-    
-    if (kalpm_state.nb_news > 0)
-    {
-        addstr ("\n%d unread news", kalpm_state.nb_news);
-    }
-    if (kalpm_state.nb_upgrades > 0)
-    {
-        addstr ("\n%d upgrades available", kalpm_state.nb_upgrades);
-    }
-    if (kalpm_state.nb_watched > 0)
-    {
-        addstr ("\n%d watched packages updated", kalpm_state.nb_watched);
-    }
-    if (kalpm_state.nb_aur > 0)
-    {
-        addstr ("\n%d AUR packages updated", kalpm_state.nb_aur);
-    }
-    if (kalpm_state.nb_watched_aur > 0)
-    {
-        addstr ("\n%d watched AUR packages updated", kalpm_state.nb_watched_aur);
-    }
-    
-    if (max <= 0)
-    {
-        sprintf (buf, "kalu: error setting tooltip");
-    }
-    gtk_tooltip_set_text (tooltip, buf);
-    return TRUE;
-}
-#undef addstr
-
-static void
 free_config (void)
 {
     if (config == NULL)
     free (w_pkg);
 }
 
-gboolean
-reload_watched (gboolean is_aur, GError **error)
-{
-    GError *local_err = NULL;
-    gchar file[MAX_PATH];
-    conf_file_t conffile;
-    
-    if (is_aur)
-    {
-        /* clear */
-        FREE_WATCHED_PACKAGE_LIST (config->watched_aur);
-        /* load */
-        snprintf (file, MAX_PATH - 1, "%s/.config/kalu/watched-aur.conf", g_get_home_dir ());
-        conffile = CONF_FILE_WATCHED_AUR;
-    }
-    else
-    {
-        /* clear */
-        FREE_WATCHED_PACKAGE_LIST (config->watched);
-        /* load */
-        snprintf (file, MAX_PATH - 1, "%s/.config/kalu/watched.conf", g_get_home_dir ());
-        conffile = CONF_FILE_WATCHED;
-    }
-    
-    if (!parse_config_file (file, conffile, &local_err))
-    {
-        g_propagate_error (error, local_err);
-        return FALSE;
-    }
-    
-    return TRUE;
-}
-
-static GtkStatusIcon *icon = NULL;
-
-static gboolean
-set_status_icon (gboolean active)
-{
-    if (active)
-    {
-        gtk_status_icon_set_from_stock (icon, "kalu-logo");
-    }
-    else
-    {
-        gtk_status_icon_set_from_stock (icon, "kalu-logo-gray");
-    }
-    /* do NOT get called back */
-    return FALSE;
-}
-
-void
-set_kalpm_nb (check_t type, gint nb)
-{
-    if (type & CHECK_UPGRADES)
-    {
-        kalpm_state.nb_upgrades = nb;
-    }
-    
-    if (type & CHECK_WATCHED)
-    {
-        kalpm_state.nb_watched = nb;
-    }
-    
-    if (type & CHECK_AUR)
-    {
-        kalpm_state.nb_aur = nb;
-    }
-    
-    if (type & CHECK_WATCHED_AUR)
-    {
-        kalpm_state.nb_watched_aur = nb;
-    }
-    
-    if (type & CHECK_NEWS)
-    {
-        kalpm_state.nb_news = nb;
-    }
-    
-    /* thing is, this function can be called from another thread (e.g. from
-     * kalu_check_work, which runs in a separate thread not to block GUI...)
-     * but when that happens, we can't use gtk_* functions, i.e. we can't change
-     * the status icon. so, this will make sure the call to set_status_icon
-     * happens in the main thread */
-    gboolean active = (kalpm_state.nb_upgrades + kalpm_state.nb_watched
-                       + kalpm_state.nb_aur + kalpm_state.nb_watched_aur
-                       + kalpm_state.nb_news > 0);
-    g_main_context_invoke (NULL, (GSourceFunc) set_status_icon, GINT_TO_POINTER (active));
-}
-
-inline void
-set_kalpm_nb_syncdbs (gint nb)
-{
-    kalpm_state.nb_syncdbs = nb;
-}
-
-static gboolean
-switch_status_icon (void)
-{
-    static gboolean active = FALSE;
-    active = !active;
-    set_status_icon (active);
-    /* keep timeout alive */
-    return TRUE;
-}
-
-void
-set_kalpm_busy (gboolean busy)
-{
-    gint old = kalpm_state.is_busy;
-