[download]

local/src/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 
  42 #define MODE_HELP 1
  43 #define MODE_TARGET 2
  44 #define MODE_VERSION 4
  45 
  46 #define TARGET_TYPE_TEXT 1
  47 #define TARGET_TYPE_URI 2
  48 
  49 struct draggable_thing {
  50     char *text;
  51     char *uri;
  52 };
  53 
  54 // MODE_ALL
  55 #define MAX_SIZE 100
  56 char** uri_collection;
  57 int uri_count;
  58 bool drag_all = false;
  59 // ---
  60 
  61 void add_target_button();
  62 
  63 void do_quit(GtkWidget *widget, gpointer data) {
  64     exit(0);
  65 }
  66 
  67 void button_clicked(GtkWidget *widget, gpointer user_data) {
  68     struct draggable_thing *dd = (struct draggable_thing *)user_data;
  69     if (0 == fork()) {
  70         execlp("xdg-open", "xdg-open", dd->uri, NULL);
  71     }
  72 }
  73 
  74 void drag_data_get(GtkWidget    *widget,
  75                GdkDragContext   *context,
  76                GtkSelectionData *data,
  77                guint             info,
  78                guint             time,
  79                gpointer          user_data) {
  80     struct draggable_thing *dd = (struct draggable_thing *)user_data;
  81     if (info == TARGET_TYPE_URI) {
  82 
  83         char** uris;
  84         char* single_uri_data[2] = {dd->uri, NULL};
  85         if (drag_all) {
  86             uri_collection[uri_count] = NULL;
  87             uris = uri_collection;
  88         } else {
  89             uris = single_uri_data;
  90         }
  91         if (verbose) {
  92             if (drag_all)
  93                 fputs("Sending all as URI\n", stderr);
  94             else
  95                 fprintf(stderr, "Sending as URI: %s\n", dd->uri);
  96         }
  97 
  98         gtk_selection_data_set_uris(data, uris);
  99         g_signal_stop_emission_by_name(widget, "drag-data-get");
 100     } else if (info == TARGET_TYPE_TEXT) {
 101         if (verbose)
 102             fprintf(stderr, "Sending as TEXT: %s\n", dd->text);
 103         gtk_selection_data_set_text(data, dd->text, -1);
 104     } else {
 105         fprintf(stderr, "Error: bad target type %i\n", info);
 106     }
 107 }
 108 
 109 void drag_end(GtkWidget *widget, GdkDragContext *context, gpointer user_data) {
 110     if (verbose) {
 111         gboolean succeeded = gdk_drag_drop_succeeded(context);
 112         GdkDragAction action = gdk_drag_context_get_selected_action (context);
 113         char* action_str;
 114         switch (action) {
 115             case GDK_ACTION_COPY:
 116                 action_str = "COPY"; break;
 117             case GDK_ACTION_MOVE:
 118                 action_str = "MOVE"; break;
 119             case GDK_ACTION_LINK:
 120                 action_str = "LINK"; break;
 121             case GDK_ACTION_ASK:
 122                 action_str = "ASK"; break;
 123             default:
 124                 action_str = malloc(sizeof(char) * 20);
 125                 snprintf(action_str, 20, "invalid (%d)", action);
 126                 break;
 127         }
 128         fprintf(stderr, "Selected drop action: %s; Succeeded: %d\n", action_str, succeeded);
 129         if (action_str[0] == 'i')
 130             free(action_str);
 131     }
 132     if (and_exit)
 133         gtk_main_quit();
 134 }
 135 
 136 GtkButton *add_button(char *label, struct draggable_thing *dragdata, int type) {
 137     GtkWidget *button;
 138 
 139     if (icons_only) {
 140         button = gtk_button_new();
 141     } else {
 142         button = gtk_button_new_with_label(label);
 143     }
 144 
 145     GtkTargetList *targetlist = gtk_drag_source_get_target_list(GTK_WIDGET(button));
 146     if (targetlist)
 147         gtk_target_list_ref(targetlist);
 148     else
 149         targetlist = gtk_target_list_new(NULL, 0);
 150     if (type == TARGET_TYPE_URI)
 151         gtk_target_list_add_uri_targets(targetlist, TARGET_TYPE_URI);
 152     else
 153         gtk_target_list_add_text_targets(targetlist, TARGET_TYPE_TEXT);
 154 
 155     gtk_drag_source_set(GTK_WIDGET(button), GDK_BUTTON1_MASK, NULL, 0,
 156             GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_ASK);
 157     gtk_drag_source_set_target_list(GTK_WIDGET(button), targetlist);
 158     g_signal_connect(GTK_WIDGET(button), "drag-data-get",
 159             G_CALLBACK(drag_data_get), dragdata);
 160     g_signal_connect(GTK_WIDGET(button), "clicked",
 161             G_CALLBACK(button_clicked), dragdata);
 162     g_signal_connect(GTK_WIDGET(button), "drag-end",
 163             G_CALLBACK(drag_end), dragdata);
 164 
 165     gtk_container_add(GTK_CONTAINER(vbox), button);
 166 
 167     if (drag_all) {
 168         if (uri_count < MAX_SIZE) {
 169             uri_collection[uri_count++] = dragdata->uri;
 170         } else {
 171             fprintf(stderr, "Exceeded maximum number of files for drag_all (%d)\n", MAX_SIZE);
 172         }
 173     }
 174 
 175     return (GtkButton *)button;
 176 }
 177 
 178 void left_align_button(GtkButton *button) {
 179     GList *child = g_list_first(
 180             gtk_container_get_children(GTK_CONTAINER(button)));
 181     if (child)
 182         gtk_widget_set_halign(GTK_WIDGET(child->data), GTK_ALIGN_START);
 183 }
 184 
 185 GtkIconInfo* icon_info_from_content_type(char *content_type) {
 186     GIcon *icon = g_content_type_get_icon(content_type);
 187     return gtk_icon_theme_lookup_by_gicon(icon_theme, icon, 48, 0);
 188 }
 189 
 190 void add_file_button(GFile *file) {
 191     char *filename = g_file_get_path(file);
 192     if(!g_file_query_exists(file, NULL)) {
 193         fprintf(stderr, "The file `%s' does not exist.\n",
 194                 filename);
 195         exit(1);
 196     }
 197     char *uri = g_file_get_uri(file);
 198     struct draggable_thing *dragdata = malloc(sizeof(struct draggable_thing));
 199     dragdata->text = filename;
 200     dragdata->uri = uri;
 201 
 202     GtkButton *button = add_button(filename, dragdata, TARGET_TYPE_URI);
 203     GFileInfo *fileinfo = g_file_query_info(file, "*", 0, NULL, NULL);
 204     GIcon *icon = g_file_info_get_icon(fileinfo);
 205     GtkIconInfo *icon_info = gtk_icon_theme_lookup_by_gicon(icon_theme,
 206             icon, 48, 0);
 207 
 208     // Try a few fallback mimetypes if no icon can be found
 209     if (!icon_info)
 210         icon_info = icon_info_from_content_type("application/octet-stream");
 211     if (!icon_info)
 212         icon_info = icon_info_from_content_type("text/x-generic");
 213     if (!icon_info)
 214         icon_info = icon_info_from_content_type("text/plain");
 215 
 216     if (icon_info) {
 217         GtkWidget *image = gtk_image_new_from_pixbuf(
 218                 gtk_icon_info_load_icon(icon_info, NULL));
 219         gtk_button_set_image(button, image);
 220         gtk_button_set_always_show_image(button, true);
 221     }
 222 
 223     if (!icons_only)
 224         left_align_button(button);
 225 }
 226 
 227 void add_filename_button(char *filename) {
 228     GFile *file = g_file_new_for_path(filename);
 229     add_file_button(file);
 230 }
 231 
 232 void add_uri_button(char *uri) {
 233     struct draggable_thing *dragdata = malloc(sizeof(struct draggable_thing));
 234     dragdata->text = uri;
 235     dragdata->uri = uri;
 236     GtkButton *button = add_button(uri, dragdata, TARGET_TYPE_URI);
 237     left_align_button(button);
 238 }
 239 
 240 bool is_uri(char *uri) {
 241     for (int i=0; uri[i]; i++)
 242         if (uri[i] == '/')
 243             return false;
 244         else if (uri[i] == ':')
 245             return true;
 246     return false;
 247 }
 248 
 249 bool is_file_uri(char *uri) {
 250     char *prefix = "file:";
 251     return strncmp(prefix, uri, strlen(prefix)) == 0;
 252 }
 253 
 254 gboolean drag_drop (GtkWidget *widget,
 255                GdkDragContext *context,
 256                gint            x,
 257                gint            y,
 258                guint           time,
 259                gpointer        user_data) {
 260     GtkTargetList *targetlist = gtk_drag_dest_get_target_list(widget);
 261     GList *list = gdk_drag_context_list_targets(context);
 262     if (list) {
 263         while (list) {
 264             GdkAtom atom = (GdkAtom)g_list_nth_data(list, 0);
 265             if (gtk_target_list_find(targetlist,
 266                         GDK_POINTER_TO_ATOM(g_list_nth_data(list, 0)), NULL)) {
 267                 gtk_drag_get_data(widget, context, atom, time);
 268                 return true;
 269             }
 270             list = g_list_next(list);
 271         }
 272     }
 273     gtk_drag_finish(context, false, false, time);
 274     return true;
 275 }
 276 
 277 void
 278 drag_data_received (GtkWidget          *widget,
 279                     GdkDragContext     *context,
 280                     gint                x,
 281                     gint                y,
 282                     GtkSelectionData   *data,
 283                     guint               info,
 284                     guint               time) {
 285     gchar **uris = gtk_selection_data_get_uris(data);
 286     unsigned char *text = gtk_selection_data_get_text(data);
 287     if (!uris && !text)
 288         gtk_drag_finish (context, FALSE, FALSE, time);
 289     if (uris) {
 290         if (verbose)
 291             fputs("Received URIs\n", stderr);
 292         gtk_container_remove(GTK_CONTAINER(vbox), widget);
 293         for (; *uris; uris++) {
 294             if (is_file_uri(*uris)) {
 295                 GFile *file = g_file_new_for_uri(*uris);
 296                 if (print_path) {
 297                     char *filename = g_file_get_path(file);
 298                     printf("%s\n", filename);
 299                 } else
 300                     printf("%s\n", *uris);
 301                 if (keep)
 302                     add_file_button(file);
 303 
 304             } else {
 305                 printf("%s\n", *uris);
 306                 if (keep)
 307                     add_uri_button(*uris);
 308             }
 309         }
 310         add_target_button();
 311         gtk_widget_show_all(window);
 312     } else if (text) {
 313         if (verbose)
 314             fputs("Received Text\n", stderr);
 315         printf("%s\n", text);
 316     } else if (verbose)
 317         fputs("Received nothing\n", stderr);
 318     gtk_drag_finish (context, TRUE, FALSE, time);
 319     if (and_exit)
 320         gtk_main_quit();
 321 }
 322 
 323 void add_target_button() {
 324     GtkWidget *label = gtk_button_new();
 325     gtk_button_set_label(GTK_BUTTON(label), "Drag something here...");
 326     gtk_container_add(GTK_CONTAINER(vbox), label);
 327     GtkTargetList *targetlist = gtk_drag_dest_get_target_list(GTK_WIDGET(label));
 328     if (targetlist)
 329         gtk_target_list_ref(targetlist);
 330     else
 331         targetlist = gtk_target_list_new(NULL, 0);
 332     gtk_target_list_add_text_targets(targetlist, TARGET_TYPE_TEXT);
 333     gtk_target_list_add_uri_targets(targetlist, TARGET_TYPE_URI);
 334     gtk_drag_dest_set(GTK_WIDGET(label),
 335             GTK_DEST_DEFAULT_MOTION | GTK_DEST_DEFAULT_HIGHLIGHT, NULL, 0,
 336             GDK_ACTION_COPY);
 337     gtk_drag_dest_set_target_list(GTK_WIDGET(label), targetlist);
 338     g_signal_connect(GTK_WIDGET(label), "drag-drop",
 339             G_CALLBACK(drag_drop), NULL);
 340     g_signal_connect(GTK_WIDGET(label), "drag-data-received",
 341             G_CALLBACK(drag_data_received), NULL);
 342 }
 343 
 344 void target_mode() {
 345     add_target_button();
 346     gtk_widget_show_all(window);
 347     gtk_main();
 348 }
 349 
 350 int main (int argc, char **argv) {
 351     progname = argv[0];
 352     char *filename = NULL;
 353     for (int i=1; i<argc; i++) {
 354         if (strcmp(argv[i], "--help") == 0) {
 355             mode = MODE_HELP;
 356             printf("dragon - lightweight DnD source/target\n");
 357             printf("Usage: %s [OPTION] [FILENAME]\n", argv[0]);
 358             printf("  --and-exit,   -x  exit after a single completed drop\n");
 359             printf("  --target,     -t  act as a target instead of source\n");
 360             printf("  --keep,       -k  with --target, keep files to drag out\n");
 361             printf("  --print-path, -p  with --target, print file paths"
 362                     " instead of URIs\n");
 363             printf("  --all,        -a  drag all files at once\n");
 364             printf("  --verbose,    -v  be verbose\n");
 365             printf("  --help            show help\n");
 366             printf("  --version         show version details\n");
 367             exit(0);
 368         } else if (strcmp(argv[i], "--version") == 0) {
 369             mode = MODE_VERSION;
 370             puts("dragon " VERSION);
 371             puts("Copyright (C) 2014-2018 Michael Homer");
 372             puts("This program comes with ABSOLUTELY NO WARRANTY.");
 373             puts("See the source for copying conditions.");
 374             exit(0);
 375         } else if (strcmp(argv[i], "-v") == 0
 376                 || strcmp(argv[i], "--verbose") == 0) {
 377             verbose = true;
 378         } else if (strcmp(argv[i], "-t") == 0
 379                 || strcmp(argv[i], "--target") == 0) {
 380             mode = MODE_TARGET;
 381         } else if (strcmp(argv[i], "-x") == 0
 382                 || strcmp(argv[i], "--and-exit") == 0) {
 383             and_exit = true;
 384         } else if (strcmp(argv[i], "-k") == 0
 385                 || strcmp(argv[i], "--keep") == 0) {
 386             keep = true;
 387         } else if (strcmp(argv[i], "-p") == 0
 388                 || strcmp(argv[i], "--print-path") == 0) {
 389             print_path = true;
 390         } else if (strcmp(argv[i], "-a") == 0
 391                 || strcmp(argv[i], "--all") == 0) {
 392             drag_all = true;
 393         } else if (strcmp(argv[i], "-i") == 0
 394                 || strcmp(argv[i], "--icon-only") == 0) {
 395             icons_only = true;
 396         } else if (argv[i][0] == '-') {
 397             fprintf(stderr, "%s: error: unknown option `%s'.\n",
 398                     progname, argv[i]);
 399         }
 400     }
 401     setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
 402 
 403     GtkAccelGroup *accelgroup;
 404     GClosure *closure;
 405 
 406     gtk_init(&argc, &argv);
 407 
 408     icon_theme = gtk_icon_theme_get_default();
 409 
 410     window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
 411 
 412     closure = g_cclosure_new(G_CALLBACK(do_quit), NULL, NULL);
 413     accelgroup = gtk_accel_group_new();
 414     gtk_accel_group_connect(accelgroup, GDK_KEY_Escape, 0, 0, closure);
 415     gtk_window_add_accel_group(GTK_WINDOW(window), accelgroup);
 416 
 417     gtk_window_set_title(GTK_WINDOW(window), "Run");
 418     gtk_window_set_resizable(GTK_WINDOW(window), FALSE);
 419 
 420     g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
 421 
 422     vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 6);
 423 
 424     gtk_container_add(GTK_CONTAINER(window), vbox);
 425 
 426     gtk_window_set_title(GTK_WINDOW(window), "dragon");
 427 
 428     if (mode == MODE_TARGET) {
 429         target_mode();
 430         exit(0);
 431     }
 432 
 433     if (drag_all) {
 434        uri_collection = malloc(sizeof(char*) * ((argc > MAX_SIZE ? argc : MAX_SIZE) + 1));
 435        uri_count = 0;
 436     }
 437 
 438     bool had_filename = false;
 439     for (int i=1; i<argc; i++) {
 440         if (argv[i][0] != '-') {
 441             filename = argv[i];
 442             if (!is_uri(filename)) {
 443                 add_filename_button(filename);
 444             } else if (is_file_uri(filename)) {
 445                 GFile *file = g_file_new_for_uri(filename);
 446                 add_file_button(file);
 447             } else {
 448                 add_uri_button(filename);
 449             }
 450             had_filename = true;
 451         }
 452     }
 453     if (!had_filename) {
 454         printf("Usage: %s [OPTIONS] FILENAME\n", progname);
 455         exit(0);
 456     }
 457 
 458     gtk_widget_show_all(window);
 459 
 460     gtk_main();
 461 
 462     return 0;
 463 }