Skip to content

Commit fe32490

Browse files
committed
Unicode character support in screen tab names
1 parent cd5d5e6 commit fe32490

File tree

5 files changed

+212
-7
lines changed

5 files changed

+212
-7
lines changed

Action.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -408,13 +408,14 @@ Htop_Reaction Action_setScreenTab(State* st, int x) {
408408
return 0;
409409
}
410410
const char* tab = settings->screens[i]->heading;
411-
int len = strlen(tab);
412-
if (x < s + len + 2) {
411+
const char* ptr = tab;
412+
int width = String_mbswidth(&ptr, SIZE_MAX, INT_MAX);
413+
if (x < s + width + 2) {
413414
settings->ssIndex = i;
414415
setActiveScreen(settings, st, i);
415416
return HTOP_UPDATE_PANELHDR | HTOP_REFRESH | HTOP_REDRAW_BAR;
416417
}
417-
s += len + 2 + SCREEN_TAB_COLUMN_GAP;
418+
s += width + 2 + SCREEN_TAB_COLUMN_GAP;
418419
}
419420
return 0;
420421
}

ScreenManager.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,11 +166,11 @@ static inline bool drawTab(const int* y, int* x, int l, const char* name, bool c
166166
(*x)++;
167167
if (*x >= l)
168168
return false;
169-
int nameLen = strlen(name);
170-
int n = MINIMUM(l - *x, nameLen);
169+
const char* ptr = name;
170+
int nameWidth = String_mbswidth(&ptr, SIZE_MAX, l - *x);
171171
attrset(CRT_colors[cur ? SCREENS_CUR_TEXT : SCREENS_OTH_TEXT]);
172-
mvaddnstr(*y, *x, name, n);
173-
*x += n;
172+
mvaddnstr(*y, *x, name, (int)(ptr - name));
173+
*x += nameWidth;
174174
if (*x >= l)
175175
return false;
176176
attrset(CRT_colors[cur ? SCREENS_CUR_BORDER : SCREENS_OTH_BORDER]);

XUtils.c

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@ in the source distribution for its full text.
1010
#include "XUtils.h"
1111

1212
#include <assert.h>
13+
#include <ctype.h> // IWYU pragma: keep
1314
#include <errno.h>
1415
#include <fcntl.h>
16+
#include <limits.h> // IWYU pragma: keep
1517
#include <math.h>
1618
#include <stdarg.h>
1719
#include <stdint.h>
@@ -224,6 +226,185 @@ size_t String_safeStrncpy(char* restrict dest, const char* restrict src, size_t
224226
return i;
225227
}
226228

229+
#ifdef HAVE_LIBNCURSESW
230+
static void String_encodeWChar(WCharEncoderState* ps, wchar_t wc) {
231+
char tempBuf[MB_LEN_MAX];
232+
233+
char* dest = ps->buf ? (char*)ps->buf + ps->pos : tempBuf;
234+
size_t len = wcrtomb(dest, wc, &ps->mbState);
235+
assert(len != (size_t)-1);
236+
assert(len > 0);
237+
238+
ps->pos += len;
239+
}
240+
#else
241+
static void String_encodeWChar(WCharEncoderState* ps, int c) {
242+
if (ps->buf) {
243+
((char*)ps->buf)[ps->pos] = (char)c;
244+
}
245+
ps->pos += 1;
246+
}
247+
#endif
248+
249+
void EncodePrintableString(WCharEncoderState* ps, const char* src, size_t maxLen, EncodeWChar encodeWChar) {
250+
assert(src || maxLen == 0);
251+
252+
size_t pos = 0;
253+
bool wasReplaced = false;
254+
255+
#ifdef HAVE_LIBNCURSESW
256+
const wchar_t replacementChar = CRT_utf8 ? L'\xFFFD' : L'?';
257+
wchar_t ch;
258+
259+
mbstate_t decState;
260+
memset(&decState, 0, sizeof(decState));
261+
mbstate_t newState;
262+
#else
263+
const char replacementChar = '?';
264+
char ch;
265+
#endif
266+
267+
do {
268+
size_t len = 0;
269+
bool shouldReplace = false;
270+
ch = 0;
271+
272+
if (pos < maxLen) {
273+
// Read the next character from the byte sequence
274+
#ifdef HAVE_LIBNCURSESW
275+
memcpy(&newState, &decState, sizeof(newState));
276+
len = mbrtowc(&ch, &src[pos], maxLen - pos, &newState);
277+
278+
switch (len) {
279+
280+
case (size_t)-2:
281+
errno = EILSEQ;
282+
shouldReplace = true;
283+
len = maxLen - pos;
284+
break;
285+
286+
case (size_t)-1:
287+
shouldReplace = true;
288+
len = 1;
289+
break;
290+
291+
case 0:
292+
assert(ch == 0);
293+
len = 1;
294+
// Fallthrough
295+
296+
default:
297+
memcpy(&decState, &newState, sizeof(decState));
298+
}
299+
#else
300+
len = 1;
301+
ch = src[pos];
302+
#endif
303+
}
304+
305+
pos += len;
306+
307+
// Filter unprintable characters
308+
if (!shouldReplace && ch != 0) {
309+
#ifdef HAVE_LIBNCURSESW
310+
shouldReplace = !iswprint(ch);
311+
#else
312+
shouldReplace = !isprint((unsigned char)ch);
313+
#endif
314+
}
315+
316+
if (shouldReplace) {
317+
ch = replacementChar;
318+
if (wasReplaced) {
319+
continue;
320+
}
321+
}
322+
wasReplaced = shouldReplace;
323+
324+
encodeWChar(ps, ch);
325+
} while (ch != 0);
326+
}
327+
328+
char* String_makePrintable(const char* str, size_t maxLen) {
329+
WCharEncoderState encState;
330+
331+
memset(&encState, 0, sizeof(encState));
332+
EncodePrintableString(&encState, str, maxLen, String_encodeWChar);
333+
size_t bufSize = encState.pos;
334+
assert(bufSize > 0);
335+
336+
memset(&encState, 0, sizeof(encState));
337+
char* buf = xMalloc(bufSize);
338+
encState.buf = buf;
339+
EncodePrintableString(&encState, str, maxLen, String_encodeWChar);
340+
assert(encState.pos == bufSize);
341+
encState.buf = NULL;
342+
343+
return buf;
344+
}
345+
346+
#ifndef HAVE_STRNLEN
347+
static size_t strnlen(const char* str, size_t maxLen) {
348+
size_t len;
349+
for (len = 0; len < maxLen && str[len] != '\0'; len++) {}
350+
return len;
351+
}
352+
#endif
353+
354+
int String_mbswidth(const char** str, size_t maxLen, int maxWidth) {
355+
assert(*str || maxLen == 0);
356+
357+
if (maxWidth < 0)
358+
maxWidth = INT_MAX;
359+
360+
const char* start = *str;
361+
362+
#ifdef HAVE_LIBNCURSESW
363+
mbstate_t state;
364+
memset(&state, 0, sizeof(state));
365+
366+
int totalWidth = 0;
367+
for (size_t pos = 0; pos < maxLen; ) {
368+
wchar_t wc;
369+
size_t len = mbrtowc(&wc, &start[pos], maxLen - pos, &state);
370+
if (len == 0 || len == (size_t)-2 || len == (size_t)-1) {
371+
assert(len != (size_t)-1);
372+
break;
373+
}
374+
375+
pos += len;
376+
377+
assert(wc != L'\0');
378+
int w = wcwidth(wc);
379+
if (w < 0) {
380+
assert(w >= 0);
381+
break;
382+
}
383+
384+
if (w > maxWidth - totalWidth)
385+
break;
386+
387+
if (w > 0 || CRT_utf8) {
388+
// (*str - start) will represent the length of the substring bounded
389+
// by the width limit.
390+
391+
// In Unicode, combining characters are always placed after the base
392+
// character. Some legacy 8-bit encodings instead place combining
393+
// characters before the base character.
394+
*str = &start[pos];
395+
}
396+
totalWidth += w;
397+
}
398+
399+
return totalWidth;
400+
#else
401+
maxLen = MINIMUM((unsigned int)maxWidth, maxLen);
402+
size_t len = strnlen(*str, maxLen);
403+
*str = start + len;
404+
return (int)len;
405+
#endif
406+
}
407+
227408
int xAsprintf(char** strp, const char* fmt, ...) {
228409
va_list vl;
229410
va_start(vl, fmt);

XUtils.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,21 @@ in the source distribution for its full text.
2323

2424
#include "Compat.h"
2525
#include "Macros.h"
26+
#include "ProvideCurses.h"
2627

2728

29+
typedef struct WCharEncoderState_ {
30+
size_t pos;
31+
void* buf;
32+
mbstate_t mbState;
33+
} WCharEncoderState;
34+
35+
#ifdef HAVE_LIBNCURSESW
36+
typedef ATTR_NONNULL void (*EncodeWChar)(WCharEncoderState* ps, wchar_t wc);
37+
#else
38+
typedef ATTR_NONNULL void (*EncodeWChar)(WCharEncoderState* ps, int c);
39+
#endif
40+
2841
ATTR_NORETURN
2942
void fail(void);
3043

@@ -102,6 +115,15 @@ static inline char* String_strchrnul(const char* s, int c) {
102115
ATTR_NONNULL ATTR_ACCESS3_W(1, 3) ATTR_ACCESS3_R(2, 3)
103116
size_t String_safeStrncpy(char* restrict dest, const char* restrict src, size_t size);
104117

118+
ATTR_NONNULL_N(1, 4) ATTR_ACCESS2_W(1) ATTR_ACCESS3_R(2, 3)
119+
void EncodePrintableString(WCharEncoderState* ps, const char* src, size_t maxLen, EncodeWChar encodeWChar);
120+
121+
ATTR_RETNONNULL ATTR_MALLOC ATTR_ACCESS3_R(1, 2)
122+
char* String_makePrintable(const char* str, size_t maxLen);
123+
124+
ATTR_NONNULL ATTR_ACCESS2_RW(1)
125+
int String_mbswidth(const char** str, size_t maxLen, int maxWidth);
126+
105127
ATTR_FORMAT(printf, 2, 3) ATTR_NONNULL_N(1, 2)
106128
int xAsprintf(char** strp, const char* fmt, ...);
107129

configure.ac

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,7 @@ AC_CHECK_FUNCS([ \
372372
sched_getscheduler \
373373
sched_setscheduler \
374374
strchrnul \
375+
strnlen \
375376
])
376377

377378
if test "$my_htop_platform" = darwin; then

0 commit comments

Comments
 (0)