From c35d8b832801d926a16ad29c416b969654051ef0 Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Sat, 16 Oct 2021 13:20:44 +0100 Subject: [PATCH] win_set_[icon_]title: send a codepage along with the string. While fixing the previous commit I noticed that window titles don't actually _work_ properly if you change the terminal character set, because the text accumulated in the OSC string buffer is sent to the TermWin as raw bytes, with no indication of what character set it should interpret them as. You might get lucky if you happened to choose the right charset (in particular, UTF-8 is a common default), but if you change the charset half way through a run, then there's certainly no way the frontend will know to interpret two window titles sent before and after the change in two different charsets. So, now win_set_title() and win_set_icon_title() both include a codepage parameter along with the byte string, and it's up to them to translate the provided window title from that encoding to whatever the local window system expects to receive. On Windows, that's wide-string Unicode, so we can just use the existing dup_mb_to_wc utility function. But in GTK, it's UTF-8, so I had to write an extra utility function to encode a wide string as UTF-8. --- fuzzterm.c | 4 ++-- misc.h | 5 +++++ putty.h | 14 ++++++++------ terminal/terminal.c | 11 +++++++++-- terminal/terminal.h | 1 + unix/window.c | 20 +++++++++++++++---- utils/CMakeLists.txt | 1 + utils/dup_mb_to_wc.c | 28 +++++++++++++++++++++++++++ utils/encode_wide_string_as_utf8.c | 25 ++++++++++++++++++++++++ windows/window.c | 31 +++++++++++++++--------------- 10 files changed, 111 insertions(+), 29 deletions(-) create mode 100644 utils/dup_mb_to_wc.c create mode 100644 utils/encode_wide_string_as_utf8.c diff --git a/fuzzterm.c b/fuzzterm.c index a85df35a..a1902ede 100644 --- a/fuzzterm.c +++ b/fuzzterm.c @@ -82,8 +82,8 @@ static void fuzz_clip_write( static void fuzz_clip_request_paste(TermWin *tw, int clipboard) {} static void fuzz_refresh(TermWin *tw) {} static void fuzz_request_resize(TermWin *tw, int w, int h) {} -static void fuzz_set_title(TermWin *tw, const char *title) {} -static void fuzz_set_icon_title(TermWin *tw, const char *icontitle) {} +static void fuzz_set_title(TermWin *tw, const char *title, int codepage) {} +static void fuzz_set_icon_title(TermWin *tw, const char *icontitle, int cp) {} static void fuzz_set_minimised(TermWin *tw, bool minimised) {} static void fuzz_set_maximised(TermWin *tw, bool maximised) {} static void fuzz_move(TermWin *tw, int x, int y) {} diff --git a/misc.h b/misc.h index 04fe3e68..5c8f982e 100644 --- a/misc.h +++ b/misc.h @@ -215,6 +215,11 @@ bool smemeq(const void *av, const void *bv, size_t len); * been removed. */ size_t encode_utf8(void *output, unsigned long ch); +/* Encode a wide-character string into UTF-8. Tolerates surrogates if + * sizeof(wchar_t) == 2, assuming that in that case the wide string is + * encoded in UTF-16. */ +char *encode_wide_string_as_utf8(const wchar_t *wstr); + /* Write a string out in C string-literal format. */ void write_c_string_literal(FILE *fp, ptrlen str); diff --git a/putty.h b/putty.h index 6bff90bb..550b7ee5 100644 --- a/putty.h +++ b/putty.h @@ -1414,8 +1414,9 @@ struct TermWinVtable { void (*request_resize)(TermWin *, int w, int h); - void (*set_title)(TermWin *, const char *title); - void (*set_icon_title)(TermWin *, const char *icontitle); + void (*set_title)(TermWin *, const char *title, int codepage); + void (*set_icon_title)(TermWin *, const char *icontitle, int codepage); + /* set_minimised and set_maximised are assumed to set two * independent settings, rather than a single three-way * {min,normal,max} switch. The idea is that when you un-minimise @@ -1480,10 +1481,11 @@ static inline void win_refresh(TermWin *win) { win->vt->refresh(win); } static inline void win_request_resize(TermWin *win, int w, int h) { win->vt->request_resize(win, w, h); } -static inline void win_set_title(TermWin *win, const char *title) -{ win->vt->set_title(win, title); } -static inline void win_set_icon_title(TermWin *win, const char *icontitle) -{ win->vt->set_icon_title(win, icontitle); } +static inline void win_set_title(TermWin *win, const char *title, int codepage) +{ win->vt->set_title(win, title, codepage); } +static inline void win_set_icon_title(TermWin *win, const char *icontitle, + int codepage) +{ win->vt->set_icon_title(win, icontitle, codepage); } static inline void win_set_minimised(TermWin *win, bool minimised) { win->vt->set_minimised(win, minimised); } static inline void win_set_maximised(TermWin *win, bool maximised) diff --git a/terminal/terminal.c b/terminal/terminal.c index c087096f..e44f9f69 100644 --- a/terminal/terminal.c +++ b/terminal/terminal.c @@ -1433,11 +1433,13 @@ void term_update(Terminal *term) term->win_maximise_pending = false; } if (term->win_title_pending) { - win_set_title(term->win, term->window_title); + win_set_title(term->win, term->window_title, + term->wintitle_codepage); term->win_title_pending = false; } if (term->win_icon_title_pending) { - win_set_icon_title(term->win, term->icon_title); + win_set_icon_title(term->win, term->icon_title, + term->icontitle_codepage); term->win_icon_title_pending = false; } if (term->win_pointer_shape_pending) { @@ -1670,6 +1672,7 @@ void term_reconfig(Terminal *term, Conf *conf) if (strcmp(old_title, new_title)) { sfree(term->window_title); term->window_title = dupstr(new_title); + term->wintitle_codepage = DEFAULT_CODEPAGE; term->win_title_pending = true; term_schedule_update(term); } @@ -1807,6 +1810,7 @@ void term_setup_window_titles(Terminal *term, const char *title_hostname) term->window_title = dupstr(appname); term->icon_title = dupstr(term->window_title); } + term->wintitle_codepage = term->icontitle_codepage = DEFAULT_CODEPAGE; term->win_title_pending = true; term->win_icon_title_pending = true; } @@ -2032,6 +2036,7 @@ Terminal *term_init(Conf *myconf, struct unicode_data *ucsdata, TermWin *win) term->window_title = dupstr(""); term->icon_title = dupstr(""); + term->wintitle_codepage = term->icontitle_codepage = DEFAULT_CODEPAGE; term->minimised = false; term->winpos_x = term->winpos_y = 0; term->winpixsize_x = term->winpixsize_y = 0; @@ -3117,6 +3122,7 @@ static void do_osc(Terminal *term) if (!term->no_remote_wintitle) { sfree(term->icon_title); term->icon_title = dupstr(term->osc_string); + term->icontitle_codepage = term->ucsdata->line_codepage; term->win_icon_title_pending = true; term_schedule_update(term); } @@ -3128,6 +3134,7 @@ static void do_osc(Terminal *term) if (!term->no_remote_wintitle) { sfree(term->window_title); term->window_title = dupstr(term->osc_string); + term->wintitle_codepage = term->ucsdata->line_codepage; term->win_title_pending = true; term_schedule_update(term); } diff --git a/terminal/terminal.h b/terminal/terminal.h index c0524d05..3af752e7 100644 --- a/terminal/terminal.h +++ b/terminal/terminal.h @@ -351,6 +351,7 @@ struct terminal_tag { int mouse_paste_clipboard; char *window_title, *icon_title; + int wintitle_codepage, icontitle_codepage; bool minimised; BidiContext *bidi_ctx; diff --git a/unix/window.c b/unix/window.c index 69c35eb3..beaa2776 100644 --- a/unix/window.c +++ b/unix/window.c @@ -3248,19 +3248,31 @@ static void set_window_titles(GtkFrontend *inst) inst->icontitle); } -static void gtkwin_set_title(TermWin *tw, const char *title) +static void gtkwin_set_title(TermWin *tw, const char *title, int codepage) { GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); sfree(inst->wintitle); - inst->wintitle = dupstr(title); + if (codepage != CP_UTF8) { + wchar_t *title_w = dup_mb_to_wc(codepage, 0, title); + inst->wintitle = encode_wide_string_as_utf8(title_w); + sfree(title_w); + } else { + inst->wintitle = dupstr(title); + } set_window_titles(inst); } -static void gtkwin_set_icon_title(TermWin *tw, const char *title) +static void gtkwin_set_icon_title(TermWin *tw, const char *title, int codepage) { GtkFrontend *inst = container_of(tw, GtkFrontend, termwin); sfree(inst->icontitle); - inst->icontitle = dupstr(title); + if (codepage != CP_UTF8) { + wchar_t *title_w = dup_mb_to_wc(codepage, 0, title); + inst->icontitle = encode_wide_string_as_utf8(title_w); + sfree(title_w); + } else { + inst->icontitle = dupstr(title); + } set_window_titles(inst); } diff --git a/utils/CMakeLists.txt b/utils/CMakeLists.txt index a209eb58..6376c0c9 100644 --- a/utils/CMakeLists.txt +++ b/utils/CMakeLists.txt @@ -15,6 +15,7 @@ add_sources_from_current_dir(utils dupprintf.c dupstr.c encode_utf8.c + encode_wide_string_as_utf8.c fgetline.c host_strchr.c host_strchr_internal.c diff --git a/utils/dup_mb_to_wc.c b/utils/dup_mb_to_wc.c new file mode 100644 index 00000000..7785f9b6 --- /dev/null +++ b/utils/dup_mb_to_wc.c @@ -0,0 +1,28 @@ +/* + * Centralised Unicode-related helper functions, separate from misc.c + * so that they can be omitted from tools that aren't including + * Unicode handling. + */ + +#include "putty.h" +#include "misc.h" + +wchar_t *dup_mb_to_wc_c(int codepage, int flags, const char *string, int len) +{ + int mult; + for (mult = 1 ;; mult++) { + wchar_t *ret = snewn(mult*len + 2, wchar_t); + int outlen; + outlen = mb_to_wc(codepage, flags, string, len, ret, mult*len + 1); + if (outlen < mult*len+1) { + ret[outlen] = L'\0'; + return ret; + } + sfree(ret); + } +} + +wchar_t *dup_mb_to_wc(int codepage, int flags, const char *string) +{ + return dup_mb_to_wc_c(codepage, flags, string, strlen(string)); +} diff --git a/utils/encode_wide_string_as_utf8.c b/utils/encode_wide_string_as_utf8.c new file mode 100644 index 00000000..870903d5 --- /dev/null +++ b/utils/encode_wide_string_as_utf8.c @@ -0,0 +1,25 @@ +/* + * Encode a string of wchar_t as UTF-8. + */ + +#include "putty.h" +#include "misc.h" + +char *encode_wide_string_as_utf8(const wchar_t *ws) +{ + strbuf *sb = strbuf_new(); + while (*ws) { + unsigned long ch = *ws++; + if (sizeof(wchar_t) == 2 && IS_HIGH_SURROGATE(ch) && + IS_LOW_SURROGATE(*ws)) { + ch = FROM_SURROGATES(ch, *ws); + ws++; + } else if (IS_SURROGATE(ch)) { + ch = 0xfffd; /* illegal UTF-16 -> REPLACEMENT CHARACTER */ + } + char utf8[6]; + size_t size = encode_utf8(utf8, ch); + put_data(sb, utf8, size); + } + return strbuf_to_str(sb); +} diff --git a/windows/window.c b/windows/window.c index 8fbbbd27..aba1c000 100644 --- a/windows/window.c +++ b/windows/window.c @@ -219,7 +219,7 @@ static bool pointer_indicates_raw_mouse = false; static BusyStatus busy_status = BUSY_NOT; -static char *window_name, *icon_name; +static wchar_t *window_name, *icon_name; static int compose_state = 0; @@ -250,8 +250,9 @@ static void wintw_clip_write( static void wintw_clip_request_paste(TermWin *, int clipboard); static void wintw_refresh(TermWin *); static void wintw_request_resize(TermWin *, int w, int h); -static void wintw_set_title(TermWin *, const char *title); -static void wintw_set_icon_title(TermWin *, const char *icontitle); +static void wintw_set_title(TermWin *, const char *title, int codepage); +static void wintw_set_icon_title(TermWin *, const char *icontitle, + int codepage); static void wintw_set_minimised(TermWin *, bool minimised); static void wintw_set_maximised(TermWin *, bool maximised); static void wintw_move(TermWin *, int x, int y); @@ -416,8 +417,8 @@ static void close_session(void *ignored_context) session_closed = true; newtitle = dupprintf("%s (inactive)", appname); - win_set_icon_title(wintw, newtitle); - win_set_title(wintw, newtitle); + win_set_icon_title(wintw, newtitle, DEFAULT_CODEPAGE); + win_set_title(wintw, newtitle, DEFAULT_CODEPAGE); sfree(newtitle); if (ldisc) { @@ -2955,11 +2956,11 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message, term, r.right - r.left, r.bottom - r.top); } if (wParam == SIZE_MINIMIZED) - SetWindowText(hwnd, - conf_get_bool(conf, CONF_win_name_always) ? - window_name : icon_name); + SetWindowTextW(hwnd, + conf_get_bool(conf, CONF_win_name_always) ? + window_name : icon_name); if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED) - SetWindowText(hwnd, window_name); + SetWindowTextW(hwnd, window_name); if (wParam == SIZE_RESTORED) { processed_resize = false; clear_full_screen(); @@ -4707,20 +4708,20 @@ static int TranslateKey(UINT message, WPARAM wParam, LPARAM lParam, return -1; } -static void wintw_set_title(TermWin *tw, const char *title) +static void wintw_set_title(TermWin *tw, const char *title, int codepage) { sfree(window_name); - window_name = dupstr(title); + window_name = dup_mb_to_wc(codepage, 0, title); if (conf_get_bool(conf, CONF_win_name_always) || !IsIconic(wgs.term_hwnd)) - SetWindowText(wgs.term_hwnd, title); + SetWindowTextW(wgs.term_hwnd, window_name); } -static void wintw_set_icon_title(TermWin *tw, const char *title) +static void wintw_set_icon_title(TermWin *tw, const char *title, int codepage) { sfree(icon_name); - icon_name = dupstr(title); + icon_name = dup_mb_to_wc(codepage, 0, title); if (!conf_get_bool(conf, CONF_win_name_always) && IsIconic(wgs.term_hwnd)) - SetWindowText(wgs.term_hwnd, title); + SetWindowTextW(wgs.term_hwnd, icon_name); } static void wintw_set_scrollbar(TermWin *tw, int total, int start, int page)