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)