How to implement file drag and drop in the Windows version of GTK2

⌚Time: 2026-07-26 17:41:00

👨‍💻Author: Jack Ge

I used to try using the GTK2 Windows version for file drag and drop, but it didn't work. GTK2's support for file drag and drop on Windows is pretty bad. There are even some bugs.

Today the code written by AI helped me achieve this feature. My system is Windows 10, and the GTK2 version is gtk-bundle_2.22.0-20101016_win32.

#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <gdk/gdkwin32.h>
#include <string.h>

// drag-and-drop callback function
static void drag_data_received(GtkWidget *widget, GdkDragContext *context,
                               gint x, gint y, GtkSelectionData *selection_data,
                               guint info, guint time, gpointer user_data)
{
    GtkWidget *label = GTK_WIDGET(user_data);
    gchar **uris = NULL;
    gchar *data_str = NULL;
    GString *display_text = g_string_new("");
    gint i;
    
    // Security check
    if (!selection_data || selection_data->length <= 0) {
        gtk_label_set_text(GTK_LABEL(label), "Error: no data received.");
        gtk_drag_finish(context, FALSE, FALSE, time);
        g_string_free(display_text, TRUE);
        return;
    }
    
    // Check the data format
    GdkAtom target_type = gtk_selection_data_get_data_type(selection_data);
    gchar *type_name = gdk_atom_name(target_type);
    
    if (type_name == NULL || strcmp(type_name, "text/uri-list") != 0) {
        gtk_label_set_text(GTK_LABEL(label), "Error: Data format not supported");
        if (type_name) g_free(type_name);
        gtk_drag_finish(context, FALSE, FALSE, time);
        g_string_free(display_text, TRUE);
        return;
    }
    if (type_name) {
        g_free(type_name);
    }
    
    // Copy data
    data_str = g_strndup((gchar *)selection_data->data, selection_data->length);
    
    if (!data_str || strlen(data_str) == 0) {
        gtk_label_set_text(GTK_LABEL(label), "Error: Data is empty");
        if (data_str) {
            g_free(data_str);
        }
        gtk_drag_finish(context, FALSE, FALSE, time);
        g_string_free(display_text, TRUE);
        return;
    }
    
    // Extract URI
    uris = g_uri_list_extract_uris(data_str);
    
    if (uris != NULL && uris[0] != NULL) {
        gint file_count = 0;
        for (i = 0; uris[i] != NULL; i++) {
            gchar *filename = g_filename_from_uri(uris[i], NULL, NULL);
            if (filename != NULL) {
                if (file_count > 0){
                    g_string_append(display_text, "\n");
                }
                g_string_append_printf(display_text, "[%d] %s", file_count + 1, filename);
                g_free(filename);
                file_count++;
            }
        }
        
        if (file_count > 0) {
            gtk_label_set_text(GTK_LABEL(label), display_text->str);
            gtk_drag_finish(context, TRUE, FALSE, time);
        } else {
            gtk_label_set_text(GTK_LABEL(label), "No valid documents");
            gtk_drag_finish(context, FALSE, FALSE, time);
        }
        
        g_strfreev(uris);
    } else {
        gtk_label_set_text(GTK_LABEL(label), "Can't parse the file list");
        gtk_drag_finish(context, FALSE, FALSE, time);
    }
    
    // Clean
    if (data_str) {
        g_free(data_str);
    }
    g_string_free(display_text, TRUE);
}

static void drag_leave(GtkWidget *widget, GdkDragContext *context, guint time, gpointer user_data)
{
    
}

int main(int argc, char *argv[])
{
    GtkWidget *window;
    GtkWidget *vbox;
    GtkWidget *label;
    GtkWidget *frame;
    GtkWidget *scrolled_window;
    
    gtk_init(&argc, &argv);
    
    // Create main window
    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    gtk_window_set_title(GTK_WINDOW(window), "GTK2 Program");
    gtk_window_set_default_size(GTK_WINDOW(window), 550, 350);
    g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
    
    // vbox
    vbox = gtk_vbox_new(FALSE, 10);
    gtk_container_set_border_width(GTK_CONTAINER(vbox), 20);
    gtk_container_add(GTK_CONTAINER(window), vbox);
    
    // Prompt Label
    GtkWidget *hint = gtk_label_new("Drag and drop files from the file manager into the area below");
    gtk_box_pack_start(GTK_BOX(vbox), hint, FALSE, FALSE, 0);
    
    // Create a scroll window
    scrolled_window = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
    gtk_box_pack_start(GTK_BOX(vbox), scrolled_window, TRUE, TRUE, 0);
    
    // Frame as a drag-and-drop target
    frame = gtk_frame_new("Drag and drop area");
    gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_IN);
    gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(scrolled_window), frame);
    
    // label used to display the path
    label = gtk_label_new("Waiting for the file to be dragged in...");
    gtk_label_set_line_wrap(GTK_LABEL(label), TRUE);
    gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
    gtk_label_set_selectable(GTK_LABEL(label), TRUE);
    gtk_container_add(GTK_CONTAINER(frame), label);
    
    // Set drag-and-drop target
    GtkTargetEntry targets[] = {
        { (gchar *)"text/uri-list", 0, 0 },
        { (gchar *)"text/plain", 0, 1 },
    };
    
    // Use GTK_DEST_DEFAULT_ALL or individual values
    gtk_drag_dest_set(frame, 
                      GTK_DEST_DEFAULT_ALL,
                      targets, G_N_ELEMENTS(targets),
                      GDK_ACTION_COPY);
    
    // Connection signal
    g_signal_connect(frame, "drag-data-received",
                     G_CALLBACK(drag_data_received), label);
    g_signal_connect(frame, "drag-leave",
                     G_CALLBACK(drag_leave), NULL);
    
    // Show all controls
    gtk_widget_show_all(window);
    
    gtk_main();
    return 0;
}

Demo