AppImage version update notification
authorColin Clark <colin.clark@cclark.uk>
Mon, 5 Dec 2022 10:42:50 +0000 (10:42 +0000)
committerColin Clark <colin.clark@cclark.uk>
Mon, 5 Dec 2022 10:42:50 +0000 (10:42 +0000)
If Geeqie is being run as an AppImage and the version on the server is
newer than the one currently being run, a fade-out message is shown on
start-up.
This can be disabled via an option on Preferences/General.
An Internet connection is required.
The option is not displayed if not being run as an AppImage.

doc/docbook/GuideOptionsGeneral.xml
src/main.cc
src/main.h
src/options.cc
src/options.h
src/preferences.cc
src/rcfile.cc
src/ui-utildlg.cc
src/ui-utildlg.h

index 8764b7c..e43fd06 100644 (file)
       <para>Geeqie must be restarted for changes to take effect.</para>
     </note>
   </section>
+  <section id="AppImageUpdatesNotifications>
+    <title>AppImage updates notifications"</title>
+    <para>Show a notification on start-up if the server has a newer version than the current. Requires an Internet connection. Displayed only if an AppImage is being run.</para>
+  </section>
   <section id="PredefinedKeywordTree">
     <title>Show predefined keyword tree</title>
     <para>Deselecting this option will hide the list of predefined keywords on the right-hand side of the keywords pane of the info sidebar.</para>
index 62cb022..1c94866 100644 (file)
@@ -1469,6 +1469,12 @@ gint main(gint argc, gchar *argv[])
                set_theme_bg_color();
                }
 
+       /* Show a fade-out notification window if the server has a newer AppImage version */
+       if (options->appimage_notifications && g_getenv("APPDIR") && strstr(g_getenv("APPDIR"), "/tmp/.mount_Geeqie"))
+               {
+               appimage_notification();
+               }
+
        DEBUG_1("%s main: gtk_main", get_exec_time());
        gtk_main();
 
index dad07b8..1ade3dd 100644 (file)
 #include "debug.h"
 #include "options.h"
 
+#define APPIMAGE_VERSION_FILE "https://raw.githubusercontent.com/geeqie/geeqie.github.io/master/AppImage/appimages.txt"
 #define TIMEZONE_DATABASE_WEB "https://cdn.bertold.org/zonedetect/db/db.zip"
 #define TIMEZONE_DATABASE_FILE "timezone21.bin"
 #define TIMEZONE_DATABASE_VERSION "out_v1"
index b4020c6..24f1efc 100644 (file)
@@ -85,6 +85,7 @@ ConfOptions *init_options(ConfOptions *options)
        options->fullscreen.disable_saver = TRUE;
        options->fullscreen.screen = -1;
 
+       options->appimage_notifications = TRUE;
        options->marks_save = TRUE;
        options->with_rename = FALSE;
        options->collections_on_top = FALSE;
index 5fd5fc1..a4dae1d 100644 (file)
@@ -75,6 +75,8 @@ struct _ConfOptions
        gboolean marks_save;            /**< save marks on exit */
        gchar *marks_tooltips[FILEDATA_MARKS_SIZE];
 
+       gboolean appimage_notifications;
+
        gboolean with_rename;
        gboolean collections_on_top;
        gboolean hide_window_in_fullscreen;
index cf3275b..9fd6937 100644 (file)
@@ -353,6 +353,8 @@ static void config_window_apply(void)
 
        options->image.enable_read_ahead = c_options->image.enable_read_ahead;
 
