// dragon - very lightweight DnD file source/target // Copyright 2014 Michael Homer. // // This program 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. // // This program 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 this program. If not, see . #define _POSIX_C_SOURCE 200809L #define _XOPEN_SOURCE 500 #include #include #include #include #include #include #include #include #define VERSION "1.1.2" GtkWidget *window; GtkWidget *vbox; GtkIconTheme *icon_theme; char *progname; bool verbose = false; int mode = 0; bool and_exit; bool keep; bool print_path = false; bool icons_only = false; bool always_on_top = false; static char *stdin_files; #define MODE_HELP 1 #define MODE_TARGET 2 #define MODE_VERSION 4 #define TARGET_TYPE_TEXT 1 #define TARGET_TYPE_URI 2 struct draggable_thing { char *text; char *uri; }; // MODE_ALL #define MAX_SIZE 100 char** uri_collection; int uri_count = 0; bool drag_all = false; // --- void add_target_button(); void do_quit(GtkWidget *widget, gpointer data) { exit(0); } void button_clicked(GtkWidget *widget, gpointer user_data) { struct draggable_thing *dd = (struct draggable_thing *)user_data; if (0 == fork()) { execlp("xdg-open", "xdg-open", dd->uri, NULL); } } void drag_data_get(GtkWidget *widget, GdkDragContext *context, GtkSelectionData *data, guint info, guint time, gpointer user_data) { struct draggable_thing *dd = (struct draggable_thing *)user_data; if (info == TARGET_TYPE_URI) { char** uris; char* single_uri_data[2] = {dd->uri, NULL}; if (drag_all) { uri_collection[uri_count] = NULL; uris = uri_collection; } else { uris = single_uri_data; } if (verbose) { if (drag_all) fputs("Sending all as URI\n", stderr); else fprintf(stderr, "Sending as URI: %s\n", dd->uri); } gtk_selection_data_set_uris(data, uris); g_signal_stop_emission_by_name(widget, "drag-data-get"); } else if (info == TARGET_TYPE_TEXT) { if (verbose) fprintf(stderr, "Sending as TEXT: %s\n", dd->text); gtk_selection_data_set_text(data, dd->text, -1); } else { fprintf(stderr, "Error: bad target type %i\n", info); } } void drag_end(GtkWidget *widget, GdkDragContext *context, gpointer user_data) { if (verbose) { gboolean succeeded = gdk_drag_drop_succeeded(context); GdkDragAction action = gdk_drag_context_get_selected_action (context); char* action_str; switch (action) { case GDK_ACTION_COPY: action_str = "COPY"; break; case GDK_ACTION_MOVE: action_str = "MOVE"; break; case GDK_ACTION_LINK: action_str = "LINK"; break; case GDK_ACTION_ASK: action_str = "ASK"; break; default: action_str = malloc(sizeof(char) * 20); snprintf(action_str, 20, "invalid (%d)", action); break; } fprintf(stderr, "Selected drop action: %s; Succeeded: %d\n", action_str, succeeded); if (action_str[0] == 'i') free(action_str); } if (and_exit) gtk_main_quit(); } GtkButton *add_button(char *label, struct draggable_thing *dragdata, int type) { GtkWidget *button; if (icons_only) { button = gtk_button_new(); } else { button = gtk_button_new_with_label(label); } GtkTargetList *targetlist = gtk_drag_source_get_target_list(GTK_WIDGET(button)); if (targetlist) gtk_target_list_ref(targetlist); else targetlist = gtk_target_list_new(NULL, 0); if (type == TARGET_TYPE_URI) gtk_target_list_add_uri_targets(targetlist, TARGET_TYPE_URI); else gtk_target_list_add_text_targets(targetlist, TARGET_TYPE_TEXT); gtk_drag_source_set(GTK_WIDGET(button), GDK_BUTTON1_MASK, NULL, 0, GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK); gtk_drag_source_set_target_list(GTK_WIDGET(button), targetlist); g_signal_connect(GTK_WIDGET(button), "drag-data-get", G_CALLBACK(drag_data_get), dragdata); g_signal_connect(GTK_WIDGET(button), "clicked", G_CALLBACK(button_clicked), dragdata); g_signal_connect(GTK_WIDGET(button), "drag-end", G_CALLBACK(drag_end), dragdata); gtk_container_add(GTK_CONTAINER(vbox), button); if (drag_all) { if (uri_count < MAX_SIZE) { uri_collection[uri_count] = dragdata->uri; } else { fprintf(stderr, "Exceeded maximum number of files for drag_all (%d)\n", MAX_SIZE); } } uri_count++; return (GtkButton *)button; } void left_align_button(GtkButton *button) { GList *child = g_list_first( gtk_container_get_children(GTK_CONTAINER(button))); if (child) gtk_widget_set_halign(GTK_WIDGET(child->data), GTK_ALIGN_START); } GtkIconInfo* icon_info_from_content_type(char *content_type) { GIcon *icon = g_content_type_get_icon(content_type); return gtk_icon_theme_lookup_by_gicon(icon_theme, icon, 48, 0); } void add_file_button(GFile *file) { char *filename = g_file_get_path(file); if(!g_file_query_exists(file, NULL)) { fprintf(stderr, "The file `%s' does not exist.\n", filename); exit(1); } char *uri = g_file_get_uri(file); struct draggable_thing *dragdata = malloc(sizeof(struct draggable_thing)); dragdata->text = filename; dragdata->uri = uri; GtkButton *button = add_button(filename, dragdata, TARGET_TYPE_URI); GdkPixbuf *pb = gdk_pixbuf_new_from_file_at_size(filename, 96, 96, NULL); if (pb) { GtkWidget *image = gtk_image_new_from_pixbuf(pb); gtk_button_set_always_show_image(button, true); gtk_button_set_image(button, image); gtk_button_set_always_show_image(button, true); } else { GFileInfo *fileinfo = g_file_query_info(file, "*", 0, NULL, NULL); GIcon *icon = g_file_info_get_icon(fileinfo); GtkIconInfo *icon_info = gtk_icon_theme_lookup_by_gicon(icon_theme, icon, 48, 0); // Try a few fallback mimetypes if no icon can be found if (!icon_info) icon_info = icon_info_from_content_type("application/octet-stream"); if (!icon_info) icon_info = icon_info_from_content_type("text/x-generic"); if (!icon_info) icon_info = icon_info_from_content_type("text/plain"); if (icon_info) { GtkWidget *image = gtk_image_new_from_pixbuf( gtk_icon_info_load_icon(icon_info, NULL)); gtk_button_set_image(button, image); gtk_button_set_always_show_image(button, true); } } if (!icons_only) left_align_button(button); } void add_filename_button(char *filename) { GFile *file = g_file_new_for_path(filename); add_file_button(file); } void add_uri_button(char *uri) { struct draggable_thing *dragdata = malloc(sizeof(struct draggable_thing)); dragdata->text = uri; dragdata->uri = uri; GtkButton *button = add_button(uri, dragdata, TARGET_TYPE_URI); left_align_button(button); } bool is_uri(char *uri) { for (int i=0; uri[i]; i++) if (uri[i] == '/') return false; else if (uri[i] == ':') return true; return false; } bool is_file_uri(char *uri) { char *prefix = "file:"; return strncmp(prefix, uri, strlen(prefix)) == 0; } gboolean drag_drop (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer user_data) { GtkTargetList *targetlist = gtk_drag_dest_get_target_list(widget); GList *list = gdk_drag_context_list_targets(context); if (list) { while (list) { GdkAtom atom = (GdkAtom)g_list_nth_data(list, 0); if (gtk_target_list_find(targetlist, GDK_POINTER_TO_ATOM(g_list_nth_data(list, 0)), NULL)) { gtk_drag_get_data(widget, context, atom, time); return true; } list = g_list_next(list); } } gtk_drag_finish(context, false, false, time); return true; } void drag_data_received (GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *data, guint info, guint time) { gchar **uris = gtk_selection_data_get_uris(data); unsigned char *text = gtk_selection_data_get_text(data); if (!uris && !text) gtk_drag_finish (context, FALSE, FALSE, time); if (uris) { if (verbose) fputs("Received URIs\n", stderr); gtk_container_remove(GTK_CONTAINER(vbox), widget); for (; *uris; uris++) { if (is_file_uri(*uris)) { GFile *file = g_file_new_for_uri(*uris); if (print_path) { char *filename = g_file_get_path(file); printf("%s\n", filename); } else printf("%s\n", *uris); if (keep) add_file_button(file); } else { printf("%s\n", *uris); if (keep) add_uri_button(*uris); } } add_target_button(); gtk_widget_show_all(window); } else if (text) { if (verbose) fputs("Received Text\n", stderr); printf("%s\n", text); } else if (verbose) fputs("Received nothing\n", stderr); gtk_drag_finish (context, TRUE, FALSE, time); if (and_exit) gtk_main_quit(); } void add_target_button() { GtkWidget *label = gtk_button_new(); gtk_button_set_label(GTK_BUTTON(label), "Drop\nhere"); // Custom for drop-videos, 12 char *dragon_icon = getenv("DRAGON_ICON"); if ((dragon_icon != NULL) && (dragon_icon[0] != '\0')) { GtkIconInfo *icon_info = icon_info = icon_info_from_content_type(dragon_icon); if (icon_info) { GtkWidget *image = gtk_image_new_from_pixbuf( gtk_icon_info_load_icon(icon_info, NULL)); gtk_button_set_image(GTK_BUTTON(label), image); gtk_button_set_always_show_image(GTK_BUTTON(label), true); }; gtk_button_set_label(GTK_BUTTON(label), ""); }; gtk_container_add(GTK_CONTAINER(vbox), label); GtkTargetList *targetlist = gtk_drag_dest_get_target_list(GTK_WIDGET(label)); if (targetlist) gtk_target_list_ref(targetlist); else targetlist = gtk_target_list_new(NULL, 0); gtk_target_list_add_text_targets(targetlist, TARGET_TYPE_TEXT); gtk_target_list_add_uri_targets(targetlist, TARGET_TYPE_URI); gtk_drag_dest_set(GTK_WIDGET(label), GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT, NULL, 0, GDK_ACTION_COPY); gtk_drag_dest_set_target_list(GTK_WIDGET(label), targetlist); g_signal_connect(GTK_WIDGET(label), "drag-drop", G_CALLBACK(drag_drop), NULL); g_signal_connect(GTK_WIDGET(label), "drag-data-received", G_CALLBACK(drag_data_received), NULL); } void target_mode() { add_target_button(); gtk_widget_show_all(window); gtk_main(); } void make_btn(char *filename) { if (!is_uri(filename)) { add_filename_button(filename); } else if (is_file_uri(filename)) { GFile *file = g_file_new_for_uri(filename); add_file_button(file); } else { add_uri_button(filename); } } static void readstdin(void) { char *write_pos = stdin_files, *newline; size_t max_size = BUFSIZ * 2, cur_size = 0; // read each line from stdin and add it to the item list while (fgets(write_pos, BUFSIZ, stdin)) { if (write_pos[0] == '-') continue; if ((newline = strchr(write_pos, '\n'))) *newline = '\0'; else break; make_btn(write_pos); cur_size = newline - stdin_files + 1; if (max_size < cur_size + BUFSIZ) { if (!(stdin_files = realloc(stdin_files, (max_size += BUFSIZ)))) fprintf(stderr, "%s: cannot realloc %lu bytes.\n", progname, max_size); newline = stdin_files + cur_size - 1; } write_pos = newline + 1; } } int main (int argc, char **argv) { bool from_stdin = false; stdin_files = malloc(BUFSIZ * 2); progname = argv[0]; for (int i=1; i MAX_SIZE ? argc : MAX_SIZE) + 1)); for (int i=1; i