From 30e3ac339967f19023e11679e3ad1880a42c93c7 Mon Sep 17 00:00:00 2001 From: Raphael Coeffic Date: Mon, 14 Feb 2022 12:48:45 +0100 Subject: [PATCH] Basic utf8 support for B&W radios --- radio/src/gui/128x64/lcd.cpp | 5 ++ radio/src/gui/212x64/lcd.cpp | 5 ++ radio/src/gui/common/stdlcd/CMakeLists.txt | 1 + radio/src/gui/common/stdlcd/utf8.cpp | 95 ++++++++++++++++++++++ radio/src/gui/common/stdlcd/utf8.h | 24 ++++++ radio/src/gui/common/stdlcd/view_text.cpp | 7 +- radio/src/strhelpers.cpp | 82 ++++++++++--------- radio/src/switches.cpp | 27 ++++-- radio/src/tests/lcd.cpp | 40 +++++++++ radio/src/translations/untranslated.h | 50 ++++-------- 10 files changed, 259 insertions(+), 77 deletions(-) create mode 100644 radio/src/gui/common/stdlcd/utf8.cpp create mode 100644 radio/src/gui/common/stdlcd/utf8.h diff --git a/radio/src/gui/128x64/lcd.cpp b/radio/src/gui/128x64/lcd.cpp index 8a054b33b..86bdf4a32 100644 --- a/radio/src/gui/128x64/lcd.cpp +++ b/radio/src/gui/128x64/lcd.cpp @@ -22,6 +22,7 @@ #include #include "opentx.h" #include "gui/common/stdlcd/fonts.h" +#include "gui/common/stdlcd/utf8.h" pixel_t displayBuf[DISPLAY_BUFFER_SIZE] __DMA; @@ -335,6 +336,10 @@ void lcdDrawSizedText(coord_t x, coord_t y, const char * s, uint8_t len, LcdFlag break; } else if (c >= 0x20) { + // UTF8 detection + c = map_utf8_char(s, len); + if (!c) break; + lcdDrawChar(x, y, c, flags); x = lcdNextPos; } diff --git a/radio/src/gui/212x64/lcd.cpp b/radio/src/gui/212x64/lcd.cpp index e49c01050..db2aa75ca 100644 --- a/radio/src/gui/212x64/lcd.cpp +++ b/radio/src/gui/212x64/lcd.cpp @@ -22,6 +22,7 @@ #include #include "opentx.h" #include "common/stdlcd/fonts.h" +#include "common/stdlcd/utf8.h" #if (defined(PCBX9E) || defined(PCBX9DP)) && defined(LCD_DUAL_BUFFER) pixel_t displayBuf1[DISPLAY_BUFFER_SIZE] __DMA; @@ -295,6 +296,10 @@ void lcdDrawSizedText(coord_t x, coord_t y, const char * s, uint8_t len, LcdFlag break; } else if (c >= 0x20) { + // UTF8 detection + c = map_utf8_char(s, len); + if (!c) break; + if (c == 46 && FONTSIZE(flags) == TINSIZE) { // '.' handling if (((flags & BLINK) && BLINK_ON_PHASE) || ((!(flags & BLINK) && (flags & INVERS)))) { lcdDrawSolidVerticalLine(x, y-1, 5); diff --git a/radio/src/gui/common/stdlcd/CMakeLists.txt b/radio/src/gui/common/stdlcd/CMakeLists.txt index bdc984a41..3c6096367 100644 --- a/radio/src/gui/common/stdlcd/CMakeLists.txt +++ b/radio/src/gui/common/stdlcd/CMakeLists.txt @@ -5,6 +5,7 @@ set(GUI_SRC ../common/stdlcd/menus.cpp ../common/stdlcd/draw_functions.cpp ../common/stdlcd/fonts.cpp + ../common/stdlcd/utf8.cpp ../common/stdlcd/popups.cpp ../common/stdlcd/model_inputs.cpp ../common/stdlcd/model_mixes.cpp diff --git a/radio/src/gui/common/stdlcd/utf8.cpp b/radio/src/gui/common/stdlcd/utf8.cpp new file mode 100644 index 000000000..14623cc9d --- /dev/null +++ b/radio/src/gui/common/stdlcd/utf8.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +/* -*- coding: utf-8 -*- */ + +#include +#include "definitions.h" + +#if defined(TRANSLATIONS_FR) +static wchar_t _utf8_lut[] = { + L'é', L'è', L'à', L'î', L'ç', +}; +#elif defined(TRANSLATIONS_DE) +static wchar_t _utf8_lut[] = { + L'Ä', L'ä', L'Ö', L'ö', L'Ü', L'ü', L'ß', +}; +#elif defined(TRANSLATIONS_CZ) +static wchar_t _utf8_lut[] = { + L'á', L'č', L'é', L'ě', L'í', L'ó', L'ř', + L'š', L'ú', L'ů', L'ý', L'Í', L'Ř', L'Ý', + L'ž', L'É', +}; +#elif defined(TRANSLATIONS_ES) +static wchar_t _utf8_lut[] = { + L'Ñ', L'ñ', L'Á', L'á', L'É', L'é', L'Í', + L'í', L'Ó', L'ó', L'ú', +}; +#elif defined(TRANSLATIONS_FI) +static wchar_t _utf8_lut[] = { + L'å', L'ä', L'ö', L'Å', L'Ä', L'Ö', +}; +#elif defined(TRANSLATIONS_IT) +static wchar_t _utf8_lut[] = { + L'à', L'ù', +}; +#elif defined(TRANSLATIONS_PL) +static wchar_t _utf8_lut[] = { + L'ą', L'ć', L'ę', L'ł', L'ń', L'ó', L'ś', + L'ż', L'ź', L'Ą', L'Ć', L'Ę', L'Ł', L'Ń', + L'Ó', L'Ś', L'Ż', L'Ź', +}; +#else + #define NO_UTF8_LUT +#endif + +#if !defined(NO_UTF8_LUT) +static unsigned char lookup_utf8_mapping(wchar_t w) +{ + for (int i=0; i < DIM(_utf8_lut); i++) { + if (w == _utf8_lut[i]) + return 0x95 + (uint8_t)i; // TODO: use constant + } + return 0x20; // return 'space' for unknown chars +} +#endif + +unsigned char map_utf8_char(const char*& s, uint8_t& len) +{ + unsigned char c = *s; + if ((c & 0xC0) == 0xC0) { + wchar_t w = (c & 0x1F) << 6; + if (!len) return 0; + len--; s++; c = *s; + w |= c & 0x3F; + // TODO: use constants + if (w >= 0x80 && w <= 0x94) { // extra chars + return (unsigned char)w; + } +#if !defined(NO_UTF8_LUT) + return lookup_utf8_mapping(w); +#else + return 0x20; +#endif + } + + return c; +} + diff --git a/radio/src/gui/common/stdlcd/utf8.h b/radio/src/gui/common/stdlcd/utf8.h new file mode 100644 index 000000000..8bec9822f --- /dev/null +++ b/radio/src/gui/common/stdlcd/utf8.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) EdgeTX + * + * Based on code named + * opentx - https://github.com/opentx/opentx + * th9x - http://code.google.com/p/th9x + * er9x - http://code.google.com/p/er9x + * gruvin9x - http://code.google.com/p/gruvin9x + * + * License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#pragma once + +unsigned char map_utf8_char(const char*& s, uint8_t& len); diff --git a/radio/src/gui/common/stdlcd/view_text.cpp b/radio/src/gui/common/stdlcd/view_text.cpp index 01aada192..35dbea976 100644 --- a/radio/src/gui/common/stdlcd/view_text.cpp +++ b/radio/src/gui/common/stdlcd/view_text.cpp @@ -60,14 +60,17 @@ static void sdReadTextFile(const char * filename, char lines[TEXT_VIEWER_LINES][ else if (c != '\\' && escape > 0 && escape < sizeof(escape_chars)) { escape_chars[escape - 1] = c; if (escape == 2 && !strncmp(escape_chars, "up", 2)) { - c = CHAR_UP; + lines[current_line-menuVerticalOffset][line_length++] = STR_CHAR_UP[0]; + c = STR_CHAR_UP[1]; } else if (escape == 2 && !strncmp(escape_chars, "dn", 2)) { - c = CHAR_DOWN; + lines[current_line-menuVerticalOffset][line_length++] = STR_CHAR_DOWN[0]; + c = STR_CHAR_DOWN[1]; } else if (escape == 3) { int val = atoi(escape_chars); if (val >= 200 && val < 225) { + lines[current_line-menuVerticalOffset][line_length++] = '\302'; c = '\200' + val - 200; } } diff --git a/radio/src/strhelpers.cpp b/radio/src/strhelpers.cpp index 9d7e3df03..00799bdb7 100644 --- a/radio/src/strhelpers.cpp +++ b/radio/src/strhelpers.cpp @@ -571,23 +571,24 @@ char *getSwitchPositionName(char *dest, swsrc_t idx) return dest; } - // this should be declared in header, but it used so much foreign symbols that we declare it in cpp-file and pre-instantiate it for the uses template char *getSourceString(char (&dest)[L], mixsrc_t idx) { + size_t dest_len = L; + if (idx == MIXSRC_NONE) { return getStringAtIndex(dest, STR_VSRCRAW, 0); } else if (idx <= MIXSRC_LAST_INPUT) { idx -= MIXSRC_FIRST_INPUT; - *dest = CHAR_INPUT; - if (strlen(g_model.inputNames[idx])) { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-truncation" - snprintf(dest + 1, L - 1, "%.*s", (int)sizeof(g_model.inputNames[idx]), g_model.inputNames[idx]); -#pragma GCC diagnostic pop + static_assert(L > sizeof(STR_CHAR_INPUT) - 1, "dest string too small"); + dest_len -= sizeof(STR_CHAR_INPUT) - 1; + char* pos = strAppend(dest, STR_CHAR_INPUT, sizeof(STR_CHAR_INPUT) - 1); + if (g_model.inputNames[idx][0] != '\0' && (dest_len > sizeof(g_model.inputNames[idx]))) { + memset(pos, 0, sizeof(g_model.inputNames[idx]) + 1); + strncpy(pos, g_model.inputNames[idx], sizeof(g_model.inputNames[idx])); } else { - strAppendUnsigned(dest + 1, idx + 1, 2); + strAppendUnsigned(pos, idx + 1, 2); } } @@ -595,46 +596,52 @@ char *getSourceString(char (&dest)[L], mixsrc_t idx) else if (idx <= MIXSRC_LAST_LUA) { #if defined(LUA_MODEL_SCRIPTS) div_t qr = div(idx - MIXSRC_FIRST_LUA, MAX_SCRIPT_OUTPUTS); - if (qr.quot < MAX_SCRIPTS && qr.rem < scriptInputsOutputs[qr.quot].outputsCount) { -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wformat-truncation" - // possible truncation is intentional - snprintf(dest + 1, L - 1, "%.*s", (int)sizeof(g_model.scriptsData[qr.quot].name), g_model.scriptsData[qr.quot].name); - // instance Name is empty : dest = n-ScriptFileName/OutputName - if (*(dest + 1) == '\0') { - snprintf(dest + 1, L - 1, "%d-%.*s/%.*s", qr.quot + 1, + if (qr.quot < MAX_SCRIPTS && + qr.rem < scriptInputsOutputs[qr.quot].outputsCount) { + + static_assert(L > sizeof(STR_CHAR_LUA) - 1, "dest string too small"); + dest_len -= sizeof(STR_CHAR_LUA) - 1; + char* pos = strAppend(dest, STR_CHAR_LUA, sizeof(STR_CHAR_LUA) - 1); + + if (g_model.scriptsData[qr.quot].name[0] != '\0') { + // instance Name is not empty : dest = InstanceName/OutputName + snprintf(pos, dest_len, "%.*s/%.*s", sizeof(g_model.scriptsData[qr.quot].name), g_model.scriptsData[qr.quot].name, + (int)sizeof(scriptInputsOutputs[qr.quot].outputs[qr.rem].name), scriptInputsOutputs[qr.quot].outputs[qr.rem].name); + } else { + // instance Name is empty : dest = n-ScriptFileName/OutputName + snprintf(pos, dest_len, "%d-%.*s/%.*s", qr.quot + 1, (int)sizeof(g_model.scriptsData[qr.quot].file), g_model.scriptsData[qr.quot].file, (int)sizeof(scriptInputsOutputs[qr.quot].outputs[qr.rem].name), scriptInputsOutputs[qr.quot].outputs[qr.rem].name); - // instance Name is not empty : dest = InstanceName/OutputName - } else { - snprintf(dest + 1, L - 1, "%.*s/%.*s", LEN_SCRIPT_NAME, g_model.scriptsData[qr.quot].name, - (int)sizeof(scriptInputsOutputs[qr.quot].outputs[qr.rem].name), scriptInputsOutputs[qr.quot].outputs[qr.rem].name); } -#pragma GCC diagnostic pop - *dest = CHAR_LUA; } #else - strcpy(dest, "N/A"); + strncpy(dest, "N/A", L-1); #endif } #endif else if (idx <= MIXSRC_LAST_POT) { if (g_eeGeneral.anaNames[idx - MIXSRC_Rud][0]) { - // TODO: add correct symbol + char* pos = dest; if (idx <= MIXSRC_LAST_STICK) { - dest[0] = CHAR_STICK; + pos = strAppend(pos, STR_CHAR_STICK, sizeof(STR_CHAR_STICK) - 1); + dest_len -= sizeof(STR_CHAR_STICK) - 1; #if NUM_SLIDERS > 0 } else if (idx < MIXSRC_FIRST_SLIDER) { - dest[0] = CHAR_POT; + pos = strAppend(pos, STR_CHAR_POT, sizeof(STR_CHAR_POT) - 1); + dest_len -= sizeof(STR_CHAR_POT) - 1; } else { - dest[0] = CHAR_SLIDER; + pos = strAppend(pos, STR_CHAR_SLIDER, sizeof(STR_CHAR_SLIDER) - 1); + dest_len -= sizeof(STR_CHAR_SLIDER) - 1; #else } else { - dest[0] = CHAR_POT; + pos = strAppend(pos, STR_CHAR_POT, sizeof(STR_CHAR_POT) - 1); + dest_len -= sizeof(STR_CHAR_POT) - 1; #endif } idx -= MIXSRC_Rud; - copyToTerminated(dest, g_eeGeneral.anaNames[idx], offset_t<1>{}); + size_t ana_len = std::min(sizeof(g_eeGeneral.anaNames[idx]), dest_len - 1); + strncpy(pos, g_eeGeneral.anaNames[idx], ana_len); + pos[ana_len] = '\0'; } else { idx -= MIXSRC_Rud; getStringAtIndex(dest, STR_VSRCRAW, idx + 1); @@ -645,7 +652,7 @@ char *getSourceString(char (&dest)[L], mixsrc_t idx) } else if (idx <= MIXSRC_LAST_SWITCH) { idx -= MIXSRC_FIRST_SWITCH; if (g_eeGeneral.switchNames[idx][0] != '\0') { - copyToTerminated(dest, g_eeGeneral.switchNames[idx], offset_t<1>{}); + copyToTerminated(dest, g_eeGeneral.switchNames[idx]); } else { getStringAtIndex(dest, STR_VSRCRAW, idx + MIXSRC_FIRST_SWITCH - MIXSRC_Rud + 1); @@ -676,13 +683,13 @@ char *getSourceString(char (&dest)[L], mixsrc_t idx) } else { idx -= MIXSRC_FIRST_TELEM; div_t qr = div(idx, 3); - dest[0] = CHAR_TELEMETRY; - char *pos = strAppend(&dest[1], g_model.telemetrySensors[qr.quot].label, - sizeof(g_model.telemetrySensors[qr.quot].label)); + char* pos = strAppend(dest, STR_CHAR_TELEMETRY, 2); + pos = strAppend(pos, g_model.telemetrySensors[qr.quot].label, + sizeof(g_model.telemetrySensors[qr.quot].label)); if (qr.rem) *pos = (qr.rem == 2 ? '+' : '-'); *++pos = '\0'; } - dest[L - 1] = '\0'; // assert the termination (should not be nesseccary) + dest[L - 1] = '\0'; // assert the termination return dest; } @@ -755,14 +762,15 @@ char *strAppendFilename(char *dest, const char *filename, const int size) return dest; } -std::string formatNumberAsString(int32_t val, LcdFlags flags, uint8_t len, const char * prefix, const char * suffix) +#if defined(LIBOPENUI) +std::string formatNumberAsString(int32_t val, LcdFlags flags, uint8_t len, + const char *prefix, const char *suffix) { char s[100]; BitmapBuffer::formatNumberAsString(s, 99, val, flags, len, prefix, suffix); return std::string(s); } - - +#endif #if defined(RTCLOCK) #include "rtc.h" diff --git a/radio/src/switches.cpp b/radio/src/switches.cpp index e115fa08c..b2e2920d4 100644 --- a/radio/src/switches.cpp +++ b/radio/src/switches.cpp @@ -745,6 +745,14 @@ void checkSwitches() LED_ERROR_END(); } #elif defined(GUI) + +static const char* switch_state_str[] { + " ", + STR_CHAR_UP, + "-", + STR_CHAR_DOWN, +}; + void checkSwitches() { swarnstate_t last_bad_switches = 0xff; @@ -780,10 +788,9 @@ void checkSwitches() ((states & mask) == (switches_states & mask)) ? 0 : INVERS; if (attr) { if (++numWarnings < 6) { - char c = (" " STR_CHAR_UP - "-" STR_CHAR_DOWN)[(states & mask) >> (i * 3)]; + const char* s = switch_state_str[(states & mask) >> (i * 3)]; drawSource(x, y, MIXSRC_FIRST_SWITCH + i, attr); - lcdDrawChar(lcdNextPos, y, c, attr); + lcdDrawText(lcdNextPos, y, s, attr); x = lcdNextPos + 3; } } @@ -801,9 +808,19 @@ void checkSwitches() if (++numWarnings < 6) { lcdDrawTextAtIndex(x, y, STR_VSRCRAW, NUM_STICKS + 1 + i, INVERS); if (IS_POT(POT1 + i)) - lcdDrawChar(lcdNextPos, y, g_model.potsWarnPosition[i] > GET_LOWRES_POT_POSITION(i) ? 126 : 127, INVERS); // TODO: use constants for chars + lcdDrawChar( + lcdNextPos, y, + g_model.potsWarnPosition[i] > GET_LOWRES_POT_POSITION(i) + ? 126 + : 127, + INVERS); // TODO: use constants for chars else - lcdDrawChar(lcdNextPos, y, g_model.potsWarnPosition[i] > GET_LOWRES_POT_POSITION(i) ? CHAR_UP : CHAR_DOWN, INVERS); + lcdDrawText( + lcdNextPos, y, + g_model.potsWarnPosition[i] > GET_LOWRES_POT_POSITION(i) + ? STR_CHAR_UP + : STR_CHAR_DOWN, + INVERS); x = lcdNextPos + 3; } } diff --git a/radio/src/tests/lcd.cpp b/radio/src/tests/lcd.cpp index 71947cad5..3a0b820d4 100644 --- a/radio/src/tests/lcd.cpp +++ b/radio/src/tests/lcd.cpp @@ -18,6 +18,7 @@ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ +/* -*- coding: utf-8 -*- */ #include #include @@ -267,6 +268,45 @@ TEST(Lcd, Dblsize) EXPECT_TRUE(checkScreenshot("dblsize")); } +#define TEST_CHAR_RIGHT "\302\200" +#define TEST_CHAR_LEFT "\302\201" +#define TEST_CHAR_UP "\302\202" +#define TEST_CHAR_DOWN "\302\203" + +#define TEST_CHAR_DELTA "\302\210" +#define TEST_CHAR_STICK "\302\211" +#define TEST_CHAR_POT "\302\212" +#define TEST_CHAR_SLIDER "\302\213" +#define TEST_CHAR_SWITCH "\302\214" +#define TEST_CHAR_TRIM "\302\215" +#define TEST_CHAR_INPUT "\302\216" +#define TEST_CHAR_FUNCTION "\302\217" +#define TEST_CHAR_CYC "\302\220" +#define TEST_CHAR_TRAINER "\302\221" +#define TEST_CHAR_CHANNEL "\302\222" +#define TEST_CHAR_TELEMETRY "\302\223" +#define TEST_CHAR_LUA "\302\224" + +//#define EXTRA_TEST TEST_CHAR_RIGHT TEST_CHAR_LEFT TEST_CHAR_UP TEST_CHAR_DOWN +#define EXTRA_TEST TEST_CHAR_DELTA TEST_CHAR_STICK TEST_CHAR_POT TEST_CHAR_SLIDER \ + TEST_CHAR_SWITCH TEST_CHAR_TRIM TEST_CHAR_INPUT + +TEST(Lcd, Extra_chars) +{ + lcdClear(); + lcdDrawText(0, 10, EXTRA_TEST); + EXPECT_TRUE(checkScreenshot("extra_chars")); +} + +#if defined(TRANSLATIONS_FR) +TEST(Lcd, fr_chars) +{ + lcdClear(); + lcdDrawText(0, 10, u8"éèàîç"); + EXPECT_TRUE(checkScreenshot("fr_chars")); +} +#endif + #if defined(PCBTARANIS) && LCD_W >= 212 TEST(Lcd, DrawSwitch) { diff --git a/radio/src/translations/untranslated.h b/radio/src/translations/untranslated.h index dd6aad31f..19bc72182 100644 --- a/radio/src/translations/untranslated.h +++ b/radio/src/translations/untranslated.h @@ -336,40 +336,24 @@ #define TR_SENSOR_SBEC_VOLTAGE "BecV" #define TR_SENSOR_SBEC_CURRENT "BecA" -#define STR_CHAR_RIGHT "\200" -#define STR_CHAR_LEFT "\201" -#define STR_CHAR_UP "\202" -#define STR_CHAR_DOWN "\203" +#define STR_CHAR_RIGHT "\302\200" +#define STR_CHAR_LEFT "\302\201" +#define STR_CHAR_UP "\302\202" +#define STR_CHAR_DOWN "\302\203" -#define STR_CHAR_DELTA "\210" -#define STR_CHAR_STICK "\211" -#define STR_CHAR_POT "\212" -#define STR_CHAR_SLIDER "\213" -#define STR_CHAR_SWITCH "\214" -#define STR_CHAR_TRIM "\215" -#define STR_CHAR_INPUT "\216" -#define STR_CHAR_FUNCTION "\217" -#define STR_CHAR_CYC "\220" -#define STR_CHAR_TRAINER "\221" -#define STR_CHAR_CHANNEL "\222" -#define STR_CHAR_TELEMETRY "\223" -#define STR_CHAR_LUA "\224" - -#define CHAR_UP STR_CHAR_UP[0] -#define CHAR_DOWN STR_CHAR_DOWN[0] -#define CHAR_DELTA STR_CHAR_DELTA[0] -#define CHAR_STICK STR_CHAR_STICK[0] -#define CHAR_POT STR_CHAR_POT[0] -#define CHAR_SLIDER STR_CHAR_SLIDER[0] -#define CHAR_SWITCH STR_CHAR_SWITCH[0] -#define CHAR_TRIM STR_CHAR_TRIM[0] -#define CHAR_INPUT STR_CHAR_INPUT[0] -#define CHAR_FUNCTION STR_CHAR_FUNCTION[0] -#define CHAR_CYC STR_CHAR_CYC[0] -#define CHAR_TRAINER STR_CHAR_TRAINER[0] -#define CHAR_CHANNEL STR_CHAR_CHANNEL[0] -#define CHAR_TELEMETRY STR_CHAR_TELEMETRY[0] -#define CHAR_LUA STR_CHAR_LUA[0] +#define STR_CHAR_DELTA "\302\210" +#define STR_CHAR_STICK "\302\211" +#define STR_CHAR_POT "\302\212" +#define STR_CHAR_SLIDER "\302\213" +#define STR_CHAR_SWITCH "\302\214" +#define STR_CHAR_TRIM "\302\215" +#define STR_CHAR_INPUT "\302\216" +#define STR_CHAR_FUNCTION "\302\217" +#define STR_CHAR_CYC "\302\220" +#define STR_CHAR_TRAINER "\302\221" +#define STR_CHAR_CHANNEL "\302\222" +#define STR_CHAR_TELEMETRY "\302\223" +#define STR_CHAR_LUA "\302\224" #define LEN_FSGROUPS "\001" #define TR_FSGROUPS "-""1""2""3"