Documentation: Use G_SOURCE_CONTINUE and G_SOURCE_REMOVE
[geeqie.git] / src / ui-utildlg.cc
index cbef47e..fecf9f3 100644 (file)
@@ -58,7 +58,7 @@ static void generic_dialog_save_window(const gchar *title, const gchar *role, gi
        work = g_list_first(dialog_windows);
        while (work)
                {
-               DialogWindow *dw = work->data;
+               DialogWindow *dw = static_cast<DialogWindow *>(work->data);
                if (g_strcmp0(dw->title ,title) == 0 && g_strcmp0(dw->role, role) == 0)
                        {
                        dw->x = x;
@@ -88,7 +88,7 @@ static gboolean generic_dialog_find_window(const gchar *title, const gchar *role
        work = g_list_first(dialog_windows);
        while (work)
                {
-               DialogWindow *dw = work->data;
+               DialogWindow *dw = static_cast<DialogWindow *>(work->data);
 
                if (g_strcmp0(dw->title,title) == 0 && g_strcmp0(dw->role, role) == 0)
                        {
@@ -132,7 +132,7 @@ void generic_dialog_close(GenericDialog *gd)
 
 static void generic_dialog_click_cb(GtkWidget *widget, gpointer data)
 {
-       GenericDialog *gd = data;
+       GenericDialog *gd = static_cast<GenericDialog *>(data);
        void (*func)(GenericDialog *, gpointer);
        gboolean auto_close;
 
@@ -145,7 +145,7 @@ static void generic_dialog_click_cb(GtkWidget *widget, gpointer data)
 
 static gboolean generic_dialog_default_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
 {
-       GenericDialog *gd = data;
+       GenericDialog *gd = static_cast<GenericDialog *>(data);
 
        if (event->keyval == GDK_KEY_Return && gtk_widget_has_focus(widget)
            && gd->default_cb)
@@ -170,7 +170,7 @@ void generic_dialog_attach_default(GenericDialog *gd, GtkWidget *widget)
 
 static gboolean generic_dialog_key_press_cb(GtkWidget *widget, GdkEventKey *event, gpointer data)
 {
-       GenericDialog *gd = data;
+       GenericDialog *gd = static_cast<GenericDialog *>(data);
        gboolean auto_close = gd->auto_close;
 
        if (event->keyval == GDK_KEY_Escape)
@@ -191,7 +191,7 @@ static gboolean generic_dialog_key_press_cb(GtkWidget *widget, GdkEventKey *even
 
 static gboolean generic_dialog_delete_cb(GtkWidget *UNUSED(w), GdkEventAny *UNUSED(event), gpointer data)
 {
-       GenericDialog *gd = data;
+       GenericDialog *gd = static_cast<GenericDialog *>(data);
        gboolean auto_close;
 
        auto_close = gd->auto_close;
@@ -204,7 +204,7 @@ static gboolean generic_dialog_delete_cb(GtkWidget *UNUSED(w), GdkEventAny *UNUS
 
 static void generic_dialog_show_cb(GtkWidget *widget, gpointer data)
 {
-       GenericDialog *gd = data;
+       GenericDialog *gd = static_cast<GenericDialog *>(data);
        if (gd->cancel_button)
                {
                gtk_box_reorder_child(GTK_BOX(gd->hbox), gd->cancel_button, -1);
@@ -361,7 +361,7 @@ void generic_dialog_windows_write_config(GString *outstr, gint indent)
                work = g_list_first(dialog_windows);
                while (work)
                        {
-                       DialogWindow *dw = work->data;
+                       DialogWindow *dw = static_cast<DialogWindow *>(work->data);
                        WRITE_NL(); WRITE_STRING("<window ");
                        write_char_option(outstr, indent + 1, "title", dw->title);
                        write_char_option(outstr, indent + 1, "role", dw->role);
@@ -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 G_SOURCE_REMOVE;
+}
+
+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
@@ -547,14 +740,14 @@ GtkWidget *file_dialog_add_button(FileDialog *fdlg, const gchar *stock_id, const
 
 static void file_dialog_entry_cb(GtkWidget *UNUSED(widget), gpointer data)
 {
-       FileDialog *fdlg = data;
+       FileDialog *fdlg = static_cast<FileDialog *>(data);
        g_free(fdlg->dest_path);
        fdlg->dest_path = remove_trailing_slash(gtk_entry_get_text(GTK_ENTRY(fdlg->entry)));
 }
 
 static void file_dialog_entry_enter_cb(const gchar *UNUSED(path), gpointer data)
 {
-       GenericDialog *gd = data;
+       GenericDialog *gd = static_cast<GenericDialog *>(data);
 
        file_dialog_entry_cb(NULL, data);