+       options->appimage_notifications = c_options->appimage_notifications;
+
 
        if (options->image.use_custom_border_color != c_options->image.use_custom_border_color
            || options->image.use_custom_border_color_in_fullscreen != c_options->image.use_custom_border_color_in_fullscreen
@@ -2148,6 +2150,17 @@ static void config_tab_general(GtkWidget *notebook)
 
        pref_spacer(group, PREF_PAD_GROUP);
 
+       if (g_getenv("APPDIR") && strstr(g_getenv("APPDIR"), "/tmp/.mount_Geeqie"))
+               {
+               group = pref_group_new(vbox, FALSE, _("AppImage updates notifications"), GTK_ORIENTATION_VERTICAL);
+               hbox = pref_box_new(group, TRUE, GTK_ORIENTATION_HORIZONTAL, PREF_PAD_SPACE);
+               pref_checkbox_new_int(group, _("Enable"), options->appimage_notifications, &c_options->appimage_notifications);
+               gtk_widget_set_tooltip_text(group, _("Show a notification on start-up if the server has a newer version than the current. Requires an Internet connection"));
+
+               pref_spacer(group, PREF_PAD_GROUP);
+               }
+
+
        net_mon = g_network_monitor_get_default();
        tz_org = g_network_address_parse_uri(TIMEZONE_DATABASE_WEB, 80, NULL);
        if (tz_org)
index 1f0585d..1d8bf87 100644 (file)
@@ -347,6 +347,7 @@ static void write_global_attributes(GString *outstr, gint indent)
        WRITE_NL(); WRITE_UINT(*options, log_window_lines);
        WRITE_NL(); WRITE_BOOL(*options, log_window.timer_data);
 
+       WRITE_NL(); WRITE_BOOL(*options, appimage_notifications);
        WRITE_NL(); WRITE_BOOL(*options, marks_save);
        WRITE_NL(); WRITE_CHAR(*options, help_search_engine);
 
@@ -847,6 +848,7 @@ static gboolean load_global_params(const gchar **attribute_names, const gchar **
                if (READ_INT(*options, log_window_lines)) continue;
                if (READ_BOOL(*options, log_window.timer_data)) continue;
 
+               if (READ_BOOL(*options, appimage_notifications)) continue;
                if (READ_BOOL(*options, marks_save)) continue;
                if (READ_CHAR(*options, help_search_engine)) continue;
 
index cbef47e..f9461a4 100644 (file)
@@ -507,6 +507,199 @@ GenericDialog *warning_dialog(const gchar *heading, const gchar *text,
        return gd;
 }
 
+/*
+ *-----------------------------------------------------------------------------
+ * AppImage version update notification message with fade-out
+ *-----------------------------------------------------------------------------
+ * 
+ * It is expected that the version file on the server has the following format
+ * for the first two lines in these formats:
+ * 
+ * 1. x86_64 AppImage - e.g. Geeqie-v2.0+20221113-x86_64.AppImage
+ * 2. arm AppImage - e.g. Geeqie-v2.0+20221025-aarch64.AppImage
+ */
+
+typedef struct _AppImageData AppImageData;
+struct _AppImageData
+{
+       GFile *version_file;
+       GDataInputStream *data_input_stream;
+       GFileInputStream *file_input_stream;
+       GThreadPool *thread_pool;
+       GtkWidget *window;
+       guint id;
+};
+
+static gboolean appimage_notification_close_cb(gpointer data)
+{
+       AppImageData *appimage_data = static_cast<AppImageData *>(data);
+
+       if (appimage_data->window && gtk_window_get_opacity(GTK_WINDOW(appimage_data->window)) != 0)
+               {
+               g_source_remove(appimage_data->id);
+               }
+
+       if (appimage_data->window)
+               {
+               gtk_widget_destroy(appimage_data->window);
+               }
+
+       g_object_unref(appimage_data->data_input_stream);
+       g_object_unref(appimage_data->file_input_stream);
+       g_object_unref(appimage_data->version_file);
+       g_thread_pool_free(appimage_data->thread_pool, TRUE, TRUE);
+       g_free(appimage_data);
+
+       return FALSE;
+}
+
+static gboolean appimage_notification_fade_cb(gpointer data)
+{
+       AppImageData *appimage_data = static_cast<AppImageData *>(data);
+
+       gtk_window_set_opacity(GTK_WINDOW(appimage_data->window), (gtk_window_get_opacity(GTK_WINDOW(appimage_data->window)) - 0.02));
+
+       if (gtk_window_get_opacity(GTK_WINDOW(appimage_data->window)) == 0)
+               {
+               g_idle_add(appimage_notification_close_cb, data);
+
+               return FALSE;
+               }
+
+       return TRUE;
+}
+
+static gboolean user_close_cb(GtkWidget *UNUSED(widget), GdkEvent *UNUSED(event), gpointer data)
+{
+       AppImageData *appimage_data = static_cast<AppImageData *>(data);
+
+       g_idle_add(appimage_notification_close_cb, appimage_data);
+
+       return FALSE;
+}
+
+static void show_notification_message(gchar *version_string_from_file, AppImageData *appimage_data)
+{
+       GtkWidget *label;
+       gint server_version;
+       gint running_version;
+       gchar *version_string;
+
+       server_version = atoi(strtok(strstr(version_string_from_file, "+") + 1, "-") );
+       version_string = g_strdup(strstr(VERSION, "git"));
+       running_version = atoi(strtok(version_string + 3, "-") );
+       g_free(version_string);
+
+       if (server_version > running_version)
+               {
+               appimage_data->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+               label = gtk_label_new (_("A new Geeqie AppImage is available"));
+               gtk_widget_show(label);
+               gtk_container_add(GTK_CONTAINER(appimage_data->window), label);
+               gtk_window_set_decorated(GTK_WINDOW(appimage_data->window), FALSE);
+               gtk_widget_set_size_request(appimage_data->window, 300, 40);
+               gtk_window_set_gravity(GTK_WINDOW(appimage_data->window), GDK_GRAVITY_NORTH_EAST);
+               gtk_window_move(GTK_WINDOW(appimage_data->window), (gdk_screen_width() * 0.8), (gdk_screen_height() / 20));
+               gtk_window_set_skip_taskbar_hint(GTK_WINDOW(appimage_data->window), TRUE);
+               gtk_window_set_focus_on_map(GTK_WINDOW(appimage_data->window), FALSE);
+               g_signal_connect(appimage_data->window, "focus-in-event", G_CALLBACK(user_close_cb), appimage_data);
+               appimage_data->id = g_timeout_add(100, appimage_notification_fade_cb, appimage_data);
+               gtk_widget_show((appimage_data->window));
+               }
+       else
+               {
+               g_idle_add(appimage_notification_close_cb, appimage_data);
+               }
+}
+
+#ifdef __arm__
+static void appimage_data_arm_read_line_async_ready_cb(GObject *source_object, GAsyncResult *res, gpointer data)
+{
+       AppImageData *appimage_data = static_cast<AppImageData *>(data);
+       gsize length;
+       gchar *result;
+
+       result = g_data_input_stream_read_line_finish(appimage_data->data_input_stream, res, &length, NULL);
+
+       if (result && strstr(result, "-aarch64.AppImage"))
+               {
+               show_notification_message(result, appimage_data);
+               }
+       else
+               {
+               g_idle_add(appimage_notification_close_cb, data);
+               }
+
+       g_free(result);
+}
+#endif
+
+static void appimage_data_x86_read_line_async_ready_cb(GObject *UNUSED(source_object), GAsyncResult *res, gpointer data)
+{
+       AppImageData *appimage_data = static_cast<AppImageData *>(data);
+       gsize length;
+       gchar *result;
+
+       result = g_data_input_stream_read_line_finish(appimage_data->data_input_stream, res, &length, NULL);
+
+#ifdef __x86_64__
+       if (result && strstr(result, "-x86_64.AppImage"))
+               {
+               show_notification_message(result, appimage_data);
+               }
+       else
+               {
+               g_idle_add(appimage_notification_close_cb, data);
+               }
+#endif
+
+#ifdef __arm__
+       g_data_input_stream_read_line_async(appimage_data->data_input_stream, G_PRIORITY_LOW, NULL, appimage_data_arm_read_line_async_ready_cb, appimage_data);
+#endif
+
+       g_free(result);
+}
+
+static void appimage_notification_func(gpointer data, gpointer UNUSED(user_data))
+{
+       AppImageData *appimage_data = static_cast<AppImageData *>(data);
+       GNetworkMonitor *net_mon;
+       GSocketConnectable *geeqie_github_io;
+       gboolean internet_available = FALSE;
+
+       net_mon = g_network_monitor_get_default();
+       geeqie_github_io = g_network_address_parse_uri(APPIMAGE_VERSION_FILE, 80, NULL);
+       if (geeqie_github_io)
+               {
+               internet_available = g_network_monitor_can_reach(net_mon, geeqie_github_io, NULL, NULL);
+               g_object_unref(geeqie_github_io);
+               }
+
+       if (internet_available)
+               {
+               appimage_data->version_file = g_file_new_for_uri(APPIMAGE_VERSION_FILE);
+               appimage_data->file_input_stream = g_file_read(appimage_data->version_file, NULL, NULL);
+               appimage_data->data_input_stream = g_data_input_stream_new(G_INPUT_STREAM(appimage_data->file_input_stream));
+
+               g_data_input_stream_read_line_async(appimage_data->data_input_stream, G_PRIORITY_LOW, NULL, appimage_data_x86_read_line_async_ready_cb, appimage_data);
+               }
+       else
+               {
+               g_thread_pool_free(appimage_data->thread_pool, TRUE, TRUE);
+               g_free(appimage_data);
+               }
+}
+
+void appimage_notification()
+{
+       AppImageData *appimage_data;
+
+       appimage_data = g_new0(AppImageData, 1);
+
+       appimage_data->thread_pool = g_thread_pool_new(appimage_notification_func, appimage_data, 1, FALSE, NULL);
+       g_thread_pool_push(appimage_data->thread_pool, appimage_data, NULL);
+}
+
 /*
  *-----------------------------------------------------------------------------
  * generic file ops dialog routines
index ec8c4b7..fd3fa3d 100644 (file)
@@ -106,5 +106,7 @@ void file_dialog_sync_history(FileDialog *fd, gboolean dir_only);
 
 void generic_dialog_windows_load_config(const gchar **window_attributes, const gchar **attribute_values);
 void generic_dialog_windows_write_config(GString *outstr, gint indent);
+
+void appimage_notification();
 #endif
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */