]> git.neil.brown.name Git - edlib.git/blob - lib-x11selection-gtk.c
TODO: clean out done items.
[edlib.git] / lib-x11selection-gtk.c
1 /*
2  * Copyright Neil Brown ©2020-2021 <neil@brown.name>
3  * May be distributed under terms of GPLv2 - see file:COPYING
4  *
5  * x11selection - integrate X11 clipboards with copybuf and selection.
6  *
7  * Use gtk_clipboard interfaces to provide the selection and recent copied
8  * content to other applications, and to use what is provided by those
9  * applications to satisfy internal requests.
10  *
11  * We overload copy:save to claim both PRIMARY and CLIPBOARD so other apps will
12  * ask us for content.  When asked we call copy:get to get the content, but see
13  * selections below.
14  * We overload copy:get to interpolate PRIMARY and CLIPBOARD into the list
15  * of copies, if they are exist, are not owned by us and only consider CLIPBOARD
16  * if it is different to PRIMARY.
17  *
18  * We also claim the edlib selection at startup on behalf of whichever X11
19  * application owns it.  If it is claimed from us, we claim ownership of PRIMARY.
20  * If it is committed, we ask for text from the owner of PRIMARY and save that.
21  * If we lose ownership of the PRIMARY, we reclaim the selection.
22  */
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #define PANE_DATA_TYPE struct xs_info
27 #include "core.h"
28
29 #ifndef __CHECKER__
30 #include <gtk/gtk.h>
31 #else
32 /* sparse/smatch don't like gtk.h, so provide our own declarations */
33 typedef void *gpointer;
34 typedef unsigned int guint;
35 typedef char gchar;
36 typedef struct _GdkDisplay {} GdkDisplay;
37 typedef struct _GtkTargetEntry {} GtkTargetEntry;
38 typedef struct _GtkTargetList {} GtkTargetList;
39 typedef struct _GtkClipboard {} GtkClipboard;
40 typedef struct _GtkSelectionData {} GtkSelectionData;
41 typedef int GdkAtom;
42
43 GdkAtom gdk_atom_intern(gchar *, int);
44 #define TRUE (1)
45 #define FALSE (0)
46
47 GdkDisplay *gdk_display_open(gchar*);
48 void gdk_display_close(GdkDisplay*);
49 GtkClipboard *gtk_clipboard_get_for_display(GdkDisplay*, GdkAtom);
50 void gtk_clipboard_set_with_data(GtkClipboard*, GtkTargetEntry*, guint,
51                                  void (*get)(GtkClipboard *, GtkSelectionData *,
52                                              guint, gpointer d safe),
53                                  void (*clear)(GtkClipboard *, gpointer safe),
54                                  gpointer d safe);
55 void gtk_clipboard_clear(GtkClipboard*);
56 void gtk_selection_data_set_text(GtkSelectionData *, gchar *, guint);
57
58 gchar *gtk_clipboard_wait_for_text(GtkClipboard*);
59 int gtk_clipboard_wait_is_text_available(GtkClipboard*);
60 void g_free(gpointer);
61
62 GtkTargetList *gtk_target_list_new(gpointer, guint);
63 void gtk_target_list_add_text_targets(GtkTargetList *, guint);
64 void gtk_target_list_unref(GtkTargetList *);
65 GtkTargetEntry *gtk_target_table_new_from_list(GtkTargetList *, int *);
66 void gtk_target_table_free(GtkTargetEntry*, int);
67
68 #endif
69
70 struct xs_info {
71         struct pane             *self safe;
72         GdkDisplay              *display;
73         struct cb {
74                 /* 'data' is allocated space that stores a pointer to this
75                  * xs_info.  Data is given the gtk has a handle.
76                  */
77                 struct xs_info  **data;
78                 int             saved;
79                 GtkClipboard    *cb;
80         } primary, clipboard;
81         GtkTargetEntry          *text_targets;
82         int                     n_text_targets;
83 };
84 #include "core-pane.h"
85
86 static void do_get(GtkClipboard *cb, GtkSelectionData *sd,
87                    guint info, gpointer vdata safe)
88 {
89         /* Another X11 application has asked for clipboard data */
90         struct xs_info **data = vdata;
91         struct xs_info *xsi = *data;
92         char *s;
93
94         if (!xsi)
95                 return;
96         if (cb == xsi->primary.cb)
97                 /* If there is an active selection, now if the time for
98                  * the content to be copied.
99                  */
100                 call("selection:commit", xsi->self);
101
102         s = call_ret(strsave, "copy:get", xsi->self);
103         if (!s)
104                 s = "";
105         gtk_selection_data_set_text(sd, s, strlen(s));
106 }
107
108 static void do_clear(GtkClipboard *cb, gpointer vdata safe)
109 {
110         struct xs_info **data = vdata;
111         struct xs_info *xsi = *data;
112
113         if (!xsi)
114                 return;
115         /* Some other X11 application wants us to release ownership
116          * of the clipboard.
117          */
118         if (data == xsi->primary.data) {
119                 /* This means some other application now has a "selection",
120                  * so we claim it on their behalf.
121                  */
122                 xsi->primary.data = NULL;
123                 call("selection:claim", xsi->self);
124         }
125
126         if (data == xsi->clipboard.data)
127                 xsi->clipboard.data = NULL;
128
129         free(data);
130 }
131
132 static void claim_primary(struct xs_info *xsi safe)
133 {
134         struct xs_info **data;
135
136         data = malloc(sizeof(*data));
137         *data = xsi;
138
139         gtk_clipboard_set_with_data(xsi->primary.cb,
140                                     xsi->text_targets,
141                                     xsi->n_text_targets,
142                                     do_get, do_clear, data);
143         xsi->primary.data = data;
144         xsi->primary.saved = 0;
145 }
146
147 static void claim_both(struct xs_info *xsi safe)
148 {
149         struct xs_info **data;
150
151         claim_primary(xsi);
152
153         data = malloc(sizeof(*data));
154         *data = xsi;
155
156         gtk_clipboard_set_with_data(xsi->clipboard.cb,
157                                     xsi->text_targets,
158                                     xsi->n_text_targets,
159                                     do_get, do_clear, data);
160         xsi->clipboard.data = data;
161         xsi->clipboard.saved = 0;
162 }
163
164 DEF_CMD(xs_copy_save)
165 {
166         struct xs_info *xsi = ci->home->data;
167
168         claim_both(xsi);
169         /* Some edlib pane own the selection, so we renounce any ownership
170          * by any X11 application.
171          */
172         call("selection:discard", ci->home);
173         return Efallthrough;
174 }
175
176 DEF_CMD(xs_copy_get)
177 {
178         struct xs_info *xsi = ci->home->data;
179         int num = ci->num;
180
181         if (xsi->clipboard.data == NULL) {
182                 if (num == 0) {
183                         /* Return CLIPBOARD if it exists */
184                         gchar *s = NULL;
185
186                         if (gtk_clipboard_wait_is_text_available(
187                                     xsi->clipboard.cb))
188                                 s = gtk_clipboard_wait_for_text(
189                                         xsi->clipboard.cb);
190                         if (s && *s) {
191                                 comm_call(ci->comm2, "cb", ci->focus,
192                                           0, NULL, s);
193                                 num -= 1;
194                         }
195                         g_free(s);
196                 } else {
197                         /* Just check if a string exists */
198                         if (gtk_clipboard_wait_is_text_available(
199                                     xsi->clipboard.cb))
200                                 num -= 1;
201                 }
202         }
203         if (num < 0)
204                 return 1;
205
206         return call_comm(ci->key, ci->home->parent, ci->comm2, num);
207 }
208
209 DEF_CMD(xs_sel_claimed)
210 {
211         struct xs_info *xsi = ci->home->data;
212
213         if (ci->focus != ci->home)
214                 /* not for me */
215                 return Efallthrough;
216         /* Some other pane holds the selection, so better tell
217          * other X11 clients
218          */
219         claim_primary(xsi);
220         return 1;
221 }
222
223 DEF_CMD(xs_sel_commit)
224 {
225         struct xs_info *xsi = ci->home->data;
226         gchar *s;
227
228         /* Someone wants to paste the selection */
229         /* Record PRIMARY if it exists */
230
231         if (ci->focus != ci->home)
232                 /* not for me */
233                 return Efallthrough;
234
235         if (xsi->primary.data || xsi->primary.saved)
236                 /* We own the primary, so nothing to do */
237                 return 1;
238
239         if (!xsi->clipboard.data && !xsi->clipboard.saved) {
240                 /* get the clipboard first - to make sure it is available
241                  * as second saved text.
242                  */
243                 s = NULL;
244                 if (gtk_clipboard_wait_is_text_available(
245                             xsi->clipboard.cb))
246                         s = gtk_clipboard_wait_for_text(xsi->clipboard.cb);
247                 if (s && *s) {
248                         call("copy:save", ci->home->parent,
249                              0, NULL, s);
250                         xsi->clipboard.saved = 1;
251                 }
252                 g_free(s);
253         }
254         s = NULL;
255         if (gtk_clipboard_wait_is_text_available(xsi->primary.cb))
256                 s = gtk_clipboard_wait_for_text(xsi->primary.cb);
257         if (s && *s) {
258                 call("copy:save", ci->home->parent,
259                      0, NULL, s);
260                 xsi->primary.saved = 1;
261         }
262         g_free(s);
263
264         return Efallthrough;
265 }
266
267 DEF_CMD_CLOSED(xs_close)
268 {
269         struct xs_info *xsi = ci->home->data;
270
271         if (xsi->primary.data)
272                 gtk_clipboard_clear(xsi->primary.cb);
273         if (xsi->clipboard.data)
274                 gtk_clipboard_clear(xsi->clipboard.cb);
275         free(xsi->primary.data);
276         free(xsi->clipboard.data);
277         gtk_target_table_free(xsi->text_targets, xsi->n_text_targets);
278         gdk_display_close(xsi->display);
279
280         return 1;
281 }
282
283 DEF_CMD(xs_clone)
284 {
285         struct pane *p;
286
287         p = call_ret(pane, "attach-x11selection", ci->focus);
288         pane_clone_children(ci->home, p);
289         return 1;
290 }
291
292 static struct map *xs_map;
293 DEF_LOOKUP_CMD(xs_handle, xs_map);
294
295 DEF_CMD(xs_attach)
296 {
297         char *d;
298         struct xs_info *xsi;
299         struct pane *p;
300         GdkAtom primary, clipboard;
301         GtkTargetList *list;
302         GdkDisplay *dis;
303
304         d = pane_attr_get(ci->focus, "DISPLAY");
305         if (!d || !*d)
306                 return 1;
307         dis = gdk_display_open(d);
308         if (!dis)
309                 return 1;
310
311         call("attach-glibevents", ci->focus);
312         p = pane_register(ci->focus, 0, &xs_handle.c);
313         if (!p)
314                 return Efail;
315         xsi = p->data;
316
317         xsi->display = dis;
318         primary = gdk_atom_intern("PRIMARY", TRUE);
319         clipboard = gdk_atom_intern("CLIPBOARD", TRUE);
320         xsi->primary.cb = gtk_clipboard_get_for_display(dis, primary);
321         xsi->clipboard.cb = gtk_clipboard_get_for_display(dis, clipboard);
322
323         list = gtk_target_list_new(NULL, 0);
324         gtk_target_list_add_text_targets(list, 0);
325         xsi->text_targets =
326                 gtk_target_table_new_from_list(list, &xsi->n_text_targets);
327         gtk_target_list_unref (list);
328
329         claim_both(xsi);
330
331         xsi->self = p;
332         return comm_call(ci->comm2, "cb:attach", xsi->self);
333 }
334
335 void edlib_init(struct pane *ed safe)
336 {
337         if (!xs_map) {
338                 xs_map = key_alloc();
339                 key_add(xs_map, "copy:save", &xs_copy_save);
340                 key_add(xs_map, "copy:get", &xs_copy_get);
341                 key_add(xs_map, "Notify:selection:claimed", &xs_sel_claimed);
342                 key_add(xs_map, "Notify:selection:commit", &xs_sel_commit);
343                 key_add(xs_map, "Clone", &xs_clone);
344                 key_add(xs_map, "Close", &xs_close);
345         }
346
347         call_comm("global-set-command", ed, &xs_attach,
348                   0, NULL, "attach-x11selection");
349 }