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