From f933af5e18c2bc7b3ead2778447d5b1b171d1df4 Mon Sep 17 00:00:00 2001 From: magiblot Date: Sun, 21 Jan 2024 18:48:27 +0100 Subject: [PATCH] Add initial big endian support The largest deterrents to big endian compatibility were: - In the public API, the unions meant for aliasing and the bit fields: - In the case of KeyDownEvent, detecting key presses would become broken. The solution is as simple as reversing the field order in CharScanType. - In the case of MessageEvent, reading from 'infoChar', 'infoInt', etc. would not work as expected when the value had been initialized through 'infoPtr', such as when using the 'message' function. It is required to add padding before the non-pointer fields. A concise way of achieving this is by using bit fields. This comes at the expense of not being able to take the address of these fields anymore, but I have found no examples of such usage. - In the case of TColorBIOS and TColorRGB, the red and blue components would become swapped. A simple solution is to reorder the bit fields. - In the internal classes: - In TCellChar, the 'moveInt' method does bit-casting, which is fine as long as the parameter itself is the result of bit-casting from a byte array. However, 'moveChar' was implemented with an invocation to 'moveInt', breaking this rule. As a result, none of the single-byte characters that went through 'moveChar' would be displayed. - The TermColor and colorconv_r structs, which had manual bit-casting optimizations. This would result in colors not being displayed at all. Rather than changing the struct's layout, in this case it seems more convenient to simply add code to to reverse the casted bytes when necessary. Some other issues that I found were: - The internal method 'utf32To8' does bit-casting from an integer to a byte array, so bytes have to be reversed. - In tvdemo, reversing the mouse buttons would not work because the address of 'TEventQueue::mouseReverse' was being treated as an address to a int32_t, even though it is a Boolean. - In the far2l terminal extensions, integer values should always be in little-endian format, so bytes have to be reversed. - The 'AsciiChar' member of 'KEY_EVENT_RECORD' is meant to alias the least significant byte of 'UnicodeChar', so padding has to be added. - In the internal method 'fast_btoa', a little-endian-dependent optimization has been replaced by an endian-safe one. The code related to streaming and resource files has not been modified, so files generated in a little endian system may be unreadable in a big endian system and viceversa. This is not so important, though, since it is highly discouraged to use these features. I also took the oportunity to add tests for several of these points and even a GitHub Actions job to run these tests automatically in a big endian system. See magiblot/turbo#65. Fixes #134. Closes #136. Co-authored-by: MookThompson <58513526+MookThompson@users.noreply.github.com> --- .github/workflows/cmake.yml | 37 ++++++++--- examples/tvdemo/tvdemo3.cpp | 6 +- include/tvision/colors.h | 22 +++++++ include/tvision/compat/windows/windows.h | 4 ++ include/tvision/internal/ansidisp.h | 7 ++ include/tvision/internal/endian.h | 32 +++++++++ include/tvision/internal/strings.h | 16 ++--- include/tvision/internal/utf8.h | 7 +- include/tvision/scrncell.h | 5 +- include/tvision/system.h | 17 +++++ include/tvision/ttypes.h | 2 + source/CMakeLists.txt | 12 ++++ source/platform/ansidisp.cpp | 4 ++ source/platform/buffdisp.cpp | 2 +- source/platform/far2l.cpp | 27 ++++++++ source/tvision/tview.cpp | 13 ++-- test/CMakeLists.txt | 4 ++ test/platform/endian.test.cpp | 82 ++++++++++++++++++++++++ test/platform/far2l.test.cpp | 6 +- test/platform/terminal.test.cpp | 2 +- test/platform/utf8.test.cpp | 12 ++-- 21 files changed, 282 insertions(+), 37 deletions(-) create mode 100644 include/tvision/internal/endian.h create mode 100644 test/platform/endian.test.cpp diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 4b53150be..d358ff4ec 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -2,9 +2,6 @@ name: Build on: [push, pull_request] -env: - BUILD_TYPE: Release - jobs: build-linux-gcc: name: Linux (GCC) (Unity) @@ -13,11 +10,11 @@ jobs: - uses: actions/checkout@v3 - name: Install Dependencies - run: sudo apt -y install libncursesw5-dev libgpm-dev + run: sudo apt -y install libncursesw5-dev libgpm-dev libgtest-dev - name: Configure CMake shell: bash - run: cmake . -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DTV_REDUCE_APP_SIZE=ON -DTV_LIBRARY_UNITY_BUILD=ON + run: cmake . -DCMAKE_BUILD_TYPE=Release -DTV_BUILD_TESTS=ON -DTV_REDUCE_APP_SIZE=ON -DTV_LIBRARY_UNITY_BUILD=ON - name: Build shell: bash @@ -42,7 +39,7 @@ jobs: env: CC: gcc-5 CXX: g++-5 - run: cmake . -DCMAKE_BUILD_TYPE=$BUILD_TYPE + run: cmake . -DCMAKE_BUILD_TYPE=Release - name: Build shell: bash @@ -62,12 +59,34 @@ jobs: env: CC: clang CXX: clang++ - run: cmake . -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DTV_OPTIMIZE_BUILD=OFF + run: cmake . -DCMAKE_BUILD_TYPE=Release -DTV_OPTIMIZE_BUILD=OFF - name: Build shell: bash run: cmake --build . -j$(nproc) + build-linux-big-endian: + name: Linux (GCC) (Big Endian) + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v3 + + - name: Build in container + uses: uraimo/run-on-arch-action@v2 + with: + arch: s390x + distro: alpine_latest + dockerRunArgs: | + --volume "${PWD}:/app" + env: | + LC_CTYPE: C.UTF-8 + shell: /bin/sh + install: apk update && apk add make cmake g++ ncurses-dev gtest-dev linux-headers + run: | + cd /app + cmake . -DCMAKE_BUILD_TYPE= -DTV_BUILD_USING_GPM=OFF -DTV_BUILD_EXAMPLES=OFF -DTV_BUILD_TESTS=ON -DTV_LIBRARY_UNITY_BUILD=ON + cmake --build . -j$(nproc) + build-windows-msvc32: name: Windows (MSVC) (Win32) runs-on: windows-latest @@ -160,7 +179,7 @@ jobs: - name: Configure CMake shell: bash - run: cmake . -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_CXX_FLAGS="-static" -DTV_OPTIMIZE_BUILD=OFF + run: cmake . -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE= -DCMAKE_CXX_FLAGS="-static" -DTV_LIBRARY_UNITY_BUILD=ON - name: Build shell: bash @@ -211,7 +230,7 @@ jobs: - name: Configure CMake shell: bash - run: cmake . -DCMAKE_BUILD_TYPE=$BUILD_TYPE + run: cmake . -DCMAKE_BUILD_TYPE=Release - name: Build shell: bash diff --git a/examples/tvdemo/tvdemo3.cpp b/examples/tvdemo/tvdemo3.cpp index 1bd90d1cc..0c5944d4b 100644 --- a/examples/tvdemo/tvdemo3.cpp +++ b/examples/tvdemo/tvdemo3.cpp @@ -48,10 +48,12 @@ void TVDemo::mouse() if (mouseCage != 0) { + int32_t mouseReverse = TEventQueue::mouseReverse; mouseCage->helpCtx = hcOMMouseDBox; - mouseCage->setData(&(TEventQueue::mouseReverse)); + mouseCage->setData(&mouseReverse); if (deskTop->execView(mouseCage) != cmCancel) - mouseCage->getData(&(TEventQueue::mouseReverse)); + mouseCage->getData(&mouseReverse); + TEventQueue::mouseReverse = mouseReverse; } destroy( mouseCage ); diff --git a/include/tvision/colors.h b/include/tvision/colors.h index f19d82015..f0beac835 100644 --- a/include/tvision/colors.h +++ b/include/tvision/colors.h @@ -57,20 +57,34 @@ inline TColorAttr reverseAttribute(TColorAttr attr) struct TColorRGB { uint32_t +#ifndef TV_BIG_ENDIAN b : 8, g : 8, r : 8, _unused : 8; +#else + _unused : 8, + r : 8, + g : 8, + b : 8; +#endif TV_TRIVIALLY_CONVERTIBLE(TColorRGB, uint32_t, 0xFFFFFF) constexpr inline TColorRGB(uint8_t r, uint8_t g, uint8_t b); }; constexpr inline TColorRGB::TColorRGB(uint8_t r, uint8_t g, uint8_t b) : +#ifndef TV_BIG_ENDIAN b(b), g(g), r(r), _unused(0) +#else + _unused(0), + r(r), + g(g), + b(b) +#endif { } @@ -90,11 +104,19 @@ constexpr inline TColorRGB::TColorRGB(uint8_t r, uint8_t g, uint8_t b) : struct TColorBIOS { uint8_t +#ifndef TV_BIG_ENDIAN b : 1, g : 1, r : 1, bright : 1, _unused : 4; +#else + _unused : 4, + bright : 1, + r : 1, + g : 1, + b : 1; +#endif TV_TRIVIALLY_CONVERTIBLE(TColorBIOS, uint8_t, 0xF) }; diff --git a/include/tvision/compat/windows/windows.h b/include/tvision/compat/windows/windows.h index 253fe1ea7..f98c97851 100644 --- a/include/tvision/compat/windows/windows.h +++ b/include/tvision/compat/windows/windows.h @@ -204,7 +204,11 @@ typedef struct _KEY_EVENT_RECORD { WORD wVirtualScanCode; union { WCHAR UnicodeChar; +#ifndef TV_BIG_ENDIAN CHAR AsciiChar; +#else + struct { WCHAR : 8*sizeof(WCHAR) - 8, AsciiChar : 8; }; +#endif } uChar; DWORD dwControlKeyState; } KEY_EVENT_RECORD; diff --git a/include/tvision/internal/ansidisp.h b/include/tvision/internal/ansidisp.h index b39fdcc86..b86f002a2 100644 --- a/include/tvision/internal/ansidisp.h +++ b/include/tvision/internal/ansidisp.h @@ -5,6 +5,7 @@ #include #include +#include namespace tvision { @@ -30,6 +31,9 @@ struct TermColor TermColor& operator=(uint32_t val) noexcept { +#ifdef TV_BIG_ENDIAN + reverseBytes(val); +#endif memcpy(this, &val, sizeof(*this)); return *this; static_assert(sizeof(*this) == 4, ""); @@ -38,6 +42,9 @@ struct TermColor { uint32_t val; memcpy(&val, this, sizeof(*this)); +#ifdef TV_BIG_ENDIAN + reverseBytes(val); +#endif return val; } TermColor(uint8_t aIdx, TermColorTypes aType) noexcept diff --git a/include/tvision/internal/endian.h b/include/tvision/internal/endian.h new file mode 100644 index 000000000..932366daa --- /dev/null +++ b/include/tvision/internal/endian.h @@ -0,0 +1,32 @@ +#ifndef TVISION_ENDIAN_H +#define TVISION_ENDIAN_H + +#include + +namespace tvision +{ + +// Optimization: the 'reverseBytes' methods are written in such a way that the +// compiler will likely replace them with a BSWAP instruction or equivalent. + +inline void reverseBytes(uint16_t &val) +{ + val = (val << 8) | (val >> 8); +} + +inline void reverseBytes(uint32_t &val) +{ + val = ((val << 8) & 0xFF00FF00U) | ((val >> 8) & 0x00FF00FF); + val = (val << 16) | (val >> 16); +} + +inline void reverseBytes(uint64_t &val) +{ + val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL); + val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL); + val = (val << 32) | (val >> 32); +} + +} // namespace tvision + +#endif // TVISION_ENDIAN_H diff --git a/include/tvision/internal/strings.h b/include/tvision/internal/strings.h index 0cb8254e9..750414d1e 100644 --- a/include/tvision/internal/strings.h +++ b/include/tvision/internal/strings.h @@ -1,19 +1,17 @@ #ifndef TVISION_STRINGS_H #define TVISION_STRINGS_H -#ifndef _TV_VERSION #include -#endif namespace tvision { template inline constexpr Int string_as_int(TStringView s) noexcept +// CAUTION: It is not endian-safe to reinterpret the result as an array of bytes. { Int res = 0; for (size_t i = 0; i < min(s.size(), sizeof(res)); ++i) - // CAUTION: Assumes Little Endian. res |= uint64_t(uint8_t(s[i])) << 8*i; return res; } @@ -32,15 +30,15 @@ struct alignas(4) btoa_lut_elem_t using btoa_lut_t = constarray; inline char *fast_btoa(uint8_t value, char *buffer) noexcept +// Pre: the capacity of 'buffer' is at least 4 bytes. { extern const btoa_lut_t btoa_lut; const auto &lut = (btoa_lut_elem_t (&) [256]) btoa_lut; - // CAUTION: Assumes Little Endian. - // We can afford to write more bytes into 'buffer' than digits. - uint32_t asInt; - memcpy(&asInt, &lut[value], 4); - memcpy(buffer, &asInt, 4); - return buffer + (asInt >> 24); + // Optimization: read and write the whole LUT entry at once in order to + // minimize memory accesses. We can afford to write more bytes into 'buffer' + // than digits. + memcpy(buffer, &lut[value], 4); + return buffer + (uint8_t) buffer[3]; static_assert(sizeof(btoa_lut_elem_t) == 4, ""); } diff --git a/include/tvision/internal/utf8.h b/include/tvision/internal/utf8.h index df02c5a98..f8800a998 100644 --- a/include/tvision/internal/utf8.h +++ b/include/tvision/internal/utf8.h @@ -1,7 +1,9 @@ #ifndef TVISION_UTF8_H #define TVISION_UTF8_H -#include +#include + +#include #include #include @@ -66,6 +68,9 @@ inline size_t utf32To8(uint32_t u32, char u8[4]) noexcept (( u32 & 0b00111111) | 0b10000000) << 24; length = 4; } +#ifdef TV_BIG_ENDIAN + reverseBytes(asInt); +#endif memcpy(u8, &asInt, 4); return length; } diff --git a/include/tvision/scrncell.h b/include/tvision/scrncell.h index f2f8fef3e..bb63925a5 100644 --- a/include/tvision/scrncell.h +++ b/include/tvision/scrncell.h @@ -76,13 +76,14 @@ struct TCellChar inline void TCellChar::moveChar(char ch) { - moveInt((uchar) ch); + memset(this, 0, sizeof(*this)); + _text[0] = ch; } inline void TCellChar::moveInt(uint32_t mbc, bool wide) +// Pre: 'mbc' is a bit-casted multibyte-encoded character. { memset(this, 0, sizeof(*this)); - // CAUTION: Assumes Little Endian. memcpy(_text, &mbc, sizeof(mbc)); _flags = -int(wide) & fWide; } diff --git a/include/tvision/system.h b/include/tvision/system.h index 71a65bbe2..1244fecd4 100644 --- a/include/tvision/system.h +++ b/include/tvision/system.h @@ -187,8 +187,15 @@ inline void TMouse::registerHandler( unsigned mask, void (_FAR *func)() ) struct CharScanType { +#if !defined( TV_BIG_ENDIAN ) uchar charCode; uchar scanCode; +#else + // Due to the reverse byte order, swap the fields in order to preserve + // the aliasing with KeyDownEvent::keyCode. + uchar scanCode; + uchar charCode; +#endif }; struct KeyDownEvent @@ -222,11 +229,21 @@ struct MessageEvent union { void *infoPtr; +#if !defined( TV_BIG_ENDIAN ) int32_t infoLong; ushort infoWord; short infoInt; uchar infoByte; char infoChar; +#else + // Due to the reverse byte order, add padding at the beginning to + // preserve the aliasing with infoPtr. + struct { intptr_t : 8*sizeof(infoPtr) - 32, infoLong : 32; }; + struct { uintptr_t : 8*sizeof(infoPtr) - 16, infoWord : 16; }; + struct { intptr_t : 8*sizeof(infoPtr) - 16, infoInt : 16; }; + struct { uintptr_t : 8*sizeof(infoPtr) - 8, infoByte : 8; }; + struct { intptr_t : 8*sizeof(infoPtr) - 8, infoChar : 8; }; +#endif }; }; diff --git a/include/tvision/ttypes.h b/include/tvision/ttypes.h index f5ccea7b9..8c4fbccdb 100644 --- a/include/tvision/ttypes.h +++ b/include/tvision/ttypes.h @@ -50,6 +50,8 @@ typedef long int32_t; typedef uchar uint8_t; typedef ushort uint16_t; typedef ulong uint32_t; +typedef long intptr_t; +typedef ulong uintptr_t; #endif #ifdef __BORLANDC__ diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index c1f891d69..253e3f220 100644 --- a/source/CMakeLists.txt +++ b/source/CMakeLists.txt @@ -74,6 +74,17 @@ target_compile_definitions(${PROJECT_NAME} PRIVATE TVISION_NO_STL ) +include(TestBigEndian) + +TEST_BIG_ENDIAN(IS_BIG_ENDIAN) + +if (IS_BIG_ENDIAN) + tv_message(STATUS "Big endian system detected") + target_compile_definitions(${PROJECT_NAME} PUBLIC + TV_BIG_ENDIAN + ) +endif() + # Dependencies if (NOT WIN32) @@ -172,6 +183,7 @@ if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.16.0" AND TV_OPTIMIZE_BUILD) file(GLOB_RECURSE TVSOURCE_NOUNITY "${CMAKE_CURRENT_LIST_DIR}/tvision/s*.cpp") list(APPEND TVSOURCE_NOUNITY "${CMAKE_CURRENT_LIST_DIR}/tvision/new.cpp") list(REMOVE_ITEM TVSOURCE_NOUNITY + "${CMAKE_CURRENT_LIST_DIR}/tvision/snprintf.cpp" "${CMAKE_CURRENT_LIST_DIR}/tvision/stddlg.cpp" "${CMAKE_CURRENT_LIST_DIR}/tvision/strmstat.cpp" "${CMAKE_CURRENT_LIST_DIR}/tvision/syserr.cpp" diff --git a/source/platform/ansidisp.cpp b/source/platform/ansidisp.cpp index bbb1237a2..6b8139667 100644 --- a/source/platform/ansidisp.cpp +++ b/source/platform/ansidisp.cpp @@ -158,7 +158,11 @@ struct alignas(8) colorconv_r colorconv_r() = default; colorconv_r(TermColor aColor, TColorAttr::Style aExtraFlags=0) noexcept { + // Optimization: do bit-casting manually, just like with TermColor. uint64_t val = aColor | (uint64_t(aExtraFlags) << 32); +#ifdef TV_BIG_ENDIAN + reverseBytes(val); +#endif memcpy(this, &val, 8); static_assert(sizeof(*this) == 8, ""); } diff --git a/source/platform/buffdisp.cpp b/source/platform/buffdisp.cpp index a2a3621db..666e1f90e 100644 --- a/source/platform/buffdisp.cpp +++ b/source/platform/buffdisp.cpp @@ -340,7 +340,7 @@ inline void FlushScreenAlgorithm::writeCell() noexcept inline void FlushScreenAlgorithm::writeSpace() noexcept { TCellChar ch; - ch.moveInt(' '); + ch.moveChar(' '); writeCell(ch, cell->attr, 0); } diff --git a/source/platform/far2l.cpp b/source/platform/far2l.cpp index bda55b6d9..b9cff0a33 100644 --- a/source/platform/far2l.cpp +++ b/source/platform/far2l.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include @@ -97,6 +98,16 @@ ParseResult parseFar2lInput(GetChBuf &buf, TEvent &ev, InputState &state) noexce memcpy(&kev.dwControlKeyState, &out[6], 4); memcpy(&kev.uChar.UnicodeChar, &out[10], 4); +#ifdef TV_BIG_ENDIAN + // The protocol states that "all integer values are in + // little-endian format", so convert them. + reverseBytes(kev.wRepeatCount); + reverseBytes(kev.wVirtualKeyCode); + reverseBytes(kev.wVirtualScanCode); + reverseBytes(kev.dwControlKeyState); + reverseBytes((uint32_t &) kev.uChar.UnicodeChar); +#endif + if (uint16_t keyCode = virtualKeyCodeToKeyCode[kev.wVirtualKeyCode]) { kev.wVirtualScanCode = keyCode >> 8; @@ -120,6 +131,14 @@ ParseResult parseFar2lInput(GetChBuf &buf, TEvent &ev, InputState &state) noexce memcpy(&mev.dwControlKeyState, &out[8], 4); memcpy(&mev.dwEventFlags, &out[12], 4); +#ifdef TV_BIG_ENDIAN + reverseBytes((uint16_t &) mev.dwMousePosition.X); + reverseBytes((uint16_t &) mev.dwMousePosition.Y); + reverseBytes(mev.dwButtonState); + reverseBytes(mev.dwControlKeyState); + reverseBytes(mev.dwEventFlags); +#endif + getWin32Mouse(mev, ev, state); return Accepted; } @@ -143,6 +162,9 @@ ParseResult parseFar2lAnswer(GetChBuf &buf, TEvent &ev, InputState &state) noexc { uint32_t dataSize; memcpy(&dataSize, &decoded[decoded.size() - 5], 4); +#ifdef TV_BIG_ENDIAN + reverseBytes(dataSize); +#endif if (dataSize < UINT_MAX - 5 && decoded.size() >= 5 + dataSize) { TStringView text = decoded.substr(decoded.size() - 5 - dataSize, dataSize); @@ -195,7 +217,12 @@ inline size_t concat(char *out, uint32_t i, Args ...args) noexcept { size_t len = sizeof(i); if (write) + { +#ifdef TV_BIG_ENDIAN + reverseBytes(i); +#endif memcpy(out, &i, len); + } return len + concat(out + len, args...); } diff --git a/source/tvision/tview.cpp b/source/tvision/tview.cpp index 777de249a..7d195be15 100644 --- a/source/tvision/tview.cpp +++ b/source/tvision/tview.cpp @@ -725,12 +725,13 @@ void TView::putInFrontOf( TView *Target ) void TView::select() { - if( ! (options & ofSelectable)) - return; - if( (options & ofTopSelect) != 0 ) - makeFirst(); - else if( owner != 0 ) - owner->setCurrent( this, normalSelect ); + if( (options & ofSelectable) != 0 && owner != 0 ) + { + if( (options & ofTopSelect) != 0 ) + makeFirst(); + else + owner->setCurrent( this, normalSelect ); + } } void TView::setBounds( const TRect& bounds ) noexcept diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 64b50af53..502f847e5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -36,4 +36,8 @@ if (TV_BUILD_TESTS) ) add_custom_target(${PROJECT_NAME}-test-run ALL DEPENDS ${PROJECT_NAME}-test-passed) + if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.16.0" AND TV_OPTIMIZE_BUILD) + target_precompile_headers(${PROJECT_NAME}-test PRIVATE "${CMAKE_CURRENT_LIST_DIR}/test.h") + tv_enable_unity(${PROJECT_NAME}-test) + endif() endif() diff --git a/test/platform/endian.test.cpp b/test/platform/endian.test.cpp new file mode 100644 index 000000000..6b86f7578 --- /dev/null +++ b/test/platform/endian.test.cpp @@ -0,0 +1,82 @@ +#define Uses_TColorAttr +#define Uses_TEvent +#define Uses_TWindow +#include + +#include + +#include + +namespace tvision +{ + +TEST(Endianess, AliasingInKeyDownEventShouldWorkCorrectly) +{ + KeyDownEvent keyDown {}; + keyDown.keyCode = 0x1234; + EXPECT_EQ(keyDown.charScan.charCode, 0x34); + EXPECT_EQ(keyDown.charScan.scanCode, 0x12); +} + +TEST(Endianess, AliasingInMessageEventShouldWorkCorrectly) +{ + MessageEvent message {}; + message.infoPtr = (void *) 0x12345678; + + EXPECT_EQ(message.infoByte, 0x78); + EXPECT_EQ(message.infoChar, 0x78); + EXPECT_EQ(message.infoWord, 0x5678); + EXPECT_EQ(message.infoInt, 0x5678); + EXPECT_EQ(message.infoLong, 0x12345678); +} + +TEST(Endianess, TWindowShouldHandleSelectCommand) +{ + short number = 1; + TWindow window(TRect(0, 0, 0, 0), nullptr, number); + + void *response = message(&window, evBroadcast, cmSelectWindowNum, (void *)(size_t) number); + EXPECT_EQ(response, &window); + + window.shutDown(); +} + +TEST(Endianess, ColorsWithBitFieldsShouldBehaveAsExpected) +{ + TColorRGB rgb = 0x112233; + EXPECT_EQ(rgb.r, 0x11); + EXPECT_EQ(rgb.g, 0x22); + EXPECT_EQ(rgb.b, 0x33); + + TColorBIOS bios = 0x3; + EXPECT_EQ(bios.b, 1); + EXPECT_EQ(bios.g, 1); + EXPECT_EQ(bios.r, 0); + EXPECT_EQ(bios.bright, 0); + + bios = 0xC; + EXPECT_EQ(bios.b, 0); + EXPECT_EQ(bios.g, 0); + EXPECT_EQ(bios.r, 1); + EXPECT_EQ(bios.bright, 1); +} + +TEST(Endianess, TermColorShouldBehaveAsExpected) +{ + TColorRGB rgb = 0x123456; + TermColor termRgb {rgb, TermColor::RGB}; + EXPECT_EQ(termRgb.type, TermColor::RGB); + EXPECT_EQ(termRgb.bgr[0], 0x56); + EXPECT_EQ(termRgb.bgr[1], 0x34); + EXPECT_EQ(termRgb.bgr[2], 0x12); + + uint8_t idx = 15; + TermColor termIdx {idx, TermColor::Indexed}; + EXPECT_EQ(termIdx.type, TermColor::Indexed); + EXPECT_EQ(termIdx.idx, idx); + + TermColor termNoColor {TermColor::NoColor}; + EXPECT_EQ(termNoColor.type, TermColor::NoColor); +} + +} // namespace tvision diff --git a/test/platform/far2l.test.cpp b/test/platform/far2l.test.cpp index 731ac1af1..fee62b517 100644 --- a/test/platform/far2l.test.cpp +++ b/test/platform/far2l.test.cpp @@ -12,7 +12,7 @@ namespace tvision const ushort kbS = 0x1f73, kb9 = 0x0a39; -TEST(Far2l, ShouldReadFar2lKeys) +TEST(Far2l, ShouldReadFar2lInput) { static constexpr char longString[1024*1024] = {0}; static const TestCase testCases[] = @@ -32,6 +32,10 @@ TEST(Far2l, ShouldReadFar2lKeys) {"AQBWAAAACgAAAFYAAABL\x07", {Accepted, keyDownEv(kbAltV, kbLeftCtrl | kbLeftAlt, "")}}, {"AQBWAAAACgAAAAAAAABL\x07", {Ignored}}, // AltGr + V, UnicodeChar = 0 {"AQAMAAAAIgAAAAAAAABL\x07", {Ignored}}, // Alt + VK_CLEAR + {"AAAGAAAAAAAAAAAAAQAAAE0=\x07", {Accepted, mouseEv({0, 6}, meMouseMoved, 0, 0, 0)}}, + {"CQANAAQAAAAAAAAAAAAAAE0=\x07", {Accepted, mouseEv({9, 13}, 0, 0, mbMiddleButton, 0)}}, + {"CQANAAAAAAAAAAAAAAAAAE0=\x07", {Accepted, mouseEv({9, 13}, 0, 0, 0, 0)}}, // Button release. + {"AAAAAAAAAAAAAAAAAAAAAE0=\x07", {Accepted, mouseEv({0, 0}, 0, 0, 0, 0)}}, // Button release. }; for (auto &testCase : testCases) diff --git a/test/platform/terminal.test.cpp b/test/platform/terminal.test.cpp index 43b26363a..2d8774bad 100644 --- a/test/platform/terminal.test.cpp +++ b/test/platform/terminal.test.cpp @@ -66,7 +66,7 @@ TEST(TermIO, ShouldReadWin32InputModeKeys) {"\x1B[112;59;0;0;8;1_", {}}, // https://github.com/microsoft/terminal/issues/15083 { // SGR mouse event - "\x1B[0;0;27;1;0;1_" + "\x1B[0;0;27;1;0;1_" // \x1B[<0;52;12M "\x1B[0;0;91;1;0;1_" "\x1B[0;0;60;1;0;1_" "\x1B[0;0;48;1;0;1_" diff --git a/test/platform/utf8.test.cpp b/test/platform/utf8.test.cpp index 1c478ddf0..5728b35a1 100644 --- a/test/platform/utf8.test.cpp +++ b/test/platform/utf8.test.cpp @@ -10,18 +10,20 @@ TEST(Utf8, ShouldConvertUtf16StringToUtf8) static const TestCase, TStringView> testCases[] = { {"", ""}, - {"\x61\x00\x62\x00\x63\x00\x64\x00", "abcd"}, - {"\x6F\x00\x3D\xD8\x95\xDC\x57\x00", "o💕W"}, + // Use Unicode string literals so that the resulting UTF-16 characters + // are encoded using the system's native endianess. + {(const char (&)[8]) u"\u0061\u0062\u0063\u0064", "abcd"}, + {(const char (&)[8]) u"\u006F\U0001F495\u0057", "o💕W"}, }; for (auto &testCase : testCases) { - TSpan input { + TSpan u16Input { (const uint16_t *) testCase.input.data(), testCase.input.size()/2, }; - char *buf = new char[input.size()*3]; - size_t length = utf16To8(input, buf); + char *buf = new char[u16Input.size()*3]; + size_t length = utf16To8(u16Input, buf); TStringView actual {buf, length}; expectResultMatches(actual, testCase); delete[] buf;