From 41aa675a5b7ca1d5055e444ae7146fcc13b140c4 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 10 Dec 2017 10:23:39 +0000 Subject: [PATCH] Make gtkwin.c able to support multiple selections. All the data fields referring to the selection in 'struct gui_data' have been pulled out into a separate structure of which there are now multiple instances, and I've plumbed through what should be the right pointers and integer ids to everywhere they should go. So now the GTK front end defines CLIP_PRIMARY and CLIP_CLIPBOARD in place of the temporary cop-out CLIP_SYSTEM from the previous commit, and copying and pasting can be done via either one. The defaults should be the same as before, except that now the non-Mac versions of the GtkApplication front ends will access CLIP_PRIMARY in response to most actions but the 'Paste' menu item will paste from CLIP_CLIPBOARD. (That's mostly just as a demonstration that accessing multiple clipboards even works.) --- unix/gtkwin.c | 278 +++++++++++++++++++++++++++++++------------------- unix/unix.h | 18 +++- 2 files changed, 189 insertions(+), 107 deletions(-) diff --git a/unix/gtkwin.c b/unix/gtkwin.c index 99144557..01bf6f29 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -54,6 +54,19 @@ GdkAtom compound_text_atom, utf8_string_atom; struct clipboard_data_instance; +struct clipboard_state { + struct gui_data *inst; + int clipboard; + GdkAtom atom; +#ifdef JUST_USE_GTK_CLIPBOARD_UTF8 + GtkClipboard *gtkclipboard; + 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 +}; + struct gui_data { GtkWidget *window, *area, *sbar; gboolean sbar_visible; @@ -101,13 +114,7 @@ struct gui_data { GdkColormap *colmap; #endif int direct_to_font; -#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 + struct clipboard_state clipstates[N_CLIPBOARDS]; int font_width, font_height; int width, height; int ignore_sbar; @@ -1022,7 +1029,7 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) #ifdef KEY_EVENT_DIAGNOSTICS debug((" - Shift-Insert paste\n")); #endif - term_request_paste(inst->term, CLIP_SYSTEM); + term_request_paste(inst->term, SHIFT_INS_CLIPBOARD); return TRUE; } @@ -2490,10 +2497,26 @@ void palette_reset(void *frontend) * formats it feels like. */ -void init_clipboard(struct gui_data *inst) +static void init_one_clipboard(struct gui_data *inst, int clipboard, + GdkAtom atom) { - inst->clipboard = gtk_clipboard_get_for_display(gdk_display_get_default(), - DEFAULT_CLIPBOARD); + struct clipboard_state *state = &inst->clipstates[clipboard]; + + state->inst = inst; + state->atom = atom; + state->clipboard = clipboard; + + state->gtkclipboard = gtk_clipboard_get_for_display( + gdk_display_get_default(), atom); + + g_object_set_data(G_OBJECT(state->gtkclipboard), "user-data", state); +} + +int init_clipboard(struct gui_data *inst) +{ + init_one_clipboard(inst, CLIP_PRIMARY, GDK_SELECTION_PRIMARY); + init_one_clipboard(inst, CLIP_CLIPBOARD, GDK_SELECTION_CLIPBOARD); + return TRUE; } /* @@ -2506,7 +2529,6 @@ void init_clipboard(struct gui_data *inst) * to 'inst', to one of these. */ struct clipboard_data_instance { - struct gui_data *inst; char *pasteout_data_utf8; int pasteout_data_utf8_len; }; @@ -2517,9 +2539,10 @@ static void clipboard_provide_data(GtkClipboard *clipboard, { struct clipboard_data_instance *cdi = (struct clipboard_data_instance *)data; - struct gui_data *inst = cdi->inst; + struct clipboard_state *state = (struct clipboard_state *) + g_object_get_data(G_OBJECT(clipboard), "user-data"); - if (inst->current_cdi == cdi) { + if (state->current_cdi == cdi) { gtk_selection_data_set_text(selection_data, cdi->pasteout_data_utf8, cdi->pasteout_data_utf8_len); } @@ -2529,21 +2552,23 @@ static void clipboard_clear(GtkClipboard *clipboard, gpointer data) { struct clipboard_data_instance *cdi = (struct clipboard_data_instance *)data; - struct gui_data *inst = cdi->inst; + struct clipboard_state *state = (struct clipboard_state *) + g_object_get_data(G_OBJECT(clipboard), "user-data"); - if (inst->current_cdi == cdi) { - term_lost_clipboard_ownership(inst->term, CLIP_SYSTEM); - inst->current_cdi = NULL; + if (state->current_cdi == cdi) { + term_lost_clipboard_ownership(state->inst->term, state->clipboard); + state->current_cdi = NULL; } sfree(cdi->pasteout_data_utf8); sfree(cdi); } void write_clip(void *frontend, int clipboard, - char_t *data, int *attr, truecolour *truecolour, int len, + wchar_t *data, int *attr, truecolour *truecolour, int len, int must_deselect) { struct gui_data *inst = (struct gui_data *)frontend; + struct clipboard_state *state = &inst->clipstates[clipboard]; struct clipboard_data_instance *cdi; if (inst->direct_to_font) { @@ -2556,8 +2581,7 @@ void write_clip(void *frontend, int clipboard, } cdi = snew(struct clipboard_data_instance); - cdi->inst = inst; - inst->current_cdi = cdi; + state->current_cdi = cdi; cdi->pasteout_data_utf8 = snewn(len*6, char); { const wchar_t *tmp = data; @@ -2581,9 +2605,9 @@ void write_clip(void *frontend, int clipboard, 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_clipboard_set_with_data(state->gtkclipboard, targettable, + n_targets, clipboard_provide_data, + clipboard_clear, cdi); gtk_target_table_free(targettable, n_targets); gtk_target_list_unref(targetlist); } @@ -2613,8 +2637,9 @@ static void clipboard_text_received(GtkClipboard *clipboard, void frontend_request_paste(void *frontend, int clipboard) { struct gui_data *inst = (struct gui_data *)frontend; - assert(clipboard == CLIP_SYSTEM); - gtk_clipboard_request_text(inst->clipboard, clipboard_text_received, inst); + struct clipboard_state *state = &inst->clipstates[clipboard]; + gtk_clipboard_request_text(state->gtkclipboard, + clipboard_text_received, inst); } #else /* JUST_USE_GTK_CLIPBOARD_UTF8 */ @@ -2674,12 +2699,14 @@ void write_clip(void *frontend, int clipboard, int must_deselect) { struct gui_data *inst = (struct gui_data *)frontend; - if (inst->pasteout_data) - sfree(inst->pasteout_data); - if (inst->pasteout_data_ctext) - sfree(inst->pasteout_data_ctext); - if (inst->pasteout_data_utf8) - sfree(inst->pasteout_data_utf8); + struct clipboard_state *state = &inst->clipstates[clipboard]; + + if (state->pasteout_data) + sfree(state->pasteout_data); + if (state->pasteout_data_ctext) + sfree(state->pasteout_data_ctext); + if (state->pasteout_data_utf8) + sfree(state->pasteout_data_utf8); /* * Set up UTF-8 and compound text paste data. This only happens @@ -2694,74 +2721,76 @@ void write_clip(void *frontend, int clipboard, Display *disp = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); #endif - inst->pasteout_data_utf8 = snewn(len*6, char); - inst->pasteout_data_utf8_len = len*6; - inst->pasteout_data_utf8_len = - charset_from_unicode(&tmp, &tmplen, inst->pasteout_data_utf8, - inst->pasteout_data_utf8_len, + state->pasteout_data_utf8 = snewn(len*6, char); + state->pasteout_data_utf8_len = len*6; + state->pasteout_data_utf8_len = + charset_from_unicode(&tmp, &tmplen, state->pasteout_data_utf8, + state->pasteout_data_utf8_len, CS_UTF8, NULL, NULL, 0); - if (inst->pasteout_data_utf8_len == 0) { - sfree(inst->pasteout_data_utf8); - inst->pasteout_data_utf8 = NULL; + if (state->pasteout_data_utf8_len == 0) { + sfree(state->pasteout_data_utf8); + state->pasteout_data_utf8 = NULL; } else { - inst->pasteout_data_utf8 = - sresize(inst->pasteout_data_utf8, - inst->pasteout_data_utf8_len + 1, char); - inst->pasteout_data_utf8[inst->pasteout_data_utf8_len] = '\0'; + state->pasteout_data_utf8 = + sresize(state->pasteout_data_utf8, + state->pasteout_data_utf8_len + 1, char); + state->pasteout_data_utf8[state->pasteout_data_utf8_len] = '\0'; } /* * Now let Xlib convert our UTF-8 data into compound text. */ #ifndef NOT_X_WINDOWS - list[0] = inst->pasteout_data_utf8; + list[0] = state->pasteout_data_utf8; if (Xutf8TextListToTextProperty(disp, list, 1, XCompoundTextStyle, &tp) == 0) { - inst->pasteout_data_ctext = snewn(tp.nitems+1, char); - memcpy(inst->pasteout_data_ctext, tp.value, tp.nitems); - inst->pasteout_data_ctext_len = tp.nitems; + state->pasteout_data_ctext = snewn(tp.nitems+1, char); + memcpy(state->pasteout_data_ctext, tp.value, tp.nitems); + state->pasteout_data_ctext_len = tp.nitems; XFree(tp.value); } else #endif { - inst->pasteout_data_ctext = NULL; - inst->pasteout_data_ctext_len = 0; + state->pasteout_data_ctext = NULL; + state->pasteout_data_ctext_len = 0; } } else { - inst->pasteout_data_utf8 = NULL; - inst->pasteout_data_utf8_len = 0; - inst->pasteout_data_ctext = NULL; - inst->pasteout_data_ctext_len = 0; + state->pasteout_data_utf8 = NULL; + state->pasteout_data_utf8_len = 0; + state->pasteout_data_ctext = NULL; + state->pasteout_data_ctext_len = 0; } - inst->pasteout_data = snewn(len*6, char); - inst->pasteout_data_len = len*6; - inst->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0, - data, len, inst->pasteout_data, - inst->pasteout_data_len, + state->pasteout_data = snewn(len*6, char); + state->pasteout_data_len = len*6; + state->pasteout_data_len = wc_to_mb(inst->ucsdata.line_codepage, 0, + data, len, state->pasteout_data, + state->pasteout_data_len, NULL, NULL, NULL); - if (inst->pasteout_data_len == 0) { - sfree(inst->pasteout_data); - inst->pasteout_data = NULL; + if (state->pasteout_data_len == 0) { + sfree(state->pasteout_data); + state->pasteout_data = NULL; } else { - inst->pasteout_data = - sresize(inst->pasteout_data, inst->pasteout_data_len, char); + state->pasteout_data = + sresize(state->pasteout_data, state->pasteout_data_len, char); } - store_cutbuffer(inst->pasteout_data, inst->pasteout_data_len); + /* The legacy X cut buffers go with PRIMARY, not any other clipboard */ + if (state->atom == GDK_SELECTION_PRIMARY) + store_cutbuffer(state->pasteout_data, state->pasteout_data_len); - if (gtk_selection_owner_set(inst->area, GDK_SELECTION_PRIMARY, + if (gtk_selection_owner_set(inst->area, state->atom, inst->input_event_time)) { #if GTK_CHECK_VERSION(2,0,0) - gtk_selection_clear_targets(inst->area, GDK_SELECTION_PRIMARY); + gtk_selection_clear_targets(inst->area, state->atom); #endif - gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY, + gtk_selection_add_target(inst->area, state->atom, GDK_SELECTION_TYPE_STRING, 1); - if (inst->pasteout_data_ctext) - gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY, + if (state->pasteout_data_ctext) + gtk_selection_add_target(inst->area, state->atom, compound_text_atom, 1); - if (inst->pasteout_data_utf8) - gtk_selection_add_target(inst->area, GDK_SELECTION_PRIMARY, + if (state->pasteout_data_utf8) + gtk_selection_add_target(inst->area, state->atom, utf8_string_atom, 1); } @@ -2769,51 +2798,75 @@ void write_clip(void *frontend, int clipboard, term_lost_clipboard_ownership(inst->term, clipboard); } +static struct clipboard_state *clipboard_from_atom( + struct gui_data *inst, GdkAtom atom) +{ + int i; + + for (i = 0; i < N_CLIPBOARDS; i++) { + struct clipboard_state *state = &inst->clipstates[i]; + if (state->inst == inst && state->atom == atom) + return state; + } + + return NULL; +} + 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); + struct clipboard_state *state = clipboard_from_atom( + inst, gtk_selection_data_get_selection(seldata)); + + if (!state) + return; + if (target == utf8_string_atom) gtk_selection_data_set(seldata, target, 8, - (unsigned char *)inst->pasteout_data_utf8, - inst->pasteout_data_utf8_len); + (unsigned char *)state->pasteout_data_utf8, + state->pasteout_data_utf8_len); else if (target == compound_text_atom) gtk_selection_data_set(seldata, target, 8, - (unsigned char *)inst->pasteout_data_ctext, - inst->pasteout_data_ctext_len); + (unsigned char *)state->pasteout_data_ctext, + state->pasteout_data_ctext_len); else gtk_selection_data_set(seldata, target, 8, - (unsigned char *)inst->pasteout_data, - inst->pasteout_data_len); + (unsigned char *)state->pasteout_data, + state->pasteout_data_len); } static gint selection_clear(GtkWidget *widget, GdkEventSelection *seldata, gpointer data) { struct gui_data *inst = (struct gui_data *)data; + struct clipboard_state *state = clipboard_from_atom( + inst, seldata->selection); - term_lost_clipboard_ownership(inst->term, CLIP_SYSTEM); - if (inst->pasteout_data) - sfree(inst->pasteout_data); - if (inst->pasteout_data_ctext) - sfree(inst->pasteout_data_ctext); - if (inst->pasteout_data_utf8) - sfree(inst->pasteout_data_utf8); - inst->pasteout_data = NULL; - inst->pasteout_data_len = 0; - inst->pasteout_data_ctext = NULL; - inst->pasteout_data_ctext_len = 0; - inst->pasteout_data_utf8 = NULL; - inst->pasteout_data_utf8_len = 0; + if (!state) + return TRUE; + + term_lost_clipboard_ownership(inst->term, state->clipboard); + if (state->pasteout_data) + sfree(state->pasteout_data); + if (state->pasteout_data_ctext) + sfree(state->pasteout_data_ctext); + if (state->pasteout_data_utf8) + sfree(state->pasteout_data_utf8); + state->pasteout_data = NULL; + state->pasteout_data_len = 0; + state->pasteout_data_ctext = NULL; + state->pasteout_data_ctext_len = 0; + state->pasteout_data_utf8 = NULL; + state->pasteout_data_utf8_len = 0; return TRUE; } void frontend_request_paste(void *frontend, int clipboard) { struct gui_data *inst = (struct gui_data *)frontend; - - assert(clipboard == CLIP_SYSTEM); + struct clipboard_state *state = &inst->clipstates[clipboard]; /* * In Unix, pasting is asynchronous: all we can do at the @@ -2829,17 +2882,16 @@ void frontend_request_paste(void *frontend, int clipboard) * fails, selection_received() will be informed and will * fall back to an ordinary string. */ - gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, - utf8_string_atom, + gtk_selection_convert(inst->area, state->atom, utf8_string_atom, inst->input_event_time); } else { /* * If we're in direct-to-font mode, we disable UTF-8 * pasting, and go straight to ordinary string data. */ - gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, - GDK_SELECTION_TYPE_STRING, - inst->input_event_time); + gtk_selection_convert(inst->area, state->atom, + GDK_SELECTION_TYPE_STRING, + inst->input_event_time); } } @@ -2861,13 +2913,18 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, gint seldata_length = gtk_selection_data_get_length(seldata); wchar_t *paste; int paste_len; + struct clipboard_state *state = clipboard_from_atom( + inst, gtk_selection_data_get_selection(seldata)); + + if (!state) + return; if (seldata_target == utf8_string_atom && seldata_length <= 0) { /* * Failed to get a UTF-8 selection string. Try compound * text next. */ - gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + gtk_selection_convert(inst->area, state->atom, compound_text_atom, inst->input_event_time); return; @@ -2878,7 +2935,7 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, * Failed to get UTF-8 or compound text. Try an ordinary * string. */ - gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + gtk_selection_convert(inst->area, state->atom, GDK_SELECTION_TYPE_STRING, inst->input_event_time); return; @@ -2935,7 +2992,7 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, /* * Compound text failed; fall back to STRING. */ - gtk_selection_convert(inst->area, GDK_SELECTION_PRIMARY, + gtk_selection_convert(inst->area, state->atom, GDK_SELECTION_TYPE_STRING, inst->input_event_time); return; @@ -2963,6 +3020,16 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, #endif } +static void init_one_clipboard(struct gui_data *inst, int clipboard, + GdkAtom atom) +{ + struct clipboard_state *state = &inst->clipstates[clipboard]; + + state->inst = inst; + state->atom = atom; + state->clipboard = clipboard; +} + void init_clipboard(struct gui_data *inst) { #ifndef NOT_X_WINDOWS @@ -2998,6 +3065,9 @@ void init_clipboard(struct gui_data *inst) XA_CUT_BUFFER7, XA_STRING, 8, PropModeAppend, empty, 0); #endif + init_one_clipboard(inst, CLIP_PRIMARY, GDK_SELECTION_PRIMARY); + init_one_clipboard(inst, CLIP_CLIPBOARD, GDK_SELECTION_CLIPBOARD); + g_signal_connect(G_OBJECT(inst->area), "selection_received", G_CALLBACK(selection_received), inst); g_signal_connect(G_OBJECT(inst->area), "selection_get", @@ -3016,7 +3086,7 @@ void init_clipboard(struct gui_data *inst) void paste_menu_action(void *frontend) { struct gui_data *inst = (struct gui_data *)frontend; - term_request_paste(inst->term, CLIP_SYSTEM); + term_request_paste(inst->term, MENU_PASTE_CLIPBOARD); } static void set_window_titles(struct gui_data *inst) @@ -4934,8 +5004,8 @@ void new_session_window(Conf *conf, const char *geometry_string) inst->eventlogstuff = eventlogstuff_new(); inst->term = term_init(inst->conf, &inst->ucsdata, inst); - inst->term->mouse_select_clipboard = CLIP_SYSTEM; - inst->term->mouse_paste_clipboard = CLIP_SYSTEM; + inst->term->mouse_select_clipboard = MOUSE_SELECT_CLIPBOARD; + inst->term->mouse_paste_clipboard = MOUSE_PASTE_CLIPBOARD; inst->logctx = log_init(inst, inst->conf); term_provide_logctx(inst->term, inst->logctx); diff --git a/unix/unix.h b/unix/unix.h index 6c9d11c7..d9e8f7a3 100644 --- a/unix/unix.h +++ b/unix/unix.h @@ -29,7 +29,6 @@ /* 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 */ #define BUILDINFO_PLATFORM_GTK "OS X (GTK)" #define BUILDINFO_GTK @@ -119,10 +118,23 @@ unsigned long getticks(void); */ #define FLAG_STDERR_TTY 0x1000 -#define PLATFORM_CLIPBOARDS(X) \ - X(CLIP_SYSTEM, "system clipboard") \ +#define PLATFORM_CLIPBOARDS(X) \ + X(CLIP_PRIMARY, "X11 primary selection") \ + X(CLIP_CLIPBOARD, "XDG clipboard") \ /* end of list */ +#ifdef OSX_GTK +/* OS X has no PRIMARY selection */ +#define SHIFT_INS_CLIPBOARD CLIP_CLIPBOARD +#define MOUSE_SELECT_CLIPBOARD CLIP_CLIPBOARD +#define MOUSE_PASTE_CLIPBOARD CLIP_CLIPBOARD +#else +#define SHIFT_INS_CLIPBOARD CLIP_PRIMARY +#define MOUSE_SELECT_CLIPBOARD CLIP_PRIMARY +#define MOUSE_PASTE_CLIPBOARD CLIP_PRIMARY +#endif +#define MENU_PASTE_CLIPBOARD CLIP_CLIPBOARD + /* The per-session frontend structure managed by gtkwin.c */ struct gui_data;