diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index 4b53150b..36ee3ed8 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -68,6 +68,29 @@ jobs: 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 + githubToken: ${{ github.token }} + 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=Debug -DTV_BUILD_USING_GPM=OFF -DTV_BUILD_APPS=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 diff --git a/examples/tvdemo/ascii.cpp b/examples/tvdemo/ascii.cpp index 8b737f17..16674dbc 100644 --- a/examples/tvdemo/ascii.cpp +++ b/examples/tvdemo/ascii.cpp @@ -226,7 +226,7 @@ void TReport::handleEvent(TEvent& event) { if (event.message.command == cmAsciiTableCmdBase + cmCharFocused) { - asciiChar = event.message.infoByte; + asciiChar = (uchar)(size_t)event.message.infoPtr; drawView(); } } diff --git a/examples/tvdemo/tvdemo3.cpp b/examples/tvdemo/tvdemo3.cpp index 1bd90d1c..0c5944d4 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 f19d8201..f0beac83 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/internal/ansidisp.h b/include/tvision/internal/ansidisp.h index b39fdcc8..b86f002a 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 00000000..9166ef53 --- /dev/null +++ b/include/tvision/internal/endian.h @@ -0,0 +1,29 @@ +#ifndef TVISION_ENDIAN_H +#define TVISION_ENDIAN_H + +#include + +namespace tvision +{ + +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 0cb8254e..750414d1 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 df02c5a9..f8800a99 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 f2f8fef3..bb63925a 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 71a65bbe..c4e823c2 100644 --- a/include/tvision/system.h +++ b/include/tvision/system.h @@ -187,8 +187,13 @@ inline void TMouse::registerHandler( unsigned mask, void (_FAR *func)() ) struct CharScanType { +#if !defined( TV_BIG_ENDIAN ) uchar charCode; uchar scanCode; +#else + uchar scanCode; + uchar charCode; +#endif }; struct KeyDownEvent diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt index c1f891d6..0b68ad7e 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) diff --git a/source/platform/ansidisp.cpp b/source/platform/ansidisp.cpp index bbb1237a..2b4fe0d8 100644 --- a/source/platform/ansidisp.cpp +++ b/source/platform/ansidisp.cpp @@ -156,9 +156,13 @@ struct alignas(8) colorconv_r uint8_t unused[2]; colorconv_r() = default; - colorconv_r(TermColor aColor, TColorAttr::Style aExtraFlags=0) noexcept + 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 a2a3621d..666e1f90 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 bda55b6d..9a45b51d 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; } diff --git a/source/platform/win32con.cpp b/source/platform/win32con.cpp index e62ffa2e..f2722a2b 100644 --- a/source/platform/win32con.cpp +++ b/source/platform/win32con.cpp @@ -413,7 +413,7 @@ bool getWin32Key(const KEY_EVENT_RECORD &KeyEvent, TEvent &ev, InputState &state ev.what = evKeyDown; ev.keyDown.charScan.scanCode = KeyEvent.wVirtualScanCode; - ev.keyDown.charScan.charCode = KeyEvent.uChar.AsciiChar; + ev.keyDown.charScan.charCode = (uchar) KeyEvent.uChar.UnicodeChar; ev.keyDown.controlKeyState = KeyEvent.dwControlKeyState & ( kbShift | kbCtrlShift | kbAltShift | kbScrollState | kbNumState | kbCapsState | kbEnhanced diff --git a/source/tvision/colorsel.cpp b/source/tvision/colorsel.cpp index a1d62c43..9ddea383 100644 --- a/source/tvision/colorsel.cpp +++ b/source/tvision/colorsel.cpp @@ -220,9 +220,9 @@ void TColorSelector::handleEvent( TEvent& event ) if( event.message.command == cmColorSet ) { if( selType == csBackground ) - color = event.message.infoByte >> 4; + color = (uchar)(size_t)event.message.infoPtr >> 4; else - color = event.message.infoByte & 0x0F; + color = (uchar)(size_t)event.message.infoPtr & 0x0F; drawView(); return; } @@ -285,7 +285,7 @@ void TMonoSelector::handleEvent( TEvent& event ) TCluster::handleEvent( event ); if( event.what == evBroadcast && event.message.command == cmColorSet ) { - value = event.message.infoByte; + value = (uchar)(size_t)event.message.infoPtr; drawView(); } } @@ -361,12 +361,12 @@ void TColorDisplay::handleEvent( TEvent& event ) switch( event.message.command ) { case cmColorBackgroundChanged: - *color = (*color & 0x0F) | ((event.message.infoByte << 4) & 0xF0); + *color = (*color & 0x0F) | (((uchar)(size_t)event.message.infoPtr << 4) & 0xF0); drawView(); break; case cmColorForegroundChanged: - *color = (*color & 0xF0) | (event.message.infoByte & 0x0F); + *color = (*color & 0xF0) | ((uchar)(size_t)event.message.infoPtr & 0x0F); drawView(); } } @@ -508,7 +508,7 @@ void TColorGroupList::handleEvent(TEvent& ev) TListViewer::handleEvent(ev); if ((ev.what == evBroadcast) && (ev.message.command == cmSaveColorIndex)) - setGroupIndex(focused, ev.message.infoByte); + setGroupIndex(focused, (uchar)(size_t)ev.message.infoPtr); } void TColorGroupList::setGroupIndex(uchar groupNum, uchar itemNum) @@ -759,7 +759,7 @@ void TColorDialog::handleEvent( TEvent& event ) groupIndex = groups->focused; TDialog::handleEvent( event ); if( event.what==evBroadcast && event.message.command==cmNewColorIndex ) - display->setColor( &pal->data[event.message.infoByte] ); + display->setColor( &pal->data[(uchar)(size_t)event.message.infoPtr] ); } ushort TColorDialog::dataSize() diff --git a/source/tvision/twindow.cpp b/source/tvision/twindow.cpp index 7c0d1780..1eb0bea6 100644 --- a/source/tvision/twindow.cpp +++ b/source/tvision/twindow.cpp @@ -155,7 +155,7 @@ void TWindow::handleEvent( TEvent& event ) } else if( event.what == evBroadcast && event.message.command == cmSelectWindowNum && - event.message.infoInt == number && + (short)(size_t)event.message.infoPtr == number && (options & ofSelectable) != 0 ) { diff --git a/test/platform/endian.test.cpp b/test/platform/endian.test.cpp new file mode 100644 index 00000000..863b59ae --- /dev/null +++ b/test/platform/endian.test.cpp @@ -0,0 +1,58 @@ +#define Uses_TColorAttr +#define Uses_TEvent +#include + +#include + +#include + +namespace tvision +{ + +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, KeyDownEventCharCodeAndScanCodeShouldBeCorrect) +{ + KeyDownEvent kev {}; + kev.keyCode = 0x1234; + EXPECT_EQ(kev.charScan.charCode, 0x34); + EXPECT_EQ(kev.charScan.scanCode, 0x12); +} + +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 731ac1af..fee62b51 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 43b26363..2d8774ba 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 1c478ddf..72bb086a 100644 --- a/test/platform/utf8.test.cpp +++ b/test/platform/utf8.test.cpp @@ -9,9 +9,11 @@ TEST(Utf8, ShouldConvertUtf16StringToUtf8) { static const TestCase, TStringView> testCases[] = { + // Use unicode escape sequences in order to get UTF-16 characters + // in the system's endianess. {"", ""}, - {"\x61\x00\x62\x00\x63\x00\x64\x00", "abcd"}, - {"\x6F\x00\x3D\xD8\x95\xDC\x57\x00", "o💕W"}, + {(const char (&)[8]) u"\u0061\u0062\u0063\u0064", "abcd"}, + {(const char (&)[8]) u"\u006F\U0001F495\u0057", "o💕W"}, }; for (auto &testCase : testCases)