summaryrefslogtreecommitdiff
path: root/src/clipboard.c
blob: 364a70b2ca0068ca34ef295393f5655cc7ebf30c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/* clipboard.c — copy image (PNG) or URIs to GdkClipboard. */
#include "clipboard.h"
#include "loader/loader.h"

#include <gdk-pixbuf/gdk-pixbuf.h>

static void
_copy_image_thread(GTask *p_task, gpointer p_src, gpointer p_data,
                   GCancellable *p_cancel) {
   (void)p_data;
   GError     *p_err = NULL;
   GdkTexture *p_tex = loader_load((GFile *)p_src, p_cancel, &p_err);
   if (p_tex == NULL) {
      g_task_return_error(p_task, p_err);
   } else {
      g_task_return_pointer(p_task, p_tex, (GDestroyNotify)g_object_unref);
   }
}

void
clipboard_copy_image_async(GdkClipboard *p_clip, GFile *p_file,
                           GCancellable *p_cancel, GAsyncReadyCallback p_cb,
                           gpointer p_data) {
   g_return_if_fail(GDK_IS_CLIPBOARD(p_clip));
   g_return_if_fail(G_IS_FILE(p_file));
   GTask *p_task = g_task_new(p_file, p_cancel, p_cb, p_data);
   g_task_run_in_thread(p_task, _copy_image_thread);
   g_object_unref(p_task);
}

static void
_clip_image_finish_cb(GObject *p_src, GAsyncResult *p_res, gpointer p_data) {
   GdkClipboard *p_clip = GDK_CLIPBOARD(p_data);
   GError       *p_err  = NULL;
   GdkTexture   *p_tex  = g_task_propagate_pointer(G_TASK(p_res), &p_err);
   if (p_tex != NULL) {
      GdkContentProvider *p_prov =
         gdk_content_provider_new_typed(GDK_TYPE_TEXTURE, p_tex);
      gdk_clipboard_set_content(p_clip, p_prov);
      g_object_unref(p_prov);
      g_object_unref(p_tex);
   } else {
      g_clear_error(&p_err);
   }
   g_object_unref(p_data);
}

/* Convenience: start the copy and set the clipboard when done. */
void
ggaze_clipboard_copy_image(GdkClipboard *p_clip, GFile *p_file) {
   clipboard_copy_image_async(p_clip, p_file, NULL, _clip_image_finish_cb,
                              g_object_ref(p_clip));
}

gboolean
clipboard_copy_image_finish(GAsyncResult *p_res, GError **p_err) {
   g_return_val_if_fail(G_IS_TASK(p_res), FALSE);
   GdkTexture *p_tex = g_task_propagate_pointer(G_TASK(p_res), p_err);
   if (p_tex) {
      g_object_unref(p_tex);
      return TRUE;
   }
   return FALSE;
}

/* Build a GdkContentProvider that offers the marked files as BOTH
 * `text/uri-list` (RFC 2483: CRLF-terminated URI lines, the format file
 * managers request) and `text/plain` (a newline-joined list of local PATHS
 * so pasting into a text field yields readable paths rather than raw URIs;
 * if a file has no local path, its URI is used for that line instead). The
 * two byte-buffers are wrapped in a union so a target requesting either MIME
 * type is satisfied. Returns a new ref the caller must unref, or NULL when
 * the file list is empty/NULL.
 *
 * An empty list returns NULL rather than an empty provider: in practice
 * clipboard_copy_uris is only invoked when marks exist, and returning NULL
 * lets the caller leave the previous clipboard content untouched instead of
 * replacing it with an empty payload (which GDK's bytes provider refuses to
 * serialize anyway). */
GdkContentProvider *
clipboard_build_uri_provider(GList *p_files) {
   if (p_files == NULL) {
      return (NULL);
   }
   GString *p_uris  = g_string_new(NULL); /* text/uri-list body   */
   GString *p_plain = g_string_new(NULL); /* text/plain body      */
   for (GList *it = p_files; it; it = it->next) {
      GFile *p_f    = G_FILE(it->data);
      char  *c_uri  = g_file_get_uri(p_f);
      char  *c_path = g_file_get_path(p_f);
      /* RFC 2483: each record terminated by CRLF, incl. the last. */
      g_string_append_printf(p_uris, "%s\r\n", c_uri);
      /* Prefer the local path for human-readable text/plain paste;
       * fall back to the URI for non-local (e.g. trash://) files. */
      g_string_append_printf(p_plain, "%s\n", c_path != NULL ? c_path : c_uri);
      g_free(c_uri);
      g_free(c_path);
   }
   GBytes *p_uri_bytes   = g_bytes_new_take(p_uris->str, p_uris->len);
   GBytes *p_plain_bytes = g_bytes_new_take(p_plain->str, p_plain->len);
   /* g_bytes_new_take freed the GString buffers; only the structs remain. */
   g_string_free(p_uris, FALSE);
   g_string_free(p_plain, FALSE);
   GdkContentProvider *p_provs[2] = {
      gdk_content_provider_new_for_bytes("text/uri-list", p_uri_bytes),
      gdk_content_provider_new_for_bytes("text/plain", p_plain_bytes),
   };
   /* new_for_bytes refs/copies the bytes; we can release our ref now. */
   g_bytes_unref(p_uri_bytes);
   g_bytes_unref(p_plain_bytes);
   GdkContentProvider *p_union =
      gdk_content_provider_new_union(p_provs, G_N_ELEMENTS(p_provs));
   /* new_union "takes ownership" of the sub-providers: it steals our refs
    * and frees them when the union is disposed, so we must NOT unref them
    * here (doing so would double-free them on the union's dispose). */
   return (p_union);
}

void
clipboard_copy_uris(GdkClipboard *p_clip, GList *p_files) {
   g_return_if_fail(GDK_IS_CLIPBOARD(p_clip));
   GdkContentProvider *p_prov = clipboard_build_uri_provider(p_files);
   if (p_prov == NULL) {
      return; /* empty list: leave the clipboard untouched. */
   }
   /* gdk_clipboard_set_content takes its own ref on the provider; we still
    * own our initial ref from clipboard_build_uri_provider, so drop it. */
   gdk_clipboard_set_content(p_clip, p_prov);
   g_object_unref(p_prov);
}