Coverage Report

Created: 2026-10-28 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/key-string.c
Line
Count
Source
1
/* $OpenBSD: key-string.c,v 1.78 2026/10/27 09:00:29 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5
 *
6
 * Permission to use, copy, modify, and distribute this software for any
7
 * purpose with or without fee is hereby granted, provided that the above
8
 * copyright notice and this permission notice appear in all copies.
9
 *
10
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15
 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
 */
18
19
#include <sys/types.h>
20
21
#include <ctype.h>
22
#include <stdlib.h>
23
#include <string.h>
24
#include <wchar.h>
25
26
#include "tmux.h"
27
28
static key_code key_string_search_table(const char *);
29
static key_code key_string_get_modifiers(const char **);
30
31
static const struct {
32
  const char     *string;
33
  key_code  key;
34
} key_string_table[] = {
35
  /* Function keys. */
36
  { "F1",   KEYC_F1|KEYC_IMPLIED_META },
37
  { "F2",   KEYC_F2|KEYC_IMPLIED_META },
38
  { "F3",   KEYC_F3|KEYC_IMPLIED_META },
39
  { "F4",   KEYC_F4|KEYC_IMPLIED_META },
40
  { "F5",   KEYC_F5|KEYC_IMPLIED_META },
41
  { "F6",   KEYC_F6|KEYC_IMPLIED_META },
42
  { "F7",   KEYC_F7|KEYC_IMPLIED_META },
43
  { "F8",   KEYC_F8|KEYC_IMPLIED_META },
44
  { "F9",   KEYC_F9|KEYC_IMPLIED_META },
45
  { "F10",  KEYC_F10|KEYC_IMPLIED_META },
46
  { "F11",  KEYC_F11|KEYC_IMPLIED_META },
47
  { "F12",  KEYC_F12|KEYC_IMPLIED_META },
48
  { "IC",   KEYC_IC|KEYC_IMPLIED_META },
49
  { "Insert", KEYC_IC|KEYC_IMPLIED_META },
50
  { "DC",   KEYC_DC|KEYC_IMPLIED_META },
51
  { "Delete", KEYC_DC|KEYC_IMPLIED_META },
52
  { "Home", KEYC_HOME|KEYC_IMPLIED_META },
53
  { "End",  KEYC_END|KEYC_IMPLIED_META },
54
  { "NPage",  KEYC_NPAGE|KEYC_IMPLIED_META },
55
  { "PageDown", KEYC_NPAGE|KEYC_IMPLIED_META },
56
  { "PgDn", KEYC_NPAGE|KEYC_IMPLIED_META },
57
  { "PPage",  KEYC_PPAGE|KEYC_IMPLIED_META },
58
  { "PageUp", KEYC_PPAGE|KEYC_IMPLIED_META },
59
  { "PgUp", KEYC_PPAGE|KEYC_IMPLIED_META },
60
  { "BTab", KEYC_BTAB },
61
  { "Space",  ' ' },
62
  { "BSpace", KEYC_BSPACE },
63
64
  /*
65
   * C0 control characters, with the exception of Tab, Enter,
66
   * and Esc, should never appear as keys. We still render them,
67
   * so to be able to spot them in logs in case of an abnormality.
68
   */
69
  { "[NUL]",  C0_NUL },
70
  { "[SOH]",  C0_SOH },
71
  { "[STX]",  C0_STX },
72
  { "[ETX]",  C0_ETX },
73
  { "[EOT]",  C0_EOT },
74
  { "[ENQ]",  C0_ENQ },
75
  { "[ASC]",  C0_ASC },
76
  { "[BEL]",  C0_BEL },
77
  { "[BS]", C0_BS },
78
  { "Tab",  C0_HT },
79
  { "[LF]", C0_LF },
80
  { "[VT]", C0_VT },
81
  { "[FF]", C0_FF },
82
  { "Enter",  C0_CR },
83
  { "[SO]", C0_SO },
84
  { "[SI]", C0_SI },
85
  { "[DLE]",  C0_DLE },
86
  { "[DC1]",  C0_DC1 },
87
  { "[DC2]",  C0_DC2 },
88
  { "[DC3]",  C0_DC3 },
89
  { "[DC4]",  C0_DC4 },
90
  { "[NAK]",  C0_NAK },
91
  { "[SYN]",  C0_SYN },
92
  { "[ETB]",  C0_ETB },
93
  { "[CAN]",  C0_CAN },
94
  { "[EM]", C0_EM },
95
  { "[SUB]",  C0_SUB },
96
  { "Escape", C0_ESC },
97
  { "[FS]", C0_FS },
98
  { "[GS]", C0_GS },
99
  { "[RS]", C0_RS },
100
  { "[US]", C0_US },
101
102
  /* Arrow keys. */
103
  { "Up",   KEYC_UP|KEYC_CURSOR|KEYC_IMPLIED_META },
104
  { "Down", KEYC_DOWN|KEYC_CURSOR|KEYC_IMPLIED_META },
105
  { "Left", KEYC_LEFT|KEYC_CURSOR|KEYC_IMPLIED_META },
106
  { "Right",  KEYC_RIGHT|KEYC_CURSOR|KEYC_IMPLIED_META },
107
108
  /* Numeric keypad. */
109
  { "KP/",  KEYC_KP_SLASH|KEYC_KEYPAD },
110
  { "KP*",  KEYC_KP_STAR|KEYC_KEYPAD },
111
  { "KP-",  KEYC_KP_MINUS|KEYC_KEYPAD },
112
  { "KP7",  KEYC_KP_SEVEN|KEYC_KEYPAD },
113
  { "KP8",  KEYC_KP_EIGHT|KEYC_KEYPAD },
114
  { "KP9",  KEYC_KP_NINE|KEYC_KEYPAD },
115
  { "KP+",  KEYC_KP_PLUS|KEYC_KEYPAD },
116
  { "KP4",  KEYC_KP_FOUR|KEYC_KEYPAD },
117
  { "KP5",  KEYC_KP_FIVE|KEYC_KEYPAD },
118
  { "KP6",  KEYC_KP_SIX|KEYC_KEYPAD },
119
  { "KP1",  KEYC_KP_ONE|KEYC_KEYPAD },
120
  { "KP2",  KEYC_KP_TWO|KEYC_KEYPAD },
121
  { "KP3",  KEYC_KP_THREE|KEYC_KEYPAD },
122
  { "KPEnter",  KEYC_KP_ENTER|KEYC_KEYPAD },
123
  { "KP0",  KEYC_KP_ZERO|KEYC_KEYPAD },
124
  { "KP.",  KEYC_KP_PERIOD|KEYC_KEYPAD },
125
126
  /* Mouse keys. */
127
  { "MouseMoveSideStatus", KEYC_MOUSEMOVE_SIDE_STATUS },
128
  { "MouseMoveSideStatusLeft", KEYC_MOUSEMOVE_SIDE_STATUS_LEFT },
129
  { "MouseMoveSideStatusRight", KEYC_MOUSEMOVE_SIDE_STATUS_RIGHT },
130
  { "MouseMoveSideStatusDefault", KEYC_MOUSEMOVE_SIDE_STATUS_DEFAULT },
131
  { "MouseMoveSideStatusBorder", KEYC_MOUSEMOVE_SIDE_STATUS_BORDER },
132
  { "MouseMoveSideStatusControl0", KEYC_MOUSEMOVE_SIDE_STATUS_CONTROL0 },
133
  { "MouseMoveSideStatusControl1", KEYC_MOUSEMOVE_SIDE_STATUS_CONTROL1 },
134
  { "MouseMoveSideStatusControl2", KEYC_MOUSEMOVE_SIDE_STATUS_CONTROL2 },
135
  { "MouseMoveSideStatusControl3", KEYC_MOUSEMOVE_SIDE_STATUS_CONTROL3 },
136
  { "MouseMoveSideStatusControl4", KEYC_MOUSEMOVE_SIDE_STATUS_CONTROL4 },
137
  { "MouseMoveSideStatusControl5", KEYC_MOUSEMOVE_SIDE_STATUS_CONTROL5 },
138
  { "MouseMoveSideStatusControl6", KEYC_MOUSEMOVE_SIDE_STATUS_CONTROL6 },
139
  { "MouseMoveSideStatusControl7", KEYC_MOUSEMOVE_SIDE_STATUS_CONTROL7 },
140
  { "MouseMoveSideStatusControl8", KEYC_MOUSEMOVE_SIDE_STATUS_CONTROL8 },
141
  { "MouseMoveSideStatusControl9", KEYC_MOUSEMOVE_SIDE_STATUS_CONTROL9 },
142
  KEYC_MOUSE_STRING(MOUSEDOWN1, MouseDown1),
143
  KEYC_MOUSE_STRING(MOUSEDOWN2, MouseDown2),
144
  KEYC_MOUSE_STRING(MOUSEDOWN3, MouseDown3),
145
  KEYC_MOUSE_STRING(MOUSEDOWN6, MouseDown6),
146
  KEYC_MOUSE_STRING(MOUSEDOWN7, MouseDown7),
147
  KEYC_MOUSE_STRING(MOUSEDOWN8, MouseDown8),
148
  KEYC_MOUSE_STRING(MOUSEDOWN9, MouseDown9),
149
  KEYC_MOUSE_STRING(MOUSEDOWN10, MouseDown10),
150
  KEYC_MOUSE_STRING(MOUSEDOWN11, MouseDown11),
151
  KEYC_MOUSE_STRING(MOUSEUP1, MouseUp1),
152
  KEYC_MOUSE_STRING(MOUSEUP2, MouseUp2),
153
  KEYC_MOUSE_STRING(MOUSEUP3, MouseUp3),
154
  KEYC_MOUSE_STRING(MOUSEUP6, MouseUp6),
155
  KEYC_MOUSE_STRING(MOUSEUP7, MouseUp7),
156
  KEYC_MOUSE_STRING(MOUSEUP8, MouseUp8),
157
  KEYC_MOUSE_STRING(MOUSEUP9, MouseUp9),
158
  KEYC_MOUSE_STRING(MOUSEUP10, MouseUp10),
159
  KEYC_MOUSE_STRING(MOUSEUP11, MouseUp11),
160
  KEYC_MOUSE_STRING(MOUSEDRAG1, MouseDrag1),
161
  KEYC_MOUSE_STRING(MOUSEDRAG2, MouseDrag2),
162
  KEYC_MOUSE_STRING(MOUSEDRAG3, MouseDrag3),
163
  KEYC_MOUSE_STRING(MOUSEDRAG6, MouseDrag6),
164
  KEYC_MOUSE_STRING(MOUSEDRAG7, MouseDrag7),
165
  KEYC_MOUSE_STRING(MOUSEDRAG8, MouseDrag8),
166
  KEYC_MOUSE_STRING(MOUSEDRAG9, MouseDrag9),
167
  KEYC_MOUSE_STRING(MOUSEDRAG10, MouseDrag10),
168
  KEYC_MOUSE_STRING(MOUSEDRAG11, MouseDrag11),
169
  KEYC_MOUSE_STRING(MOUSEDRAGEND1, MouseDragEnd1),
170
  KEYC_MOUSE_STRING(MOUSEDRAGEND2, MouseDragEnd2),
171
  KEYC_MOUSE_STRING(MOUSEDRAGEND3, MouseDragEnd3),
172
  KEYC_MOUSE_STRING(MOUSEDRAGEND6, MouseDragEnd6),
173
  KEYC_MOUSE_STRING(MOUSEDRAGEND7, MouseDragEnd7),
174
  KEYC_MOUSE_STRING(MOUSEDRAGEND8, MouseDragEnd8),
175
  KEYC_MOUSE_STRING(MOUSEDRAGEND9, MouseDragEnd9),
176
  KEYC_MOUSE_STRING(MOUSEDRAGEND10, MouseDragEnd10),
177
  KEYC_MOUSE_STRING(MOUSEDRAGEND11, MouseDragEnd11),
178
  KEYC_MOUSE_STRING(WHEELUP, WheelUp),
179
  KEYC_MOUSE_STRING(WHEELDOWN, WheelDown),
180
  KEYC_MOUSE_STRING(SECONDCLICK1, SecondClick1),
181
  KEYC_MOUSE_STRING(SECONDCLICK2, SecondClick2),
182
  KEYC_MOUSE_STRING(SECONDCLICK3, SecondClick3),
183
  KEYC_MOUSE_STRING(SECONDCLICK6, SecondClick6),
184
  KEYC_MOUSE_STRING(SECONDCLICK7, SecondClick7),
185
  KEYC_MOUSE_STRING(SECONDCLICK8, SecondClick8),
186
  KEYC_MOUSE_STRING(SECONDCLICK9, SecondClick9),
187
  KEYC_MOUSE_STRING(SECONDCLICK10, SecondClick10),
188
  KEYC_MOUSE_STRING(SECONDCLICK11, SecondClick11),
189
  KEYC_MOUSE_STRING(DOUBLECLICK1, DoubleClick1),
190
  KEYC_MOUSE_STRING(DOUBLECLICK2, DoubleClick2),
191
  KEYC_MOUSE_STRING(DOUBLECLICK3, DoubleClick3),
192
  KEYC_MOUSE_STRING(DOUBLECLICK6, DoubleClick6),
193
  KEYC_MOUSE_STRING(DOUBLECLICK7, DoubleClick7),
194
  KEYC_MOUSE_STRING(DOUBLECLICK8, DoubleClick8),
195
  KEYC_MOUSE_STRING(DOUBLECLICK9, DoubleClick9),
196
  KEYC_MOUSE_STRING(DOUBLECLICK10, DoubleClick10),
197
  KEYC_MOUSE_STRING(DOUBLECLICK11, DoubleClick11),
198
  KEYC_MOUSE_STRING(TRIPLECLICK1, TripleClick1),
199
  KEYC_MOUSE_STRING(TRIPLECLICK2, TripleClick2),
200
  KEYC_MOUSE_STRING(TRIPLECLICK3, TripleClick3),
201
  KEYC_MOUSE_STRING(TRIPLECLICK6, TripleClick6),
202
  KEYC_MOUSE_STRING(TRIPLECLICK7, TripleClick7),
203
  KEYC_MOUSE_STRING(TRIPLECLICK8, TripleClick8),
204
  KEYC_MOUSE_STRING(TRIPLECLICK9, TripleClick9),
205
  KEYC_MOUSE_STRING(TRIPLECLICK10, TripleClick10),
206
  KEYC_MOUSE_STRING(TRIPLECLICK11, TripleClick11)
207
};
208
209
/* Find key string in table. */
210
static key_code
211
key_string_search_table(const char *string)
212
0
{
213
0
  u_int i, user;
214
215
0
  for (i = 0; i < nitems(key_string_table); i++) {
216
0
    if (strcasecmp(string, key_string_table[i].string) == 0)
217
0
      return (key_string_table[i].key);
218
0
  }
219
220
0
  if (sscanf(string, "User%u", &user) == 1 && user <= KEYC_NUSER)
221
0
    return (KEYC_USER + user);
222
223
0
  return (KEYC_UNKNOWN);
224
0
}
225
226
/* Find modifiers. */
227
static key_code
228
key_string_get_modifiers(const char **string)
229
0
{
230
0
  key_code  modifiers;
231
232
0
  modifiers = 0;
233
0
  while (((*string)[0] != '\0') && (*string)[1] == '-') {
234
0
    switch ((*string)[0]) {
235
0
    case 'C':
236
0
    case 'c':
237
0
      modifiers |= KEYC_CTRL;
238
0
      break;
239
0
    case 'M':
240
0
    case 'm':
241
0
      modifiers |= KEYC_META;
242
0
      break;
243
0
    case 'S':
244
0
    case 's':
245
0
      modifiers |= KEYC_SHIFT;
246
0
      break;
247
0
    default:
248
0
      *string = NULL;
249
0
      return (0);
250
0
    }
251
0
    *string += 2;
252
0
  }
253
0
  return (modifiers);
254
0
}
255
256
/* Lookup a string and convert to a key value. */
257
key_code
258
key_string_lookup_string(const char *string)
259
0
{
260
0
  key_code     key, modifiers = 0;
261
0
  u_int      u, i;
262
0
  struct utf8_data   ud, *udp;
263
0
  enum utf8_state    more;
264
0
  utf8_char    uc;
265
0
  char       m[MB_LEN_MAX + 1];
266
0
  int      mlen;
267
268
  /* Is this no key or any key? */
269
0
  if (strcasecmp(string, "None") == 0)
270
0
    return (KEYC_NONE);
271
0
  if (strcasecmp(string, "Any") == 0)
272
0
    return (KEYC_ANY);
273
274
  /* Is this a hexadecimal value? */
275
0
  if (string[0] == '0' && string[1] == 'x') {
276
0
    if (sscanf(string + 2, "%x", &u) != 1)
277
0
      return (KEYC_UNKNOWN);
278
0
    if (u < 32)
279
0
      return (u);
280
0
    mlen = wctomb(m, u);
281
0
    if (mlen <= 0 || mlen > MB_LEN_MAX)
282
0
      return (KEYC_UNKNOWN);
283
0
    m[mlen] = '\0';
284
285
0
    udp = utf8_fromcstr(m);
286
0
    if (udp == NULL ||
287
0
        udp[0].size == 0 ||
288
0
        udp[1].size != 0 ||
289
0
        utf8_from_data(&udp[0], &uc) != UTF8_DONE) {
290
0
      free(udp);
291
0
      return (KEYC_UNKNOWN);
292
0
    }
293
0
    free(udp);
294
0
    return (uc);
295
0
  }
296
297
  /* Check for short Ctrl key. */
298
0
  if (string[0] == '^' && string[1] != '\0') {
299
0
    if (string[2] == '\0')
300
0
      return (tolower((u_char)string[1])|KEYC_CTRL);
301
0
    modifiers |= KEYC_CTRL;
302
0
    string++;
303
0
  }
304
305
  /* Check for modifiers. */
306
0
  modifiers |= key_string_get_modifiers(&string);
307
0
  if (string == NULL || string[0] == '\0')
308
0
    return (KEYC_UNKNOWN);
309
310
  /* Is this a standard ASCII key? */
311
0
  if (string[1] == '\0' && (u_char)string[0] <= 127) {
312
0
    key = (u_char)string[0];
313
0
    if (key < 32)
314
0
      return (KEYC_UNKNOWN);
315
0
  } else {
316
    /* Try as a UTF-8 key. */
317
0
    if ((more = utf8_open(&ud, (u_char)*string)) == UTF8_MORE) {
318
0
      if (strlen(string) != ud.size)
319
0
        return (KEYC_UNKNOWN);
320
0
      for (i = 1; i < ud.size; i++)
321
0
        more = utf8_append(&ud, (u_char)string[i]);
322
0
      if (more != UTF8_DONE)
323
0
        return (KEYC_UNKNOWN);
324
0
      if (utf8_from_data(&ud, &uc) != UTF8_DONE)
325
0
        return (KEYC_UNKNOWN);
326
0
      return (uc|modifiers);
327
0
    }
328
329
    /* Otherwise look the key up in the table. */
330
0
    key = key_string_search_table(string);
331
0
    if (key == KEYC_UNKNOWN)
332
0
      return (KEYC_UNKNOWN);
333
0
    if (~modifiers & KEYC_META)
334
0
      key &= ~KEYC_IMPLIED_META;
335
0
  }
336
337
0
  return (key|modifiers);
338
0
}
339
340
/* Convert a key code into string format, with prefix if necessary. */
341
const char *
342
key_string_lookup_key(key_code key, int with_flags)
343
25.2k
{
344
25.2k
  key_code     saved = key;
345
25.2k
  static char    out[64];
346
25.2k
  char       tmp[8];
347
25.2k
  const char    *s;
348
25.2k
  u_int      i;
349
25.2k
  struct utf8_data   ud;
350
25.2k
  size_t       off;
351
352
25.2k
  *out = '\0';
353
354
  /* Literal keys are themselves. */
355
25.2k
  if (key & KEYC_LITERAL) {
356
0
    snprintf(out, sizeof out, "%c", (int)(key & 0xff));
357
0
    goto out;
358
0
  }
359
360
  /* Fill in the modifiers. */
361
25.2k
  if (key & KEYC_CTRL)
362
10.0k
    strlcat(out, "C-", sizeof out);
363
25.2k
  if (key & KEYC_META)
364
0
    strlcat(out, "M-", sizeof out);
365
25.2k
  if (key & KEYC_SHIFT)
366
0
    strlcat(out, "S-", sizeof out);
367
25.2k
  key &= KEYC_MASK_KEY;
368
369
  /* Handle no key. */
370
25.2k
  if (key == KEYC_NONE) {
371
9.54k
    s = "None";
372
9.54k
    goto append;
373
9.54k
  }
374
375
  /* Handle special keys. */
376
15.6k
  if (key == KEYC_UNKNOWN) {
377
0
    s = "Unknown";
378
0
    goto append;
379
0
  }
380
15.6k
  if (key == KEYC_ANY) {
381
0
    s = "Any";
382
0
    goto append;
383
0
  }
384
15.6k
  if (key == KEYC_FOCUS_IN) {
385
0
    s = "FocusIn";
386
0
    goto append;
387
0
  }
388
15.6k
  if (key == KEYC_FOCUS_OUT) {
389
0
    s = "FocusOut";
390
0
    goto append;
391
0
  }
392
15.6k
  if (key == KEYC_PASTE_START) {
393
0
    s = "PasteStart";
394
0
    goto append;
395
0
  }
396
15.6k
  if (key == KEYC_PASTE_END) {
397
0
    s = "PasteEnd";
398
0
    goto append;
399
0
  }
400
15.6k
  if (key == KEYC_REPORT_DARK_THEME) {
401
0
    s = "ReportDarkTheme";
402
0
    goto append;
403
0
  }
404
15.6k
  if (key == KEYC_REPORT_LIGHT_THEME) {
405
0
    s = "ReportLightTheme";
406
0
    goto append;
407
0
  }
408
15.6k
  if (key == KEYC_MOUSE) {
409
0
    s = "Mouse";
410
0
    goto append;
411
0
  }
412
15.6k
  if (key == KEYC_DRAGGING) {
413
0
    s = "Dragging";
414
0
    goto append;
415
0
  }
416
15.6k
  if (key == KEYC_MOUSEMOVE_PANE) {
417
0
    s = "MouseMovePane";
418
0
    goto append;
419
0
  }
420
15.6k
  if (key == KEYC_MOUSEMOVE_STATUS) {
421
0
    s = "MouseMoveStatus";
422
0
    goto append;
423
0
  }
424
15.6k
  if (key == KEYC_MOUSEMOVE_STATUS_LEFT) {
425
0
    s = "MouseMoveStatusLeft";
426
0
    goto append;
427
0
  }
428
15.6k
  if (key == KEYC_MOUSEMOVE_STATUS_RIGHT) {
429
0
    s = "MouseMoveStatusRight";
430
0
    goto append;
431
0
  }
432
15.6k
  if (key == KEYC_MOUSEMOVE_BORDER) {
433
0
    s = "MouseMoveBorder";
434
0
    goto append;
435
0
  }
436
15.6k
  if (KEYC_IS_USER(key)) {
437
0
    snprintf(tmp, sizeof tmp, "User%u", (u_int)(key - KEYC_USER));
438
0
    strlcat(out, tmp, sizeof out);
439
0
    goto out;
440
0
  }
441
442
  /* Try the key against the string table. */
443
37.1M
  for (i = 0; i < nitems(key_string_table); i++) {
444
37.1M
    if (key == (key_string_table[i].key & KEYC_MASK_KEY))
445
0
      break;
446
37.1M
  }
447
15.6k
  if (i != nitems(key_string_table)) {
448
0
    strlcat(out, key_string_table[i].string, sizeof out);
449
0
    goto out;
450
0
  }
451
452
  /* Is this a Unicode key? */
453
15.6k
  if (KEYC_IS_UNICODE(key)) {
454
0
    utf8_to_data(key, &ud);
455
0
    off = strlen(out);
456
0
    memcpy(out + off, ud.data, ud.size);
457
0
    out[off + ud.size] = '\0';
458
0
    goto out;
459
0
  }
460
461
  /* Invalid keys are errors. */
462
15.6k
  if (key > 255) {
463
0
    snprintf(out, sizeof out, "Invalid#%llx", saved);
464
0
    goto out;
465
0
  }
466
467
  /* Printable ASCII keys. */
468
15.6k
  if (key > 32 && key <= 126) {
469
10.0k
    tmp[0] = key;
470
10.0k
    tmp[1] = '\0';
471
10.0k
  } else if (key == 127)
472
5.66k
    xsnprintf(tmp, sizeof tmp, "C-?");
473
0
  else if (key >= 128)
474
0
    xsnprintf(tmp, sizeof tmp, "\\%llo", key);
475
476
15.6k
  strlcat(out, tmp, sizeof out);
477
15.6k
  goto out;
478
479
9.54k
append:
480
9.54k
  strlcat(out, s, sizeof out);
481
482
25.2k
out:
483
25.2k
  if (with_flags && (saved & KEYC_MASK_FLAGS) != 0) {
484
0
    strlcat(out, "[", sizeof out);
485
0
    if (saved & KEYC_LITERAL)
486
0
      strlcat(out, "L", sizeof out);
487
0
    if (saved & KEYC_KEYPAD)
488
0
      strlcat(out, "K", sizeof out);
489
0
    if (saved & KEYC_CURSOR)
490
0
      strlcat(out, "C", sizeof out);
491
0
    if (saved & KEYC_IMPLIED_META)
492
0
      strlcat(out, "I", sizeof out);
493
0
    if (saved & KEYC_BUILD_MODIFIERS)
494
0
      strlcat(out, "B", sizeof out);
495
0
    if (saved & KEYC_SENT)
496
0
      strlcat(out, "S", sizeof out);
497
0
    strlcat(out, "]", sizeof out);
498
0
  }
499
25.2k
  return (out);
500
9.54k
}