From 2a76f8d4a28e01d4cb82ee9858616f13759089f6 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sun, 17 Dec 2017 18:44:27 +0000 Subject: [PATCH] Support custom clipboard names under X. This required me to turn the drop-lists into combo boxes and add an extra string-typed Conf setting alongside each enumerated value. --- config.c | 104 +++++++++++++++++++++++++++++------ doc/config.but | 12 +++- putty.h | 3 + settings.c | 31 ++++++++--- unix/gtkwin.c | 145 ++++++++++++++++++++++++++++++++++++++----------- unix/unix.h | 5 ++ 6 files changed, 242 insertions(+), 58 deletions(-) diff --git a/config.c b/config.c index df999849..a12a6d64 100644 --- a/config.c +++ b/config.c @@ -1342,29 +1342,100 @@ static void clipboard_selector_handler(union control *ctrl, void *dlg, { Conf *conf = (Conf *)data; int setting = ctrl->generic.context.i; +#ifdef NAMED_CLIPBOARDS + int strsetting = ctrl->editbox.context2.i; +#endif + + static const struct { + const char *name; + int id; + } options[] = { + {"No action", CLIPUI_NONE}, + {CLIPNAME_IMPLICIT, CLIPUI_IMPLICIT}, + {CLIPNAME_EXPLICIT, CLIPUI_EXPLICIT}, + }; if (event == EVENT_REFRESH) { int i, val = conf_get_int(conf, setting); dlg_update_start(ctrl, dlg); dlg_listbox_clear(ctrl, dlg); - dlg_listbox_addwithid(ctrl, dlg, "No action", CLIPUI_NONE); - dlg_listbox_addwithid(ctrl, dlg, CLIPNAME_IMPLICIT, CLIPUI_IMPLICIT); - dlg_listbox_addwithid(ctrl, dlg, CLIPNAME_EXPLICIT, CLIPUI_EXPLICIT); + +#ifdef NAMED_CLIPBOARDS + for (i = 0; i < lenof(options); i++) + dlg_listbox_add(ctrl, dlg, options[i].name); + if (val == CLIPUI_CUSTOM) { + const char *sval = conf_get_str(conf, strsetting); + for (i = 0; i < lenof(options); i++) + if (!strcmp(sval, options[i].name)) + break; /* needs escaping */ + if (i < lenof(options) || sval[0] == '=') { + char *escaped = dupcat("=", sval, (const char *)NULL); + dlg_editbox_set(ctrl, dlg, escaped); + sfree(escaped); + } else { + dlg_editbox_set(ctrl, dlg, sval); + } + } else { + dlg_editbox_set(ctrl, dlg, options[0].name); /* fallback */ + for (i = 0; i < lenof(options); i++) + if (val == options[i].id) + dlg_editbox_set(ctrl, dlg, options[i].name); + } +#else + for (i = 0; i < lenof(options); i++) + dlg_listbox_addwithid(ctrl, dlg, options[i].name, options[i].id); dlg_listbox_select(ctrl, dlg, 0); /* fallback */ - for (i = 0; i < 3; i++) - if (val == dlg_listbox_getid(ctrl, dlg, i)) + for (i = 0; i < lenof(options); i++) + if (val == options[i].id) dlg_listbox_select(ctrl, dlg, i); +#endif dlg_update_done(ctrl, dlg); - } else if (event == EVENT_SELCHANGE) { + } else if (event == EVENT_SELCHANGE +#ifdef NAMED_CLIPBOARDS + || event == EVENT_VALCHANGE +#endif + ) { +#ifdef NAMED_CLIPBOARDS + const char *sval = dlg_editbox_get(ctrl, dlg); + int i; + + for (i = 0; i < lenof(options); i++) + if (!strcmp(sval, options[i].name)) { + conf_set_int(conf, setting, options[i].id); + conf_set_str(conf, strsetting, ""); + break; + } + if (i == lenof(options)) { + conf_set_int(conf, setting, CLIPUI_CUSTOM); + if (sval[0] == '=') + sval++; + conf_set_str(conf, strsetting, sval); + } +#else int index = dlg_listbox_index(ctrl, dlg); if (index >= 0) { int val = dlg_listbox_getid(ctrl, dlg, index); conf_set_int(conf, setting, val); } +#endif } } +static void clipboard_control(struct controlset *s, const char *label, + char shortcut, int percentage, intorptr helpctx, + int setting, int strsetting) +{ +#ifdef NAMED_CLIPBOARDS + ctrl_combobox(s, label, shortcut, percentage, helpctx, + clipboard_selector_handler, I(setting), I(strsetting)); +#else + /* strsetting isn't needed in this case */ + ctrl_droplist(s, label, shortcut, percentage, helpctx, + clipboard_selector_handler, I(setting)); +#endif +} + void setup_config_box(struct controlbox *b, int midsession, int protocol, int protcfginfo) { @@ -1894,18 +1965,15 @@ void setup_config_box(struct controlbox *b, int midsession, CLIPNAME_EXPLICIT_OBJECT, NO_SHORTCUT, HELPCTX(selection_autocopy), conf_checkbox_handler, I(CONF_mouseautocopy)); - ctrl_droplist(s, "Mouse paste action:", NO_SHORTCUT, 60, - HELPCTX(selection_clipactions), - clipboard_selector_handler, - I(CONF_mousepaste)); - ctrl_droplist(s, "{Ctrl,Shift} + Ins:", NO_SHORTCUT, 60, - HELPCTX(selection_clipactions), - clipboard_selector_handler, - I(CONF_ctrlshiftins)); - ctrl_droplist(s, "Ctrl + Shift + {C,V}:", NO_SHORTCUT, 60, - HELPCTX(selection_clipactions), - clipboard_selector_handler, - I(CONF_ctrlshiftcv)); + clipboard_control(s, "Mouse paste action:", NO_SHORTCUT, 60, + HELPCTX(selection_clipactions), + CONF_mousepaste, CONF_mousepaste_custom); + clipboard_control(s, "{Ctrl,Shift} + Ins:", NO_SHORTCUT, 60, + HELPCTX(selection_clipactions), + CONF_ctrlshiftins, CONF_ctrlshiftins_custom); + clipboard_control(s, "Ctrl + Shift + {C,V}:", NO_SHORTCUT, 60, + HELPCTX(selection_clipactions), + CONF_ctrlshiftcv, CONF_ctrlshiftcv_custom); /* * The Window/Selection/Words panel. diff --git a/doc/config.but b/doc/config.but index 3edce1b7..79036582 100644 --- a/doc/config.but +++ b/doc/config.but @@ -1520,7 +1520,7 @@ actions pastes from (including turning the paste action off completely). On platforms with a single system clipboard, the available options are to paste from that clipboard or to paste from PuTTY's internal memory of the last selected text within that window. -On X, the options are \cw{CLIPBOARD} or \cw{PRIMARY}. +On X, the standard options are \cw{CLIPBOARD} or \cw{PRIMARY}. (\cw{PRIMARY} is conceptually similar in that it \e{also} refers to the last selected text \dash just across all applications instead of @@ -1531,6 +1531,16 @@ The two keyboard options each come with a corresponding key to copy from, Ctrl-Ins will copy to the same location; similarly, Ctrl-Shift-C will copy to whatever Ctrl-Shift-V pastes from. +On X, you can also enter a selection name of your choice. For example, +there is a rarely-used standard selection called \cq{SECONDARY}, which +Emacs (for example) can work with if you hold down the Meta key while +dragging to select or clicking to paste; if you configure a PuTTY +keyboard action to access this clipboard, then you can interoperate +with other applications' use of it. Another thing you could do would +be to invent a clipboard name yourself, to create a special clipboard +shared \e{only} between instances of PuTTY, or between just instances +configured in that particular way. + \H{config-selection-words} The Words panel PuTTY will \I{word-by-word selection}select a word at a time in the diff --git a/putty.h b/putty.h index ca00b666..4c64cb85 100644 --- a/putty.h +++ b/putty.h @@ -903,6 +903,9 @@ void cleanup_exit(int); X(INT, NONE, mousepaste) \ X(INT, NONE, ctrlshiftins) \ X(INT, NONE, ctrlshiftcv) \ + X(STR, NONE, mousepaste_custom) \ + X(STR, NONE, ctrlshiftins_custom) \ + X(STR, NONE, ctrlshiftcv_custom) \ /* translations */ \ X(INT, NONE, vtmode) \ X(STR, NONE, line_codepage) \ diff --git a/settings.c b/settings.c index 0433adc3..a51f3d58 100644 --- a/settings.c +++ b/settings.c @@ -448,7 +448,7 @@ static void wprefs(void *sesskey, const char *name, } static void write_clip_setting(void *handle, const char *savekey, - Conf *conf, int confkey) + Conf *conf, int confkey, int strconfkey) { int val = conf_get_int(conf, confkey); switch (val) { @@ -462,21 +462,33 @@ static void write_clip_setting(void *handle, const char *savekey, case CLIPUI_EXPLICIT: write_setting_s(handle, savekey, "explicit"); break; + case CLIPUI_CUSTOM: + { + char *sval = dupcat("custom:", conf_get_str(conf, strconfkey), + (const char *)NULL); + write_setting_s(handle, savekey, sval); + sfree(sval); + } + break; } } static void read_clip_setting(void *handle, const char *savekey, - int def, Conf *conf, int confkey) + int def, Conf *conf, int confkey, int strconfkey) { char *setting = read_setting_s(handle, savekey); int val; + conf_set_str(conf, strconfkey, ""); if (!setting) { val = def; } else if (!strcmp(setting, "implicit")) { val = CLIPUI_IMPLICIT; } else if (!strcmp(setting, "explicit")) { val = CLIPUI_EXPLICIT; + } else if (!strncmp(setting, "custom:", 7)) { + val = CLIPUI_CUSTOM; + conf_set_str(conf, strconfkey, setting + 7); } else { val = CLIPUI_NONE; } @@ -676,9 +688,12 @@ void save_open_settings(void *sesskey, Conf *conf) } write_setting_i(sesskey, "MouseAutocopy", conf_get_int(conf, CONF_mouseautocopy)); - write_clip_setting(sesskey, "MousePaste", conf, CONF_mousepaste); - write_clip_setting(sesskey, "CtrlShiftIns", conf, CONF_ctrlshiftins); - write_clip_setting(sesskey, "CtrlShiftCV", conf, CONF_ctrlshiftcv); + write_clip_setting(sesskey, "MousePaste", conf, + CONF_mousepaste, CONF_mousepaste_custom); + write_clip_setting(sesskey, "CtrlShiftIns", conf, + CONF_ctrlshiftins, CONF_ctrlshiftins_custom); + write_clip_setting(sesskey, "CtrlShiftCV", conf, + CONF_ctrlshiftcv, CONF_ctrlshiftcv_custom); write_setting_s(sesskey, "LineCodePage", conf_get_str(conf, CONF_line_codepage)); write_setting_i(sesskey, "CJKAmbigWide", conf_get_int(conf, CONF_cjk_ambig_wide)); write_setting_i(sesskey, "UTF8Override", conf_get_int(conf, CONF_utf8_override)); @@ -1103,11 +1118,11 @@ void load_open_settings(void *sesskey, Conf *conf) gppi(sesskey, "MouseAutocopy", CLIPUI_DEFAULT_AUTOCOPY, conf, CONF_mouseautocopy); read_clip_setting(sesskey, "MousePaste", CLIPUI_DEFAULT_MOUSE, - conf, CONF_mousepaste); + conf, CONF_mousepaste, CONF_mousepaste_custom); read_clip_setting(sesskey, "CtrlShiftIns", CLIPUI_DEFAULT_INS, - conf, CONF_ctrlshiftins); + conf, CONF_ctrlshiftins, CONF_ctrlshiftins_custom); read_clip_setting(sesskey, "CtrlShiftCV", CLIPUI_NONE, - conf, CONF_ctrlshiftcv); + conf, CONF_ctrlshiftcv, CONF_ctrlshiftcv_custom); /* * The empty default for LineCodePage will be converted later * into a plausible default for the locale. diff --git a/unix/gtkwin.c b/unix/gtkwin.c index 6811838a..f117f610 100644 --- a/unix/gtkwin.c +++ b/unix/gtkwin.c @@ -115,6 +115,7 @@ struct gui_data { #endif int direct_to_font; struct clipboard_state clipstates[N_CLIPBOARDS]; + int clipboard_ctrlshiftins, clipboard_ctrlshiftcv; int font_width, font_height; int width, height; int ignore_sbar; @@ -1041,6 +1042,12 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) #endif term_request_paste(inst->term, CLIP_CLIPBOARD); return TRUE; + case CLIPUI_CUSTOM: +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Shift-Insert: paste from custom clipboard\n")); +#endif + term_request_paste(inst->term, inst->clipboard_ctrlshiftins); + return TRUE; default: #ifdef KEY_EVENT_DIAGNOSTICS debug((" - Shift-Insert: no paste action\n")); @@ -1067,6 +1074,13 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) term_request_copy(inst->term, clips_clipboard, lenof(clips_clipboard)); return TRUE; + case CLIPUI_CUSTOM: +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Ctrl-Insert: copy to custom clipboard\n")); +#endif + term_request_copy(inst->term, + &inst->clipboard_ctrlshiftins, 1); + return TRUE; default: #ifdef KEY_EVENT_DIAGNOSTICS debug((" - Ctrl-Insert: no copy action\n")); @@ -1113,6 +1127,21 @@ gint key_event(GtkWidget *widget, GdkEventKey *event, gpointer data) term_request_copy(inst->term, clips, lenof(clips)); } return TRUE; + case CLIPUI_CUSTOM: + if (paste) { +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Ctrl-Shift-V: paste from custom clipboard\n")); +#endif + term_request_paste(inst->term, + inst->clipboard_ctrlshiftcv); + } else { +#ifdef KEY_EVENT_DIAGNOSTICS + debug((" - Ctrl-Shift-C: copy to custom clipboard\n")); +#endif + term_request_copy(inst->term, + &inst->clipboard_ctrlshiftcv, 1); + } + return TRUE; } } @@ -2571,6 +2600,20 @@ void palette_reset(void *frontend) } } +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; +} + #ifdef JUST_USE_GTK_CLIPBOARD_UTF8 /* ---------------------------------------------------------------------- @@ -2580,25 +2623,27 @@ void palette_reset(void *frontend) * formats it feels like. */ -static void init_one_clipboard(struct gui_data *inst, int clipboard, - GdkAtom atom) +void set_clipboard_atom(struct gui_data *inst, int clipboard, GdkAtom atom) { struct clipboard_state *state = &inst->clipstates[clipboard]; state->inst = inst; - state->atom = atom; state->clipboard = clipboard; + state->atom = atom; - state->gtkclipboard = gtk_clipboard_get_for_display( - gdk_display_get_default(), atom); - - g_object_set_data(G_OBJECT(state->gtkclipboard), "user-data", state); + if (state->atom != GDK_NONE) { + state->gtkclipboard = gtk_clipboard_get_for_display( + gdk_display_get_default(), state->atom); + g_object_set_data(G_OBJECT(state->gtkclipboard), "user-data", state); + } else { + state->gtkclipboard = NULL; + } } 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); + set_clipboard_atom(inst, CLIP_PRIMARY, GDK_SELECTION_PRIMARY); + set_clipboard_atom(inst, CLIP_CLIPBOARD, GDK_SELECTION_CLIPBOARD); return TRUE; } @@ -2663,6 +2708,9 @@ void write_clip(void *frontend, int clipboard, return; } + if (!state->gtkclipboard) + return; + cdi = snew(struct clipboard_data_instance); state->current_cdi = cdi; cdi->pasteout_data_utf8 = snewn(len*6, char); @@ -2721,6 +2769,10 @@ void frontend_request_paste(void *frontend, int clipboard) { struct gui_data *inst = (struct gui_data *)frontend; struct clipboard_state *state = &inst->clipstates[clipboard]; + + if (!state->gtkclipboard) + return; + gtk_clipboard_request_text(state->gtkclipboard, clipboard_text_received, inst); } @@ -2881,20 +2933,6 @@ 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) { @@ -3103,16 +3141,24 @@ static void selection_received(GtkWidget *widget, GtkSelectionData *seldata, #endif } -static void init_one_clipboard(struct gui_data *inst, int clipboard, - GdkAtom atom) +static void init_one_clipboard(struct gui_data *inst, int clipboard) { struct clipboard_state *state = &inst->clipstates[clipboard]; state->inst = inst; - state->atom = atom; state->clipboard = clipboard; } +void set_clipboard_atom(struct gui_data *inst, int clipboard, GdkAtom atom) +{ + struct clipboard_state *state = &inst->clipstates[clipboard]; + + state->inst = inst; + state->clipboard = clipboard; + + state->atom = atom; +} + void init_clipboard(struct gui_data *inst) { #ifndef NOT_X_WINDOWS @@ -3148,8 +3194,10 @@ 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); + inst->clipstates[CLIP_PRIMARY].atom = GDK_SELECTION_PRIMARY; + inst->clipstates[CLIP_CLIPBOARD].atom = GDK_SELECTION_CLIPBOARD; + init_one_clipboard(inst, CLIP_PRIMARY); + init_one_clipboard(inst, CLIP_CLIPBOARD); g_signal_connect(G_OBJECT(inst->area), "selection_received", G_CALLBACK(selection_received), inst); @@ -4252,7 +4300,7 @@ void event_log_menuitem(GtkMenuItem *item, gpointer data) showeventlog(inst->eventlogstuff, inst->window); } -void setup_clipboards(Terminal *term, Conf *conf) +void setup_clipboards(struct gui_data *inst, Terminal *term, Conf *conf) { assert(term->mouse_select_clipboards[0] == CLIP_LOCAL); @@ -4265,6 +4313,10 @@ void setup_clipboards(Terminal *term, Conf *conf) term->n_mouse_select_clipboards++] = CLIP_CLIPBOARD; } + set_clipboard_atom(inst, CLIP_CUSTOM_1, GDK_NONE); + set_clipboard_atom(inst, CLIP_CUSTOM_2, GDK_NONE); + set_clipboard_atom(inst, CLIP_CUSTOM_3, GDK_NONE); + switch (conf_get_int(conf, CONF_mousepaste)) { case CLIPUI_IMPLICIT: term->mouse_paste_clipboard = MOUSE_PASTE_CLIPBOARD; @@ -4272,10 +4324,41 @@ void setup_clipboards(Terminal *term, Conf *conf) case CLIPUI_EXPLICIT: term->mouse_paste_clipboard = CLIP_CLIPBOARD; break; + case CLIPUI_CUSTOM: + term->mouse_paste_clipboard = CLIP_CUSTOM_1; + set_clipboard_atom(inst, CLIP_CUSTOM_1, + gdk_atom_intern( + conf_get_str(conf, CONF_mousepaste_custom), + FALSE)); + break; default: term->mouse_paste_clipboard = CLIP_NULL; break; } + + if (conf_get_int(conf, CONF_ctrlshiftins) == CLIPUI_CUSTOM) { + GdkAtom atom = gdk_atom_intern( + conf_get_str(conf, CONF_ctrlshiftins_custom), FALSE); + struct clipboard_state *state = clipboard_from_atom(inst, atom); + if (state) { + inst->clipboard_ctrlshiftins = state->clipboard; + } else { + inst->clipboard_ctrlshiftins = CLIP_CUSTOM_2; + set_clipboard_atom(inst, CLIP_CUSTOM_2, atom); + } + } + + if (conf_get_int(conf, CONF_ctrlshiftcv) == CLIPUI_CUSTOM) { + GdkAtom atom = gdk_atom_intern( + conf_get_str(conf, CONF_ctrlshiftcv_custom), FALSE); + struct clipboard_state *state = clipboard_from_atom(inst, atom); + if (state) { + inst->clipboard_ctrlshiftins = state->clipboard; + } else { + inst->clipboard_ctrlshiftcv = CLIP_CUSTOM_3; + set_clipboard_atom(inst, CLIP_CUSTOM_3, atom); + } + } } struct after_change_settings_dialog_ctx { @@ -4352,7 +4435,7 @@ static void after_change_settings_dialog(void *vctx, int retval) } /* Pass new config data to the terminal */ term_reconfig(inst->term, inst->conf); - setup_clipboards(inst->term, inst->conf); + setup_clipboards(inst, inst->term, inst->conf); /* Pass new config data to the back end */ if (inst->back) inst->back->reconfig(inst->backhandle, inst->conf); @@ -5131,7 +5214,7 @@ void new_session_window(Conf *conf, const char *geometry_string) inst->eventlogstuff = eventlogstuff_new(); inst->term = term_init(inst->conf, &inst->ucsdata, inst); - setup_clipboards(inst->term, inst->conf); + setup_clipboards(inst, inst->term, inst->conf); 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 25f3cbda..dc216b4a 100644 --- a/unix/unix.h +++ b/unix/unix.h @@ -121,6 +121,9 @@ unsigned long getticks(void); #define PLATFORM_CLIPBOARDS(X) \ X(CLIP_PRIMARY, "X11 primary selection") \ X(CLIP_CLIPBOARD, "XDG clipboard") \ + X(CLIP_CUSTOM_1, "") \ + X(CLIP_CUSTOM_2, "") \ + X(CLIP_CUSTOM_3, "") \ /* end of list */ #ifdef OSX_GTK @@ -149,6 +152,8 @@ unsigned long getticks(void); #define CLIPUI_DEFAULT_AUTOCOPY FALSE #define CLIPUI_DEFAULT_MOUSE CLIPUI_IMPLICIT #define CLIPUI_DEFAULT_INS CLIPUI_IMPLICIT +/* X11 supports arbitrary named clipboards */ +#define NAMED_CLIPBOARDS #endif /* The per-session frontend structure managed by gtkwin.c */