From: Simon Tatham Date: Wed, 2 Sep 2015 20:37:33 +0000 (+0100) Subject: Initial support for clipboard on OS X. X-Git-Tag: 0.68~382 X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=commitdiff_plain;h=a50da0e30952c4edfacebb1ba01b5b91b0fee63c;p=PuTTY.git Initial support for clipboard on OS X. Rather than trying to get my existing hugely complicated X-style clipboard code to somehow work with the Quartz GTK back end, I've written an entirely new and much simpler alternative clipboard handler usnig the higher-leve GtkClipboard interface. It assumes all clipboard text can be converted to and from UTF-8 sensibly (which isn't a good assumption on all front ends, but on OS X I think it's reasonable), and it talks to GDK_SELECTION_CLIPBOARD rather than PRIMARY, which is the only clipboard OS X has. I had to do a fiddly thing to cope with the fact that each call to gtk_clipboard_set_with_data caused a call to the clipboard clear function left over from the previous set of data, so I had to avoid mistaking that for a clipboard-clear for the _new_ data and immediately deselecting it. I did that by allocating a distinct placeholder object in memory for each instance of the copy operation, so that I can tell whether a clipboard-clear is for the current copy or a previous one. This is only very basic support which demonstrates successful copying and pasting is at least possible. For a sensible OS X implementation we'll need a more believable means of generating a paste UI action (it's quite easy to find a Mac on which neither Shift-Ins nor the third mouse button even exists!). Also, after the trouble I had with the clipboard-clear event, it's a bit annoying to find that it _doesn't_ seem to get called when another application becomes the clipboard owner. That may just be something we have to put up with, if I can't find any reason why it's failing. --- diff --git a/unix/gtkwin.c b/unix/gtkwin.c index 47251ff0..8b10535c 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -74,6 +74,8 @@ extern int use_pty_argv; */ static guint timer_id = 0; +struct clipboard_data_instance; + struct gui_data { GtkWidget *window, *area, *sbar; GtkBox *hbox; @@ -98,11 +100,16 @@ struct gui_data { #if !GTK_CHECK_VERSION(3,0,0) GdkColormap *colmap; #endif - wchar_t *pastein_data; int direct_to_font; + wchar_t *pastein_data; int pastein_data_len; +#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 + GtkClipboard *clipboard; + struct clipboard_data_instance *current_cdi; +#else char *pasteout_data, *pasteout_data_ctext, *pasteout_data_utf8; int pasteout_data_len, pasteout_data_ctext_len, pasteout_data_utf8_len; +#endif int font_width, font_height; int width, height; int ignore_sbar; @@ -911,7 +918,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) /* * Neither does Shift-Ins. */ - if (event->keyval == GDK_KEY_Insert && + if (event->keyval == GDK_KEY_Return && (event->state & GDK_SHIFT_MASK)) { request_paste(inst); return TRUE; @@ -2179,35 +2186,164 @@ void palette_reset(void *frontend) } } -/* Ensure that all the cut buffers exist - according to the ICCCM, we must - * do this before we start using cut buffers. +#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 + +/* ---------------------------------------------------------------------- + * Clipboard handling, using the high-level GtkClipboard interface in + * as hands-off a way as possible. We write and read the clipboard as + * UTF-8 text, and let GTK deal with converting to any other text + * formats it feels like. */ -void init_cutbuffers() + +void init_clipboard(struct gui_data *inst) { -#ifndef NOT_X_WINDOWS - unsigned char empty[] = ""; - Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); - XChangeProperty(disp, GDK_ROOT_WINDOW(), - XA_CUT_BUFFER0, XA_STRING, 8, PropModeAppend, empty, 0); - XChangeProperty(disp, GDK_ROOT_WINDOW(), - XA_CUT_BUFFER1, XA_STRING, 8, PropModeAppend, empty, 0); - XChangeProperty(disp, GDK_ROOT_WINDOW(), - XA_CUT_BUFFER2, XA_STRING, 8, PropModeAppend, empty, 0); - XChangeProperty(disp, GDK_ROOT_WINDOW(), - XA_CUT_BUFFER3, XA_STRING, 8, PropModeAppend, empty, 0); - XChangeProperty(disp, GDK_ROOT_WINDOW(), - XA_CUT_BUFFER4, XA_STRING, 8, PropModeAppend, empty, 0); - XChangeProperty(disp, GDK_ROOT_WINDOW(), - XA_CUT_BUFFER5, XA_STRING, 8, PropModeAppend, empty, 0); - XChangeProperty(disp, GDK_ROOT_WINDOW(), - XA_CUT_BUFFER6, XA_STRING, 8, PropModeAppend, empty, 0); - XChangeProperty(disp, GDK_ROOT_WINDOW(), - XA_CUT_BUFFER7, XA_STRING, 8, PropModeAppend, empty, 0); -#endif + inst->clipboard = gtk_clipboard_get_for_display(gdk_display_get_default(), + DEFAULT_CLIPBOARD); +} + +/* + * Because calling gtk_clipboard_set_with_data triggers a call to the + * clipboard_clear function from the last time, we need to arrange a + * way to distinguish a real call to clipboard_clear for the _new_ + * instance of the clipboard data from the leftover call for the + * outgoing one. We do this by setting the user data field in our + * gtk_clipboard_set_with_data() call, instead of the obvious pointer + * to 'inst', to one of these. + */ +struct clipboard_data_instance { + struct gui_data *inst; + char *pasteout_data_utf8; + int pasteout_data_utf8_len; +}; + +static void clipboard_provide_data(GtkClipboard *clipboard, + GtkSelectionData *selection_data, + guint info, gpointer data) +{ + struct clipboard_data_instance *cdi = + (struct clipboard_data_instance *)data; + struct gui_data *inst = cdi->inst; + + if (inst->current_cdi == cdi) { + gtk_selection_data_set_text(selection_data, cdi->pasteout_data_utf8, + cdi->pasteout_data_utf8_len); + } +} + +static void clipboard_clear(GtkClipboard *clipboard, gpointer data) +{ + struct clipboard_data_instance *cdi = + (struct clipboard_data_instance *)data; + struct gui_data *inst = cdi->inst; + + if (inst->current_cdi == cdi) { + term_deselect(inst->term); + inst->current_cdi = NULL; + } + sfree(cdi->pasteout_data_utf8); + sfree(cdi); +} + +void write_clip(void *frontend, wchar_t *data, int *attr, int len, + int must_deselect) +{ + struct gui_data *inst = (struct gui_data *)frontend; + struct clipboard_data_instance *cdi; + + if (inst->direct_to_font) { + /* In this clipboard mode, we just can't paste if we're in + * direct-to-font mode. Fortunately, that shouldn't be + * important, because we'll only use this clipboard handling + * code on systems where that kind of font doesn't exist + * anyway. */ + return; + } + + cdi = snew(struct clipboard_data_instance); + cdi->inst = inst; + inst->current_cdi = cdi; + cdi->pasteout_data_utf8 = snewn(len*6, char); + { + const wchar_t *tmp = data; + int tmplen = len; + cdi->pasteout_data_utf8_len = + charset_from_unicode(&tmp, &tmplen, cdi->pasteout_data_utf8, + len*6, CS_UTF8, NULL, NULL, 0); + } + + /* + * It would be nice to just call gtk_clipboard_set_text() in place + * of all of the faffing below. Unfortunately, that won't give me + * access to the clipboard-clear event, which we use to visually + * deselect text in the terminal. + */ + { + GtkTargetList *targetlist; + GtkTargetEntry *targettable; + gint n_targets; + + targetlist = gtk_target_list_new(NULL, 0); + gtk_target_list_add_text_targets(targetlist, 0); + targettable = gtk_target_table_new_from_list(targetlist, &n_targets); + gtk_clipboard_set_with_data(inst->clipboard, targettable, n_targets, + clipboard_provide_data, clipboard_clear, + cdi); + gtk_target_table_free(targettable, n_targets); + gtk_target_list_unref(targetlist); + } +} + +static void clipboard_text_received(GtkClipboard *clipboard, + const gchar *text, gpointer data) +{ + struct gui_data *inst = (struct gui_data *)data; + int length; + + if (!text) + return; + + length = strlen(text); + + if (inst->pastein_data) + sfree(inst->pastein_data); + + inst->pastein_data = snewn(length, wchar_t); + inst->pastein_data_len = mb_to_wc(CS_UTF8, 0, text, length, + inst->pastein_data, length); + + term_do_paste(inst->term); } +void request_paste(void *frontend) +{ + struct gui_data *inst = (struct gui_data *)frontend; + gtk_clipboard_request_text(inst->clipboard, clipboard_text_received, inst); +} + +#else /* JUST_USE_GTK_CLIPBOARD_UTF8 */ + +/* ---------------------------------------------------------------------- + * Clipboard handling for X, using the low-level gtk_selection_* + * interface, handling conversions to fiddly things like compound text + * ourselves, and storing in X cut buffers too. + * + * This version of the clipboard code has to be kept around for GTK1, + * which doesn't have the higher-level GtkClipboard interface at all. + * And since it works on GTK2 and GTK3 too and has had a good few + * years of shakedown and bug fixing, we might as well keep using it + * where it's applicable. + * + * It's _possible_ that we might be able to replicate all the + * important wrinkles of this code in GtkClipboard. (In particular, + * cut buffers or local analogue look as if they might be accessible + * via gtk_clipboard_set_can_store(), and delivering text in + * non-Unicode formats only in the direct-to-font case ought to be + * possible if we can figure out the right set of things to put in the + * GtkTargetList.) But that work can wait until there's a need for it! + */ + /* Store the data in a cut-buffer. */ -void store_cutbuffer(char * ptr, int len) +static void store_cutbuffer(char * ptr, int len) { #ifndef NOT_X_WINDOWS Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); @@ -2220,7 +2356,7 @@ void store_cutbuffer(char * ptr, int len) /* Retrieve data from a cut-buffer. * Returned data needs to be freed with XFree(). */ -char * retrieve_cutbuffer(int * nbytes) +static char *retrieve_cutbuffer(int *nbytes) { #ifndef NOT_X_WINDOWS Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); @@ -2236,7 +2372,8 @@ char * retrieve_cutbuffer(int * nbytes) #endif } -void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_deselect) +void write_clip(void *frontend, wchar_t *data, int *attr, int len, + int must_deselect) { struct gui_data *inst = (struct gui_data *)frontend; if (inst->pasteout_data) @@ -2334,8 +2471,8 @@ void write_clip(void *frontend, wchar_t * data, int *attr, int len, int must_des term_deselect(inst->term); } -void selection_get(GtkWidget *widget, GtkSelectionData *seldata, - guint info, guint time_stamp, gpointer data) +static void selection_get(GtkWidget *widget, GtkSelectionData *seldata, + guint info, guint time_stamp, gpointer data) { struct gui_data *inst = (struct gui_data *)data; GdkAtom target = gtk_selection_data_get_target(seldata); @@ -2353,8 +2490,8 @@ void selection_get(GtkWidget *widget, GtkSelectionData *seldata, inst->pasteout_data_len); } -gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata, - gpointer data) +static gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata, + gpointer data) { struct gui_data *inst = (struct gui_data *)data; @@ -2405,10 +2542,8 @@ void request_paste(void *frontend) } } -gint idle_paste_func(gpointer data); /* forward ref */ - -void selection_received(GtkWidget *widget, GtkSelectionData *seldata, - guint time, gpointer data) +static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, + guint time, gpointer data) { struct gui_data *inst = (struct gui_data *)data; char *text; @@ -2529,6 +2664,48 @@ void selection_received(GtkWidget *widget, GtkSelectionData *seldata, #endif } +void init_clipboard(struct gui_data *inst) +{ +#ifndef NOT_X_WINDOWS + /* + * Ensure that all the cut buffers exist - according to the ICCCM, + * we must do this before we start using cut buffers. + */ + unsigned char empty[] = ""; + Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); + XChangeProperty(disp, GDK_ROOT_WINDOW(), + XA_CUT_BUFFER0, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(disp, GDK_ROOT_WINDOW(), + XA_CUT_BUFFER1, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(disp, GDK_ROOT_WINDOW(), + XA_CUT_BUFFER2, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(disp, GDK_ROOT_WINDOW(), + XA_CUT_BUFFER3, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(disp, GDK_ROOT_WINDOW(), + XA_CUT_BUFFER4, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(disp, GDK_ROOT_WINDOW(), + XA_CUT_BUFFER5, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(disp, GDK_ROOT_WINDOW(), + XA_CUT_BUFFER6, XA_STRING, 8, PropModeAppend, empty, 0); + XChangeProperty(disp, GDK_ROOT_WINDOW(), + XA_CUT_BUFFER7, XA_STRING, 8, PropModeAppend, empty, 0); +#endif + + g_signal_connect(G_OBJECT(inst->area), "selection_received", + G_CALLBACK(selection_received), inst); + g_signal_connect(G_OBJECT(inst->area), "selection_get", + G_CALLBACK(selection_get), inst); + g_signal_connect(G_OBJECT(inst->area), "selection_clear_event", + G_CALLBACK(selection_clear), inst); +} + +/* + * End of selection/clipboard handling. + * ---------------------------------------------------------------------- + */ + +#endif /* JUST_USE_GTK_CLIPBOARD_UTF8 */ + void get_clip(void *frontend, wchar_t ** p, int *len) { struct gui_data *inst = (struct gui_data *)frontend; @@ -4407,8 +4584,6 @@ int pt_main(int argc, char **argv) exit(1); } } - init_cutbuffers(); - inst->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); { const char *winclass = conf_get_str(inst->conf, CONF_winclass); @@ -4426,6 +4601,8 @@ int pt_main(int argc, char **argv) inst->height = conf_get_int(inst->conf, CONF_height); cache_conf_values(inst); + init_clipboard(inst); + { int w = inst->font_width * inst->width + 2*inst->window_border; int h = inst->font_height * inst->height + 2*inst->window_border; @@ -4507,12 +4684,6 @@ int pt_main(int argc, char **argv) #endif g_signal_connect(G_OBJECT(inst->area), "motion_notify_event", G_CALLBACK(motion_event), inst); - g_signal_connect(G_OBJECT(inst->area), "selection_received", - G_CALLBACK(selection_received), inst); - g_signal_connect(G_OBJECT(inst->area), "selection_get", - G_CALLBACK(selection_get), inst); - g_signal_connect(G_OBJECT(inst->area), "selection_clear_event", - G_CALLBACK(selection_clear), inst); #if GTK_CHECK_VERSION(2,0,0) g_signal_connect(G_OBJECT(inst->imc), "commit", G_CALLBACK(input_method_commit_event), inst); diff --git a/unix/unix.h b/unix/unix.h index 1c8f2e8d..b71d9210 100644 --- a/unix/unix.h +++ b/unix/unix.h @@ -27,6 +27,8 @@ #define OSX_META_KEY_CONFIG /* two possible Meta keys to choose from */ /* this potential one of the Meta keys needs manual handling */ #define META_MANUAL_MASK (GDK_MOD1_MASK) +#define JUST_USE_GTK_CLIPBOARD_UTF8 /* low-level gdk_selection_* fails */ +#define DEFAULT_CLIPBOARD GDK_SELECTION_CLIPBOARD /* OS X has no PRIMARY */ #endif struct Filename {