[download]

local/src/dragon/dragon.c

   1 // dragon - very lightweight DnD file source/target
   2 // Copyright 2014 Michael Homer.
   3 //
   4 // This program is free software: you can redistribute it and/or modify
   5 // it under the terms of the GNU General Public License as published by
   6 // the Free Software Foundation, either version 3 of the License, or
   7 // (at your option) any later version.
   8 //
   9 // This program is distributed in the hope that it will be useful,
  10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12 // GNU General Public License for more details.
  13 //
  14 // You should have received a copy of the GNU General Public License
  15 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
  16 #define _POSIX_C_SOURCE 200809L
  17 #define _XOPEN_SOURCE 500
  18 #include <gtk/gtk.h>
  19 #include <gdk/gdk.h>
  20 #include <gdk/gdkkeysyms.h>
  21 #include <gio/gio.h>
  22 #include <stdio.h>
  23 #include <stdlib.h>
  24 #include <stdbool.h>
  25 #include <string.h>
  26 
  27 #define VERSION "1.1.1"
  28 
  29 
  30 GtkWidget *window;
  31 GtkWidget *vbox;
  32 GtkIconTheme *icon_theme;
  33 
  34 char *progname;
  35 bool verbose = false;
  36 int mode = 0;
  37 bool and_exit;
  38 bool keep;
  39 bool print_path = false;
  40 bool icons_only = false;
  41 bool always_on_top = false;
  42 
  43 static char *stdin_files;
  44 
  45 #define MODE_HELP 1
  46 #define MODE_TARGET 2
  47 #define MODE_VERSION 4
  48 
  49 #define TARGET_TYPE_TEXT 1
  50 #define TARGET_TYPE_URI 2
  51 
  52 struct draggable_thing {
  53     char *text;
  54     char *uri;
  55 };
  56 
  57 // MODE_ALL
  58 #define MAX_SIZE 100
  59 char** uri_collection;
  60 int uri_count = 0;
  61 bool drag_all = false;
  62 // ---
  63 
  64 void add_target_button();
  65 
  66 void do_quit(GtkWidget *widget, gpointer data) {
  67     exit(0);
  68 }
  69 
  70 void button_clicked(GtkWidget *widget, gpointer user_data) {
  71     struct draggable_thing *dd = (struct draggable_thing *)user_data;
  72     if (0 == fork()) {
  73         execlp("xdg-open", "xdg-open", dd->uri, NULL);
  74     }
  75 }
  76 
  77 void drag_data_get(GtkWidget    *widget,
  78                GdkDragContext   *context,
  79                GtkSelectionData *data,
  80                guint             info,
  81                guint             time,
  82                gpointer          user_data) {
  83     struct draggable_thing *dd = (struct draggable_thing *)user_data;
  84     if (info == TARGET_TYPE_URI) {
  85 
  86         char** uris;
  87         char* single_uri_data[2] = {dd->uri, NULL};
  88         if (drag_all) {
  89             uri_collection[uri_count] = NULL;
  90             uris = uri_collection;
  91         } else {
  92             uris = single_uri_data;
  93         }
  94         if (verbose) {
  95             if (drag_all)
  96                 fputs("Sending all as URI\n", stderr);
  97             else
  98                 fprintf(stderr, "Sending as URI: %s\n", dd->uri);
  99         }
 100 
 101         gtk_selection_data_set_uris(data, uris);
 102         g_signal_stop_emission_by_name(widget, "drag-data-get");
 103     } else if (info == TARGET_TYPE_TEXT) {
 104         if (verbose)
 105             fprintf(stderr, "Sending as TEXT: %s\n", dd->text);
 106         gtk_selection_data_set_text(data, dd->text, -1);
 107     } else {
 108         fprintf(stderr, "Error: bad target type %i\n", info);
 109     }
 110 }
 111 
 112 void drag_end(GtkWidget *widget, GdkDragContext *context, gpointer user_data) {
 113     if (verbose) {
 114         gboolean succeeded = gdk_drag_drop_succeeded(context);
 115         GdkDragAction action = gdk_drag_context_get_selected_action (context);
 116         char* action_str;
 117         switch (action) {
 118             case GDK_ACTION_COPY:
 119                 action_str = "COPY"; break;
 120             case GDK_ACTION_MOVE:
 121                 action_str = "MOVE"; break;
 122             case GDK_ACTION_LINK:
 123                 action_str = "LINK"; break;
 124             case GDK_ACTION_ASK:
 125                 action_str = "ASK"; break;
 126             default:
 127                 action_str = malloc(sizeof(char) * 20);
 128                 snprintf(action_str, 20, "invalid (%d)", action);
 129                 break;
 130         }
 131         fprintf(stderr, "Selected drop action: %s; Succeeded: %d\n", action_str, succeeded);
 132         if (action_str[0] == 'i')
 133             free(action_str);
 134     }
 135     if (and_exit)
 136         gtk_main_quit();
 137 }
 138 
 139 GtkButton *add_button(char *label, struct draggable_thing *dragdata, int type) {
 140     GtkWidget *button;
 141 
 142     if (icons_only) {
 143         button = gtk_button_new();
 144     } else {
 145         button = gtk_button_new_with_label(label);
 146     }
 147 
 148     GtkTargetList *targetlist = gtk_drag_source_get_target_list(GTK_WIDGET(button));
 149     if (targetlist)
 150         gtk_target_list_ref(targetlist);
 151     else
 152         targetlist = gtk_target_list_new(NULL, 0);
 153     if (type == TARGET_TYPE_URI)
 154         gtk_target_list_add_uri_targets(targetlist, TARGET_TYPE_URI);
 155     else
 156         gtk_target_list_add_text_targets(targetlist, TARGET_TYPE_TEXT);
 157 
 158     gtk_drag_source_set(GTK_WIDGET(button), GDK_BUTTON1_MASK, NULL, 0,
 159             GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK);
 160     gtk_drag_source_set_target_list(GTK_WIDGET(button), targetlist);
 161     g_signal_connect(GTK_WIDGET(button), "drag-data-get",
 162             G_CALLBACK(drag_data_get), dragdata);
 163     g_signal_connect(GTK_WIDGET(button), "clicked",
 164             G_CALLBACK(button_clicked), dragdata);
 165     g_signal_connect(GTK_WIDGET(button), "drag-end",
 166             G_CALLBACK(drag_end), dragdata);
 167 
 168     gtk_container_add(GTK_CONTAINER(vbox), button);
 169 
 170     if (drag_all) {
 171         if (uri_count < MAX_SIZE) {
 172             uri_collection[uri_count] = dragdata->uri;
 173         } else {
 174             fprintf(stderr, "Exceeded maximum number of files for drag_all (%d)\n", MAX_SIZE);
 175         }
 176     }
 177     uri_count++;
 178 
 179     return (GtkButton *)button;
 180 }
 181 
 182 void left_align_button(GtkButton *button) {
 183     GList *child = g_list_first(
 184             gtk_container_get_children(GTK_CONTAINER(button)));
 185     if (child)
 186         gtk_widget_set_halign(GTK_WIDGET(child->data), GTK_ALIGN_START);
 187 }
 188 
 189 GtkIconInfo* icon_info_from_content_type(char *content_type) {
 190     GIcon *icon = g_content_type_get_icon(content_type);
 191     return gtk_icon_theme_lookup_by_gicon(icon_theme, icon, 48, 0);
 192 }
 193 
 194 void add_file_button(GFile *file) {
 195     char *filename = g_file_get_path(file);
 196     if(!g_file_query_exists(file, NULL)) {
 197         fprintf(stderr, "The file `%s' does not exist.\n",
 198                 filename);
 199         exit(1);
 200     }
 201     char *uri = g_file_get_uri(file);
 202     struct draggable_thing *dragdata = malloc(sizeof(struct draggable_thing));
 203     dragdata->text = filename;
 204     dragdata->uri = uri;
 205 
 206     GtkButton *button = add_button(filename, dragdata, TARGET_TYPE_URI);
 207     GdkPixbuf *pb = gdk_pixbuf_new_from_file_at_size(filename, 96, 96, NULL);
 208     if (pb) {
 209         GtkWidget *image = gtk_image_new_from_pixbuf(pb);
 210         gtk_button_set_always_show_image(button, true);
 211         gtk_button_set_image(button, image);
 212         gtk_button_set_always_show_image(button, true);
 213     } else {
 214         GFileInfo *fileinfo = g_file_query_info(file, "*", 0, NULL, NULL);
 215         GIcon *icon = g_file_info_get_icon(fileinfo);
 216         GtkIconInfo *icon_info = gtk_icon_theme_lookup_by_gicon(icon_theme,
 217                 icon, 48, 0);
 218 
 219         // Try a few fallback mimetypes if no icon can be found
 220         if (!icon_info)
 221             icon_info = icon_info_from_content_type("application/octet-stream");
 222         if (!icon_info)
 223             icon_info = icon_info_from_content_type("text/x-generic");
 224         if (!icon_info)
 225             icon_info = icon_info_from_content_type("text/plain");
 226 
 227         if (icon_info) {
 228             GtkWidget *image = gtk_image_new_from_pixbuf(
 229                     gtk_icon_info_load_icon(icon_info, NULL));
 230             gtk_button_set_image(button, image);
 231             gtk_button_set_always_show_image(button, true);
 232         }
 233     }
 234 
 235     if (!icons_only)
 236         left_align_button(button);
 237 }
 238 
 239 void add_filename_button(char *filename) {
 240     GFile *file = g_file_new_for_path(filename);
 241     add_file_button(file);
 242 }
 243 
 244 void add_uri_button(char *uri) {
 245     struct draggable_thing *dragdata = malloc(sizeof(struct draggable_thing));
 246     dragdata->text = uri;
 247     dragdata->uri = uri;
 248     GtkButton *button = add_button(uri, dragdata, TARGET_TYPE_URI);
 249     left_align_button(button);
 250 }
 251 
 252 bool is_uri(char *uri) {
 253     for (int i=0; uri[i]; i++)
 254         if (uri[i] == '/')
 255             return false;
 256         else if (uri[i] == ':')
 257             return true;
 258     return false;
 259 }
 260 
 261 bool is_file_uri(char *uri) {
 262     char *prefix = "file:";
 263     return strncmp(prefix, uri, strlen(prefix)) == 0;
 264 }
 265 
 266 gboolean drag_drop (GtkWidget *widget,
 267                GdkDragContext *context,
 268                gint            x,
 269                gint            y,
 270                guint           time,
 271                gpointer        user_data) {
 272     GtkTargetList *targetlist = gtk_drag_dest_get_target_list(widget);
 273     GList *list = gdk_drag_context_list_targets(context);
 274     if (list) {
 275         while (list) {
 276             GdkAtom atom = (GdkAtom)g_list_nth_data(list, 0);
 277             if (gtk_target_list_find(targetlist,
 278                         GDK_POINTER_TO_ATOM(g_list_nth_data(list, 0)), NULL)) {
 279                 gtk_drag_get_data(widget, context, atom, time);
 280                 return true;
 281             }
 282             list = g_list_next(list);
 283         }
 284     }
 285     gtk_drag_finish(context, false, false, time);
 286     return true;
 287 }
 288 
 289 void
 290 drag_data_received (GtkWidget          *widget,
 291                     GdkDragContext     *context,
 292                     gint                x,
 293                     gint                y,
 294                     GtkSelectionData   *data,
 295                     guint               info,
 296                     guint               time) {
 297     gchar **uris = gtk_selection_data_get_uris(data);
 298     unsigned char *text = gtk_selection_data_get_text(data);
 299     if (!uris && !text)
 300         gtk_drag_finish (context, FALSE, FALSE, time);
 301     if (uris) {
 302         if (verbose)
 303             fputs("Received URIs\n", stderr);
 304         gtk_container_remove(GTK_CONTAINER(vbox), widget);
 305         for (; *uris; uris++) {
 306             if (is_file_uri(*uris)) {
 307                 GFile *file = g_file_new_for_uri(*uris);
 308                 if (print_path) {
 309                     char *filename = g_file_get_path(file);
 310                     printf("%s\n", filename);
 311                 } else
 312                     printf("%s\n", *uris);
 313                 if (keep)
 314                     add_file_button(file);
 315 
 316             } else {
 317                 printf("%s\n", *uris);
 318                 if (keep)
 319                     add_uri_button(*uris);
 320             }
 321         }
 322         add_target_button();
 323         gtk_widget_show_all(window);
 324     } else if (text) {
 325         if (verbose)
 326             fputs("Received Text\n", stderr);
 327         printf("%s\n", text);
 328     } else if (verbose)
 329         fputs("Received nothing\n", stderr);
 330     gtk_drag_finish (context, TRUE, FALSE, time);
 331     if (and_exit)
 332         gtk_main_quit();
 333 }
 334 
 335 void add_target_button() {
 336     GtkWidget *label = gtk_button_new();
 337     gtk_button_set_label(GTK_BUTTON(label), "Drag something here...");
 338     gtk_container_add(GTK_CONTAINER(vbox), label);
 339     GtkTargetList *targetlist = gtk_drag_dest_get_target_list(GTK_WIDGET(label));
 340     if (targetlist)
 341         gtk_target_list_ref(targetlist);
 342     else
 343         targetlist = gtk_target_list_new(NULL, 0);
 344     gtk_target_list_add_text_targets(targetlist, TARGET_TYPE_TEXT);
 345     gtk_target_list_add_uri_targets(targetlist, TARGET_TYPE_URI);
 346     gtk_drag_dest_set(GTK_WIDGET(label),
 347             GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT, NULL, 0,
 348             GDK_ACTION_COPY);
 349     gtk_drag_dest_set_target_list(GTK_WIDGET(label), targetlist);
 350     g_signal_connect(GTK_WIDGET(label), "drag-drop",
 351             G_CALLBACK(drag_drop), NULL);
 352     g_signal_connect(GTK_WIDGET(label), "drag-data-received",
 353             G_CALLBACK(drag_data_received), NULL);
 354 }
 355 
 356 void target_mode() {
 357     add_target_button();
 358     gtk_widget_show_all(window);
 359     gtk_main();
 360 }
 361 
 362 void make_btn(char *filename) {
 363     if (!is_uri(filename)) {
 364         add_filename_button(filename);
 365     } else if (is_file_uri(filename)) {
 366         GFile *file = g_file_new_for_uri(filename);
 367         add_file_button(file);
 368     } else {
 369         add_uri_button(filename);
 370     }
 371 }
 372 
 373 static void readstdin(void) {
 374     char *write_pos = stdin_files, *newline;
 375     size_t max_size = BUFSIZ * 2, cur_size = 0;
 376     // read each line from stdin and add it to the item list
 377     while (fgets(write_pos, BUFSIZ, stdin)) {
 378             if (write_pos[0] == '-')
 379                     continue;
 380             if ((newline = strchr(write_pos, '\n')))
 381                     *newline = '\0';
 382             else
 383                     break;
 384             make_btn(write_pos);
 385             cur_size = newline - stdin_files + 1;
 386             if (max_size < cur_size + BUFSIZ) {
 387                     if (!(stdin_files = realloc(stdin_files, (max_size += BUFSIZ))))
 388                             fprintf(stderr, "%s: cannot realloc %lu bytes.\n", progname, max_size);
 389                     newline = stdin_files + cur_size - 1;
 390             }
 391             write_pos = newline + 1;
 392     }
 393 }
 394 
 395 int main (int argc, char **argv) {
 396     bool from_stdin = false;
 397     stdin_files = malloc(BUFSIZ * 2);
 398     progname = argv[0];
 399     for (int i=1; i<argc; i++) {
 400         if (strcmp(argv[i], "--help") == 0) {
 401             mode = MODE_HELP;
 402             printf("dragon - lightweight DnD source/target\n");
 403             printf("Usage: %s [OPTION] [FILENAME]\n", argv[0]);
 404             printf("  --and-exit,   -x  exit after a single completed drop\n");
 405             printf("  --target,     -t  act as a target instead of source\n");
 406             printf("  --keep,       -k  with --target, keep files to drag out\n");
 407             printf("  --print-path, -p  with --target, print file paths"
 408                     " instead of URIs\n");
 409             printf("  --all,        -a  drag all files at once\n");
 410             printf("  --icon-only,  -i  only show icons in drag-and-drop"
 411                     " windows\n");
 412             printf("  --on-top,     -T  make window always-on-top\n");
 413             printf("  --stdin,      -I  read input from stdin\n");
 414             printf("  --verbose,    -v  be verbose\n");
 415             printf("  --help            show help\n");
 416             printf("  --version         show version details\n");
 417             exit(0);
 418         } else if (strcmp(argv[i], "--version") == 0) {
 419             mode = MODE_VERSION;
 420             puts("dragon " VERSION);
 421             puts("Copyright (C) 2014-2018 Michael Homer");
 422             puts("This program comes with ABSOLUTELY NO WARRANTY.");
 423             puts("See the source for copying conditions.");
 424             exit(0);
 425         } else if (strcmp(argv[i], "-v") == 0
 426                 || strcmp(argv[i], "--verbose") == 0) {
 427             verbose = true;
 428         } else if (strcmp(argv[i], "-t") == 0
 429                 || strcmp(argv[i], "--target") == 0) {
 430             mode = MODE_TARGET;
 431         } else if (strcmp(argv[i], "-x") == 0
 432                 || strcmp(argv[i], "--and-exit") == 0) {
 433             and_exit = true;
 434         } else if (strcmp(argv[i], "-k") == 0
 435                 || strcmp(argv[i], "--keep") == 0) {
 436             keep = true;
 437         } else if (strcmp(argv[i], "-p") == 0
 438                 || strcmp(argv[i], "--print-path") == 0) {
 439             print_path = true;
 440         } else if (strcmp(argv[i], "-a") == 0
 441                 || strcmp(argv[i], "--all") == 0) {
 442             drag_all = true;
 443         } else if (strcmp(argv[i], "-i") == 0
 444                 || strcmp(argv[i], "--icon-only") == 0) {
 445             icons_only = true;
 446         } else if (strcmp(argv[i], "-T") == 0
 447                 || strcmp(argv[i], "--on-top") == 0) {
 448             always_on_top = true;
 449         } else if (strcmp(argv[i], "-I") == 0
 450                 || strcmp(argv[i], "--stdin") == 0) {
 451             from_stdin = true;
 452         } else if (argv[i][0] == '-') {
 453             fprintf(stderr, "%s: error: unknown option `%s'.\n",
 454                     progname, argv[i]);
 455         }
 456     }
 457     setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
 458 
 459     GtkAccelGroup *accelgroup;
 460     GClosure *closure;
 461 
 462     gtk_init(&argc, &argv);
 463 
 464     icon_theme = gtk_icon_theme_get_default();
 465 
 466     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
 467 
 468     closure = g_cclosure_new(G_CALLBACK(do_quit), NULL, NULL);
 469     accelgroup = gtk_accel_group_new();
 470     gtk_accel_group_connect(accelgroup, GDK_KEY_Escape, 0, 0, closure);
 471     closure = g_cclosure_new(G_CALLBACK(do_quit), NULL, NULL);
 472     gtk_accel_group_connect(accelgroup, GDK_KEY_q, 0, 0, closure);
 473     gtk_window_add_accel_group(GTK_WINDOW(window), accelgroup);
 474 
 475     gtk_window_set_title(GTK_WINDOW(window), "Run");
 476     gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
 477     gtk_window_set_keep_above(GTK_WINDOW(window), always_on_top);
 478 
 479     g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
 480 
 481     vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
 482 
 483     gtk_container_add(GTK_CONTAINER(window), vbox);
 484 
 485     gtk_window_set_title(GTK_WINDOW(window), "dragon");
 486 
 487     if (mode == MODE_TARGET) {
 488         target_mode();
 489         exit(0);
 490     }
 491 
 492     if (from_stdin)
 493         uri_collection = malloc(sizeof(char*) * (MAX_SIZE  + 1));
 494     else if (drag_all)
 495         uri_collection = malloc(sizeof(char*) * ((argc > MAX_SIZE ? argc : MAX_SIZE) + 1));
 496 
 497     for (int i=1; i<argc; i++) {
 498         if (argv[i][0] != '-')
 499            make_btn(argv[i]);
 500     }
 501     if (from_stdin)
 502         readstdin();
 503 
 504     if (!uri_count) {
 505         printf("Usage: %s [OPTIONS] FILENAME\n", progname);
 506         exit(0);
 507     }
 508 
 509     gtk_widget_show_all(window);
 510 
 511     gtk_main();
 512 
 513     return 0;
 514 }