pax_global_header00006660000000000000000000000064147730354720014526gustar00rootroot0000000000000052 comment=69f8e9a1f866f6d4f682040d872ab93f3360f6df termpaint-0.3.1/000077500000000000000000000000001477303547200135325ustar00rootroot00000000000000termpaint-0.3.1/.cirrus.yml000066400000000000000000000005721477303547200156460ustar00rootroot00000000000000macos_instance: image: ghcr.io/cirruslabs/macos-ventura-xcode:14.2 task: name: ci/cirrus/macOS setup_script: |- brew install meson pkg-config info_script: |- uname -a git log --pretty=oneline -n1 configure_script: |- meson -Db_lundef=false _build compile_script: |- ninja -v -C _build test_script: |- cd tests ../_build/testtermpaint termpaint-0.3.1/.gitattributes000066400000000000000000000001151477303547200164220ustar00rootroot00000000000000*.h diff=cpp *.c diff=cpp *.cpp diff=cpp *.inc linguist-language=c termpaint-0.3.1/.gitignore000066400000000000000000000000161477303547200155170ustar00rootroot00000000000000_build _local termpaint-0.3.1/COPYING000066400000000000000000000024001477303547200145610ustar00rootroot00000000000000Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. termpaint-0.3.1/README.md000066400000000000000000000040141477303547200150100ustar00rootroot00000000000000

Termpaint

Documentation · Getting Started ·

Low level terminal interface library for modern terminals. ## Documentation The full documentation for Termpaint can be found [here](https://termpaint.namepad.de/latest/). ## Building / Installing $ meson setup -Dprefix=$HOME/opt/termpaint/ _build $ ninja -C _build $ ninja -C _build install ## Example See [Getting started](https://termpaint.namepad.de/latest/getting-started.html) or [full source](doc/getting-started.c). integration = termpaintx_full_integration_setup_terminal_fullscreen( "+kbdsig +kbdsigint", event_callback, &quit, &terminal); surface = termpaint_terminal_get_surface(terminal); termpaint_surface_clear(surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(surface, 0, 0, "Hello World", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(terminal, false); while (!quit) { if (!termpaintx_full_integration_do_iteration(integration)) { // some kind of error break; } } termpaint_terminal_free_with_restore(terminal); ## Included examples * attrdemo [demo/attrs.c](demo/attrs.c) Show attributes and colors. * playground [playground2.cpp](playground2.cpp) Show keyboard events. * life [demo/life.c](demo/life.c) A simple "Conway's Game of Life" demo. * shuffle [demo/shuffle.c](demo/shuffle.c) A simple shuffle numbers demo. * textwrap [demo/textwrap.c](demo/textwrap.c) Usage of text measurement to wrap text. ## Why? See this [blog post](https://tty.uchuujin.de/2020/11/journey-of-termpaint/). ## License Termpaint is licensed under the [Boost Software License 1.0](COPYING)termpaint-0.3.1/charclassification.inc000066400000000000000000001157021477303547200200640ustar00rootroot00000000000000static const uint16_t termpaint_char_width_offsets_default[0x45] = { 0, 450, 453, 530, 554, 749, 764, 788, 931, 932, 933, 934, 936, 937, 938, 939, 941, 942, 943, 944, 945, 946, 947, 948, 949, 950, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 988, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, }; static const uint16_t termpaint_char_width_data_default[] = { // U+000000, NEW_WIDTH(0x0000, 1), // U+000000 NEW_WIDTH(0x0300, 0), // U+000300 NEW_WIDTH(0x0370, 1), // U+000370 NEW_WIDTH(0x0483, 0), // U+000483 NEW_WIDTH(0x048a, 1), // U+00048a NEW_WIDTH(0x0591, 0), // U+000591 NEW_WIDTH(0x05be, 1), // U+0005be NEW_WIDTH(0x05bf, 0), // U+0005bf NEW_WIDTH(0x05c0, 1), // U+0005c0 NEW_WIDTH(0x05c1, 0), // U+0005c1 NEW_WIDTH(0x05c3, 1), // U+0005c3 NEW_WIDTH(0x05c4, 0), // U+0005c4 NEW_WIDTH(0x05c6, 1), // U+0005c6 NEW_WIDTH(0x05c7, 0), // U+0005c7 NEW_WIDTH(0x05c8, 1), // U+0005c8 NEW_WIDTH(0x0600, 0), // U+000600 NEW_WIDTH(0x0606, 1), // U+000606 NEW_WIDTH(0x0610, 0), // U+000610 NEW_WIDTH(0x061b, 1), // U+00061b NEW_WIDTH(0x061c, 0), // U+00061c NEW_WIDTH(0x061d, 1), // U+00061d NEW_WIDTH(0x064b, 0), // U+00064b NEW_WIDTH(0x0660, 1), // U+000660 NEW_WIDTH(0x0670, 0), // U+000670 NEW_WIDTH(0x0671, 1), // U+000671 NEW_WIDTH(0x06d6, 0), // U+0006d6 NEW_WIDTH(0x06de, 1), // U+0006de NEW_WIDTH(0x06df, 0), // U+0006df NEW_WIDTH(0x06e5, 1), // U+0006e5 NEW_WIDTH(0x06e7, 0), // U+0006e7 NEW_WIDTH(0x06e9, 1), // U+0006e9 NEW_WIDTH(0x06ea, 0), // U+0006ea NEW_WIDTH(0x06ee, 1), // U+0006ee NEW_WIDTH(0x070f, 0), // U+00070f NEW_WIDTH(0x0710, 1), // U+000710 NEW_WIDTH(0x0711, 0), // U+000711 NEW_WIDTH(0x0712, 1), // U+000712 NEW_WIDTH(0x0730, 0), // U+000730 NEW_WIDTH(0x074b, 1), // U+00074b NEW_WIDTH(0x07a6, 0), // U+0007a6 NEW_WIDTH(0x07b1, 1), // U+0007b1 NEW_WIDTH(0x07eb, 0), // U+0007eb NEW_WIDTH(0x07f4, 1), // U+0007f4 NEW_WIDTH(0x07fd, 0), // U+0007fd NEW_WIDTH(0x07fe, 1), // U+0007fe NEW_WIDTH(0x0816, 0), // U+000816 NEW_WIDTH(0x081a, 1), // U+00081a NEW_WIDTH(0x081b, 0), // U+00081b NEW_WIDTH(0x0824, 1), // U+000824 NEW_WIDTH(0x0825, 0), // U+000825 NEW_WIDTH(0x0828, 1), // U+000828 NEW_WIDTH(0x0829, 0), // U+000829 NEW_WIDTH(0x082e, 1), // U+00082e NEW_WIDTH(0x0859, 0), // U+000859 NEW_WIDTH(0x085c, 1), // U+00085c NEW_WIDTH(0x0890, 0), // U+000890 NEW_WIDTH(0x0892, 1), // U+000892 NEW_WIDTH(0x0898, 0), // U+000898 NEW_WIDTH(0x08a0, 1), // U+0008a0 NEW_WIDTH(0x08ca, 0), // U+0008ca NEW_WIDTH(0x0903, 1), // U+000903 NEW_WIDTH(0x093a, 0), // U+00093a NEW_WIDTH(0x093b, 1), // U+00093b NEW_WIDTH(0x093c, 0), // U+00093c NEW_WIDTH(0x093d, 1), // U+00093d NEW_WIDTH(0x0941, 0), // U+000941 NEW_WIDTH(0x0949, 1), // U+000949 NEW_WIDTH(0x094d, 0), // U+00094d NEW_WIDTH(0x094e, 1), // U+00094e NEW_WIDTH(0x0951, 0), // U+000951 NEW_WIDTH(0x0958, 1), // U+000958 NEW_WIDTH(0x0962, 0), // U+000962 NEW_WIDTH(0x0964, 1), // U+000964 NEW_WIDTH(0x0981, 0), // U+000981 NEW_WIDTH(0x0982, 1), // U+000982 NEW_WIDTH(0x09bc, 0), // U+0009bc NEW_WIDTH(0x09bd, 1), // U+0009bd NEW_WIDTH(0x09c1, 0), // U+0009c1 NEW_WIDTH(0x09c5, 1), // U+0009c5 NEW_WIDTH(0x09cd, 0), // U+0009cd NEW_WIDTH(0x09ce, 1), // U+0009ce NEW_WIDTH(0x09e2, 0), // U+0009e2 NEW_WIDTH(0x09e4, 1), // U+0009e4 NEW_WIDTH(0x09fe, 0), // U+0009fe NEW_WIDTH(0x09ff, 1), // U+0009ff NEW_WIDTH(0x0a01, 0), // U+000a01 NEW_WIDTH(0x0a03, 1), // U+000a03 NEW_WIDTH(0x0a3c, 0), // U+000a3c NEW_WIDTH(0x0a3d, 1), // U+000a3d NEW_WIDTH(0x0a41, 0), // U+000a41 NEW_WIDTH(0x0a43, 1), // U+000a43 NEW_WIDTH(0x0a47, 0), // U+000a47 NEW_WIDTH(0x0a49, 1), // U+000a49 NEW_WIDTH(0x0a4b, 0), // U+000a4b NEW_WIDTH(0x0a4e, 1), // U+000a4e NEW_WIDTH(0x0a51, 0), // U+000a51 NEW_WIDTH(0x0a52, 1), // U+000a52 NEW_WIDTH(0x0a70, 0), // U+000a70 NEW_WIDTH(0x0a72, 1), // U+000a72 NEW_WIDTH(0x0a75, 0), // U+000a75 NEW_WIDTH(0x0a76, 1), // U+000a76 NEW_WIDTH(0x0a81, 0), // U+000a81 NEW_WIDTH(0x0a83, 1), // U+000a83 NEW_WIDTH(0x0abc, 0), // U+000abc NEW_WIDTH(0x0abd, 1), // U+000abd NEW_WIDTH(0x0ac1, 0), // U+000ac1 NEW_WIDTH(0x0ac6, 1), // U+000ac6 NEW_WIDTH(0x0ac7, 0), // U+000ac7 NEW_WIDTH(0x0ac9, 1), // U+000ac9 NEW_WIDTH(0x0acd, 0), // U+000acd NEW_WIDTH(0x0ace, 1), // U+000ace NEW_WIDTH(0x0ae2, 0), // U+000ae2 NEW_WIDTH(0x0ae4, 1), // U+000ae4 NEW_WIDTH(0x0afa, 0), // U+000afa NEW_WIDTH(0x0b00, 1), // U+000b00 NEW_WIDTH(0x0b01, 0), // U+000b01 NEW_WIDTH(0x0b02, 1), // U+000b02 NEW_WIDTH(0x0b3c, 0), // U+000b3c NEW_WIDTH(0x0b3d, 1), // U+000b3d NEW_WIDTH(0x0b3f, 0), // U+000b3f NEW_WIDTH(0x0b40, 1), // U+000b40 NEW_WIDTH(0x0b41, 0), // U+000b41 NEW_WIDTH(0x0b45, 1), // U+000b45 NEW_WIDTH(0x0b4d, 0), // U+000b4d NEW_WIDTH(0x0b4e, 1), // U+000b4e NEW_WIDTH(0x0b55, 0), // U+000b55 NEW_WIDTH(0x0b57, 1), // U+000b57 NEW_WIDTH(0x0b62, 0), // U+000b62 NEW_WIDTH(0x0b64, 1), // U+000b64 NEW_WIDTH(0x0b82, 0), // U+000b82 NEW_WIDTH(0x0b83, 1), // U+000b83 NEW_WIDTH(0x0bc0, 0), // U+000bc0 NEW_WIDTH(0x0bc1, 1), // U+000bc1 NEW_WIDTH(0x0bcd, 0), // U+000bcd NEW_WIDTH(0x0bce, 1), // U+000bce NEW_WIDTH(0x0c00, 0), // U+000c00 NEW_WIDTH(0x0c01, 1), // U+000c01 NEW_WIDTH(0x0c04, 0), // U+000c04 NEW_WIDTH(0x0c05, 1), // U+000c05 NEW_WIDTH(0x0c3c, 0), // U+000c3c NEW_WIDTH(0x0c3d, 1), // U+000c3d NEW_WIDTH(0x0c3e, 0), // U+000c3e NEW_WIDTH(0x0c41, 1), // U+000c41 NEW_WIDTH(0x0c46, 0), // U+000c46 NEW_WIDTH(0x0c49, 1), // U+000c49 NEW_WIDTH(0x0c4a, 0), // U+000c4a NEW_WIDTH(0x0c4e, 1), // U+000c4e NEW_WIDTH(0x0c55, 0), // U+000c55 NEW_WIDTH(0x0c57, 1), // U+000c57 NEW_WIDTH(0x0c62, 0), // U+000c62 NEW_WIDTH(0x0c64, 1), // U+000c64 NEW_WIDTH(0x0c81, 0), // U+000c81 NEW_WIDTH(0x0c82, 1), // U+000c82 NEW_WIDTH(0x0cbc, 0), // U+000cbc NEW_WIDTH(0x0cbd, 1), // U+000cbd NEW_WIDTH(0x0cbf, 0), // U+000cbf NEW_WIDTH(0x0cc0, 1), // U+000cc0 NEW_WIDTH(0x0cc6, 0), // U+000cc6 NEW_WIDTH(0x0cc7, 1), // U+000cc7 NEW_WIDTH(0x0ccc, 0), // U+000ccc NEW_WIDTH(0x0cce, 1), // U+000cce NEW_WIDTH(0x0ce2, 0), // U+000ce2 NEW_WIDTH(0x0ce4, 1), // U+000ce4 NEW_WIDTH(0x0d00, 0), // U+000d00 NEW_WIDTH(0x0d02, 1), // U+000d02 NEW_WIDTH(0x0d3b, 0), // U+000d3b NEW_WIDTH(0x0d3d, 1), // U+000d3d NEW_WIDTH(0x0d41, 0), // U+000d41 NEW_WIDTH(0x0d45, 1), // U+000d45 NEW_WIDTH(0x0d4d, 0), // U+000d4d NEW_WIDTH(0x0d4e, 1), // U+000d4e NEW_WIDTH(0x0d62, 0), // U+000d62 NEW_WIDTH(0x0d64, 1), // U+000d64 NEW_WIDTH(0x0d81, 0), // U+000d81 NEW_WIDTH(0x0d82, 1), // U+000d82 NEW_WIDTH(0x0dca, 0), // U+000dca NEW_WIDTH(0x0dcb, 1), // U+000dcb NEW_WIDTH(0x0dd2, 0), // U+000dd2 NEW_WIDTH(0x0dd5, 1), // U+000dd5 NEW_WIDTH(0x0dd6, 0), // U+000dd6 NEW_WIDTH(0x0dd7, 1), // U+000dd7 NEW_WIDTH(0x0e31, 0), // U+000e31 NEW_WIDTH(0x0e32, 1), // U+000e32 NEW_WIDTH(0x0e34, 0), // U+000e34 NEW_WIDTH(0x0e3b, 1), // U+000e3b NEW_WIDTH(0x0e47, 0), // U+000e47 NEW_WIDTH(0x0e4f, 1), // U+000e4f NEW_WIDTH(0x0eb1, 0), // U+000eb1 NEW_WIDTH(0x0eb2, 1), // U+000eb2 NEW_WIDTH(0x0eb4, 0), // U+000eb4 NEW_WIDTH(0x0ebd, 1), // U+000ebd NEW_WIDTH(0x0ec8, 0), // U+000ec8 NEW_WIDTH(0x0ece, 1), // U+000ece NEW_WIDTH(0x0f18, 0), // U+000f18 NEW_WIDTH(0x0f1a, 1), // U+000f1a NEW_WIDTH(0x0f35, 0), // U+000f35 NEW_WIDTH(0x0f36, 1), // U+000f36 NEW_WIDTH(0x0f37, 0), // U+000f37 NEW_WIDTH(0x0f38, 1), // U+000f38 NEW_WIDTH(0x0f39, 0), // U+000f39 NEW_WIDTH(0x0f3a, 1), // U+000f3a NEW_WIDTH(0x0f71, 0), // U+000f71 NEW_WIDTH(0x0f7f, 1), // U+000f7f NEW_WIDTH(0x0f80, 0), // U+000f80 NEW_WIDTH(0x0f85, 1), // U+000f85 NEW_WIDTH(0x0f86, 0), // U+000f86 NEW_WIDTH(0x0f88, 1), // U+000f88 NEW_WIDTH(0x0f8d, 0), // U+000f8d NEW_WIDTH(0x0f98, 1), // U+000f98 NEW_WIDTH(0x0f99, 0), // U+000f99 NEW_WIDTH(0x0fbd, 1), // U+000fbd NEW_WIDTH(0x0fc6, 0), // U+000fc6 NEW_WIDTH(0x0fc7, 1), // U+000fc7 NEW_WIDTH(0x102d, 0), // U+00102d NEW_WIDTH(0x1031, 1), // U+001031 NEW_WIDTH(0x1032, 0), // U+001032 NEW_WIDTH(0x1038, 1), // U+001038 NEW_WIDTH(0x1039, 0), // U+001039 NEW_WIDTH(0x103b, 1), // U+00103b NEW_WIDTH(0x103d, 0), // U+00103d NEW_WIDTH(0x103f, 1), // U+00103f NEW_WIDTH(0x1058, 0), // U+001058 NEW_WIDTH(0x105a, 1), // U+00105a NEW_WIDTH(0x105e, 0), // U+00105e NEW_WIDTH(0x1061, 1), // U+001061 NEW_WIDTH(0x1071, 0), // U+001071 NEW_WIDTH(0x1075, 1), // U+001075 NEW_WIDTH(0x1082, 0), // U+001082 NEW_WIDTH(0x1083, 1), // U+001083 NEW_WIDTH(0x1085, 0), // U+001085 NEW_WIDTH(0x1087, 1), // U+001087 NEW_WIDTH(0x108d, 0), // U+00108d NEW_WIDTH(0x108e, 1), // U+00108e NEW_WIDTH(0x109d, 0), // U+00109d NEW_WIDTH(0x109e, 1), // U+00109e NEW_WIDTH(0x1100, 2), // U+001100 NEW_WIDTH(0x1160, 0), // U+001160 NEW_WIDTH(0x1200, 1), // U+001200 NEW_WIDTH(0x135d, 0), // U+00135d NEW_WIDTH(0x1360, 1), // U+001360 NEW_WIDTH(0x1712, 0), // U+001712 NEW_WIDTH(0x1715, 1), // U+001715 NEW_WIDTH(0x1732, 0), // U+001732 NEW_WIDTH(0x1734, 1), // U+001734 NEW_WIDTH(0x1752, 0), // U+001752 NEW_WIDTH(0x1754, 1), // U+001754 NEW_WIDTH(0x1772, 0), // U+001772 NEW_WIDTH(0x1774, 1), // U+001774 NEW_WIDTH(0x17b4, 0), // U+0017b4 NEW_WIDTH(0x17b6, 1), // U+0017b6 NEW_WIDTH(0x17b7, 0), // U+0017b7 NEW_WIDTH(0x17be, 1), // U+0017be NEW_WIDTH(0x17c6, 0), // U+0017c6 NEW_WIDTH(0x17c7, 1), // U+0017c7 NEW_WIDTH(0x17c9, 0), // U+0017c9 NEW_WIDTH(0x17d4, 1), // U+0017d4 NEW_WIDTH(0x17dd, 0), // U+0017dd NEW_WIDTH(0x17de, 1), // U+0017de NEW_WIDTH(0x180b, 0), // U+00180b NEW_WIDTH(0x1810, 1), // U+001810 NEW_WIDTH(0x1885, 0), // U+001885 NEW_WIDTH(0x1887, 1), // U+001887 NEW_WIDTH(0x18a9, 0), // U+0018a9 NEW_WIDTH(0x18aa, 1), // U+0018aa NEW_WIDTH(0x1920, 0), // U+001920 NEW_WIDTH(0x1923, 1), // U+001923 NEW_WIDTH(0x1927, 0), // U+001927 NEW_WIDTH(0x1929, 1), // U+001929 NEW_WIDTH(0x1932, 0), // U+001932 NEW_WIDTH(0x1933, 1), // U+001933 NEW_WIDTH(0x1939, 0), // U+001939 NEW_WIDTH(0x193c, 1), // U+00193c NEW_WIDTH(0x1a17, 0), // U+001a17 NEW_WIDTH(0x1a19, 1), // U+001a19 NEW_WIDTH(0x1a1b, 0), // U+001a1b NEW_WIDTH(0x1a1c, 1), // U+001a1c NEW_WIDTH(0x1a56, 0), // U+001a56 NEW_WIDTH(0x1a57, 1), // U+001a57 NEW_WIDTH(0x1a58, 0), // U+001a58 NEW_WIDTH(0x1a5f, 1), // U+001a5f NEW_WIDTH(0x1a60, 0), // U+001a60 NEW_WIDTH(0x1a61, 1), // U+001a61 NEW_WIDTH(0x1a62, 0), // U+001a62 NEW_WIDTH(0x1a63, 1), // U+001a63 NEW_WIDTH(0x1a65, 0), // U+001a65 NEW_WIDTH(0x1a6d, 1), // U+001a6d NEW_WIDTH(0x1a73, 0), // U+001a73 NEW_WIDTH(0x1a7d, 1), // U+001a7d NEW_WIDTH(0x1a7f, 0), // U+001a7f NEW_WIDTH(0x1a80, 1), // U+001a80 NEW_WIDTH(0x1ab0, 0), // U+001ab0 NEW_WIDTH(0x1acf, 1), // U+001acf NEW_WIDTH(0x1b00, 0), // U+001b00 NEW_WIDTH(0x1b04, 1), // U+001b04 NEW_WIDTH(0x1b34, 0), // U+001b34 NEW_WIDTH(0x1b35, 1), // U+001b35 NEW_WIDTH(0x1b36, 0), // U+001b36 NEW_WIDTH(0x1b3b, 1), // U+001b3b NEW_WIDTH(0x1b3c, 0), // U+001b3c NEW_WIDTH(0x1b3d, 1), // U+001b3d NEW_WIDTH(0x1b42, 0), // U+001b42 NEW_WIDTH(0x1b43, 1), // U+001b43 NEW_WIDTH(0x1b6b, 0), // U+001b6b NEW_WIDTH(0x1b74, 1), // U+001b74 NEW_WIDTH(0x1b80, 0), // U+001b80 NEW_WIDTH(0x1b82, 1), // U+001b82 NEW_WIDTH(0x1ba2, 0), // U+001ba2 NEW_WIDTH(0x1ba6, 1), // U+001ba6 NEW_WIDTH(0x1ba8, 0), // U+001ba8 NEW_WIDTH(0x1baa, 1), // U+001baa NEW_WIDTH(0x1bab, 0), // U+001bab NEW_WIDTH(0x1bae, 1), // U+001bae NEW_WIDTH(0x1be6, 0), // U+001be6 NEW_WIDTH(0x1be7, 1), // U+001be7 NEW_WIDTH(0x1be8, 0), // U+001be8 NEW_WIDTH(0x1bea, 1), // U+001bea NEW_WIDTH(0x1bed, 0), // U+001bed NEW_WIDTH(0x1bee, 1), // U+001bee NEW_WIDTH(0x1bef, 0), // U+001bef NEW_WIDTH(0x1bf2, 1), // U+001bf2 NEW_WIDTH(0x1c2c, 0), // U+001c2c NEW_WIDTH(0x1c34, 1), // U+001c34 NEW_WIDTH(0x1c36, 0), // U+001c36 NEW_WIDTH(0x1c38, 1), // U+001c38 NEW_WIDTH(0x1cd0, 0), // U+001cd0 NEW_WIDTH(0x1cd3, 1), // U+001cd3 NEW_WIDTH(0x1cd4, 0), // U+001cd4 NEW_WIDTH(0x1ce1, 1), // U+001ce1 NEW_WIDTH(0x1ce2, 0), // U+001ce2 NEW_WIDTH(0x1ce9, 1), // U+001ce9 NEW_WIDTH(0x1ced, 0), // U+001ced NEW_WIDTH(0x1cee, 1), // U+001cee NEW_WIDTH(0x1cf4, 0), // U+001cf4 NEW_WIDTH(0x1cf5, 1), // U+001cf5 NEW_WIDTH(0x1cf8, 0), // U+001cf8 NEW_WIDTH(0x1cfa, 1), // U+001cfa NEW_WIDTH(0x1dc0, 0), // U+001dc0 NEW_WIDTH(0x1e00, 1), // U+001e00 NEW_WIDTH(0x200b, 0), // U+00200b NEW_WIDTH(0x2010, 1), // U+002010 NEW_WIDTH(0x202a, 0), // U+00202a NEW_WIDTH(0x202f, 1), // U+00202f NEW_WIDTH(0x2060, 0), // U+002060 NEW_WIDTH(0x2065, 1), // U+002065 NEW_WIDTH(0x2066, 0), // U+002066 NEW_WIDTH(0x2070, 1), // U+002070 NEW_WIDTH(0x20d0, 0), // U+0020d0 NEW_WIDTH(0x20f1, 1), // U+0020f1 NEW_WIDTH(0x231a, 2), // U+00231a NEW_WIDTH(0x231c, 1), // U+00231c NEW_WIDTH(0x2329, 2), // U+002329 NEW_WIDTH(0x232b, 1), // U+00232b NEW_WIDTH(0x23e9, 2), // U+0023e9 NEW_WIDTH(0x23ed, 1), // U+0023ed NEW_WIDTH(0x23f0, 2), // U+0023f0 NEW_WIDTH(0x23f1, 1), // U+0023f1 NEW_WIDTH(0x23f3, 2), // U+0023f3 NEW_WIDTH(0x23f4, 1), // U+0023f4 NEW_WIDTH(0x25fd, 2), // U+0025fd NEW_WIDTH(0x25ff, 1), // U+0025ff NEW_WIDTH(0x2614, 2), // U+002614 NEW_WIDTH(0x2616, 1), // U+002616 NEW_WIDTH(0x2648, 2), // U+002648 NEW_WIDTH(0x2654, 1), // U+002654 NEW_WIDTH(0x267f, 2), // U+00267f NEW_WIDTH(0x2680, 1), // U+002680 NEW_WIDTH(0x2693, 2), // U+002693 NEW_WIDTH(0x2694, 1), // U+002694 NEW_WIDTH(0x26a1, 2), // U+0026a1 NEW_WIDTH(0x26a2, 1), // U+0026a2 NEW_WIDTH(0x26aa, 2), // U+0026aa NEW_WIDTH(0x26ac, 1), // U+0026ac NEW_WIDTH(0x26bd, 2), // U+0026bd NEW_WIDTH(0x26bf, 1), // U+0026bf NEW_WIDTH(0x26c4, 2), // U+0026c4 NEW_WIDTH(0x26c6, 1), // U+0026c6 NEW_WIDTH(0x26ce, 2), // U+0026ce NEW_WIDTH(0x26cf, 1), // U+0026cf NEW_WIDTH(0x26d4, 2), // U+0026d4 NEW_WIDTH(0x26d5, 1), // U+0026d5 NEW_WIDTH(0x26ea, 2), // U+0026ea NEW_WIDTH(0x26eb, 1), // U+0026eb NEW_WIDTH(0x26f2, 2), // U+0026f2 NEW_WIDTH(0x26f4, 1), // U+0026f4 NEW_WIDTH(0x26f5, 2), // U+0026f5 NEW_WIDTH(0x26f6, 1), // U+0026f6 NEW_WIDTH(0x26fa, 2), // U+0026fa NEW_WIDTH(0x26fb, 1), // U+0026fb NEW_WIDTH(0x26fd, 2), // U+0026fd NEW_WIDTH(0x26fe, 1), // U+0026fe NEW_WIDTH(0x2705, 2), // U+002705 NEW_WIDTH(0x2706, 1), // U+002706 NEW_WIDTH(0x270a, 2), // U+00270a NEW_WIDTH(0x270c, 1), // U+00270c NEW_WIDTH(0x2728, 2), // U+002728 NEW_WIDTH(0x2729, 1), // U+002729 NEW_WIDTH(0x274c, 2), // U+00274c NEW_WIDTH(0x274d, 1), // U+00274d NEW_WIDTH(0x274e, 2), // U+00274e NEW_WIDTH(0x274f, 1), // U+00274f NEW_WIDTH(0x2753, 2), // U+002753 NEW_WIDTH(0x2756, 1), // U+002756 NEW_WIDTH(0x2757, 2), // U+002757 NEW_WIDTH(0x2758, 1), // U+002758 NEW_WIDTH(0x2795, 2), // U+002795 NEW_WIDTH(0x2798, 1), // U+002798 NEW_WIDTH(0x27b0, 2), // U+0027b0 NEW_WIDTH(0x27b1, 1), // U+0027b1 NEW_WIDTH(0x27bf, 2), // U+0027bf NEW_WIDTH(0x27c0, 1), // U+0027c0 NEW_WIDTH(0x2b1b, 2), // U+002b1b NEW_WIDTH(0x2b1d, 1), // U+002b1d NEW_WIDTH(0x2b50, 2), // U+002b50 NEW_WIDTH(0x2b51, 1), // U+002b51 NEW_WIDTH(0x2b55, 2), // U+002b55 NEW_WIDTH(0x2b56, 1), // U+002b56 NEW_WIDTH(0x2cef, 0), // U+002cef NEW_WIDTH(0x2cf2, 1), // U+002cf2 NEW_WIDTH(0x2d7f, 0), // U+002d7f NEW_WIDTH(0x2d80, 1), // U+002d80 NEW_WIDTH(0x2de0, 0), // U+002de0 NEW_WIDTH(0x2e00, 1), // U+002e00 NEW_WIDTH(0x2e80, 2), // U+002e80 NEW_WIDTH(0x2e9a, 1), // U+002e9a NEW_WIDTH(0x2e9b, 2), // U+002e9b NEW_WIDTH(0x2ef4, 1), // U+002ef4 NEW_WIDTH(0x2f00, 2), // U+002f00 NEW_WIDTH(0x2fd6, 1), // U+002fd6 NEW_WIDTH(0x2ff0, 2), // U+002ff0 NEW_WIDTH(0x2ffc, 1), // U+002ffc NEW_WIDTH(0x3000, 2), // U+003000 NEW_WIDTH(0x302a, 0), // U+00302a NEW_WIDTH(0x302e, 2), // U+00302e NEW_WIDTH(0x303f, 1), // U+00303f NEW_WIDTH(0x3041, 2), // U+003041 NEW_WIDTH(0x3097, 1), // U+003097 NEW_WIDTH(0x3099, 0), // U+003099 NEW_WIDTH(0x309b, 2), // U+00309b NEW_WIDTH(0x3100, 1), // U+003100 NEW_WIDTH(0x3105, 2), // U+003105 NEW_WIDTH(0x3130, 1), // U+003130 NEW_WIDTH(0x3131, 2), // U+003131 NEW_WIDTH(0x318f, 1), // U+00318f NEW_WIDTH(0x3190, 2), // U+003190 NEW_WIDTH(0x31e4, 1), // U+0031e4 NEW_WIDTH(0x31f0, 2), // U+0031f0 NEW_WIDTH(0x321f, 1), // U+00321f NEW_WIDTH(0x3220, 2), // U+003220 NEW_WIDTH(0x3248, 1), // U+003248 NEW_WIDTH(0x3250, 2), // U+003250 // U+004000, NEW_WIDTH(0x0000, 2), // U+004000 NEW_WIDTH(0x0dc0, 1), // U+004dc0 NEW_WIDTH(0x0e00, 2), // U+004e00 // U+008000, NEW_WIDTH(0x0000, 2), // U+008000 NEW_WIDTH(0x248d, 1), // U+00a48d NEW_WIDTH(0x2490, 2), // U+00a490 NEW_WIDTH(0x24c7, 1), // U+00a4c7 NEW_WIDTH(0x266f, 0), // U+00a66f NEW_WIDTH(0x2673, 1), // U+00a673 NEW_WIDTH(0x2674, 0), // U+00a674 NEW_WIDTH(0x267e, 1), // U+00a67e NEW_WIDTH(0x269e, 0), // U+00a69e NEW_WIDTH(0x26a0, 1), // U+00a6a0 NEW_WIDTH(0x26f0, 0), // U+00a6f0 NEW_WIDTH(0x26f2, 1), // U+00a6f2 NEW_WIDTH(0x2802, 0), // U+00a802 NEW_WIDTH(0x2803, 1), // U+00a803 NEW_WIDTH(0x2806, 0), // U+00a806 NEW_WIDTH(0x2807, 1), // U+00a807 NEW_WIDTH(0x280b, 0), // U+00a80b NEW_WIDTH(0x280c, 1), // U+00a80c NEW_WIDTH(0x2825, 0), // U+00a825 NEW_WIDTH(0x2827, 1), // U+00a827 NEW_WIDTH(0x282c, 0), // U+00a82c NEW_WIDTH(0x282d, 1), // U+00a82d NEW_WIDTH(0x28c4, 0), // U+00a8c4 NEW_WIDTH(0x28c6, 1), // U+00a8c6 NEW_WIDTH(0x28e0, 0), // U+00a8e0 NEW_WIDTH(0x28f2, 1), // U+00a8f2 NEW_WIDTH(0x28ff, 0), // U+00a8ff NEW_WIDTH(0x2900, 1), // U+00a900 NEW_WIDTH(0x2926, 0), // U+00a926 NEW_WIDTH(0x292e, 1), // U+00a92e NEW_WIDTH(0x2947, 0), // U+00a947 NEW_WIDTH(0x2952, 1), // U+00a952 NEW_WIDTH(0x2960, 2), // U+00a960 NEW_WIDTH(0x297d, 1), // U+00a97d NEW_WIDTH(0x2980, 0), // U+00a980 NEW_WIDTH(0x2983, 1), // U+00a983 NEW_WIDTH(0x29b3, 0), // U+00a9b3 NEW_WIDTH(0x29b4, 1), // U+00a9b4 NEW_WIDTH(0x29b6, 0), // U+00a9b6 NEW_WIDTH(0x29ba, 1), // U+00a9ba NEW_WIDTH(0x29bc, 0), // U+00a9bc NEW_WIDTH(0x29be, 1), // U+00a9be NEW_WIDTH(0x29e5, 0), // U+00a9e5 NEW_WIDTH(0x29e6, 1), // U+00a9e6 NEW_WIDTH(0x2a29, 0), // U+00aa29 NEW_WIDTH(0x2a2f, 1), // U+00aa2f NEW_WIDTH(0x2a31, 0), // U+00aa31 NEW_WIDTH(0x2a33, 1), // U+00aa33 NEW_WIDTH(0x2a35, 0), // U+00aa35 NEW_WIDTH(0x2a37, 1), // U+00aa37 NEW_WIDTH(0x2a43, 0), // U+00aa43 NEW_WIDTH(0x2a44, 1), // U+00aa44 NEW_WIDTH(0x2a4c, 0), // U+00aa4c NEW_WIDTH(0x2a4d, 1), // U+00aa4d NEW_WIDTH(0x2a7c, 0), // U+00aa7c NEW_WIDTH(0x2a7d, 1), // U+00aa7d NEW_WIDTH(0x2ab0, 0), // U+00aab0 NEW_WIDTH(0x2ab1, 1), // U+00aab1 NEW_WIDTH(0x2ab2, 0), // U+00aab2 NEW_WIDTH(0x2ab5, 1), // U+00aab5 NEW_WIDTH(0x2ab7, 0), // U+00aab7 NEW_WIDTH(0x2ab9, 1), // U+00aab9 NEW_WIDTH(0x2abe, 0), // U+00aabe NEW_WIDTH(0x2ac0, 1), // U+00aac0 NEW_WIDTH(0x2ac1, 0), // U+00aac1 NEW_WIDTH(0x2ac2, 1), // U+00aac2 NEW_WIDTH(0x2aec, 0), // U+00aaec NEW_WIDTH(0x2aee, 1), // U+00aaee NEW_WIDTH(0x2af6, 0), // U+00aaf6 NEW_WIDTH(0x2af7, 1), // U+00aaf7 NEW_WIDTH(0x2be5, 0), // U+00abe5 NEW_WIDTH(0x2be6, 1), // U+00abe6 NEW_WIDTH(0x2be8, 0), // U+00abe8 NEW_WIDTH(0x2be9, 1), // U+00abe9 NEW_WIDTH(0x2bed, 0), // U+00abed NEW_WIDTH(0x2bee, 1), // U+00abee NEW_WIDTH(0x2c00, 2), // U+00ac00 // U+00c000, NEW_WIDTH(0x0000, 2), // U+00c000 NEW_WIDTH(0x17a4, 1), // U+00d7a4 NEW_WIDTH(0x3900, 2), // U+00f900 NEW_WIDTH(0x3b00, 1), // U+00fb00 NEW_WIDTH(0x3b1e, 0), // U+00fb1e NEW_WIDTH(0x3b1f, 1), // U+00fb1f NEW_WIDTH(0x3e00, 0), // U+00fe00 NEW_WIDTH(0x3e10, 2), // U+00fe10 NEW_WIDTH(0x3e1a, 1), // U+00fe1a NEW_WIDTH(0x3e20, 0), // U+00fe20 NEW_WIDTH(0x3e30, 2), // U+00fe30 NEW_WIDTH(0x3e53, 1), // U+00fe53 NEW_WIDTH(0x3e54, 2), // U+00fe54 NEW_WIDTH(0x3e67, 1), // U+00fe67 NEW_WIDTH(0x3e68, 2), // U+00fe68 NEW_WIDTH(0x3e6c, 1), // U+00fe6c NEW_WIDTH(0x3eff, 0), // U+00feff NEW_WIDTH(0x3f00, 1), // U+00ff00 NEW_WIDTH(0x3f01, 2), // U+00ff01 NEW_WIDTH(0x3f61, 1), // U+00ff61 NEW_WIDTH(0x3fe0, 2), // U+00ffe0 NEW_WIDTH(0x3fe7, 1), // U+00ffe7 NEW_WIDTH(0x3ff9, 0), // U+00fff9 NEW_WIDTH(0x3ffc, 1), // U+00fffc // U+010000, NEW_WIDTH(0x0000, 1), // U+010000 NEW_WIDTH(0x01fd, 0), // U+0101fd NEW_WIDTH(0x01fe, 1), // U+0101fe NEW_WIDTH(0x02e0, 0), // U+0102e0 NEW_WIDTH(0x02e1, 1), // U+0102e1 NEW_WIDTH(0x0376, 0), // U+010376 NEW_WIDTH(0x037b, 1), // U+01037b NEW_WIDTH(0x0a01, 0), // U+010a01 NEW_WIDTH(0x0a04, 1), // U+010a04 NEW_WIDTH(0x0a05, 0), // U+010a05 NEW_WIDTH(0x0a07, 1), // U+010a07 NEW_WIDTH(0x0a0c, 0), // U+010a0c NEW_WIDTH(0x0a10, 1), // U+010a10 NEW_WIDTH(0x0a38, 0), // U+010a38 NEW_WIDTH(0x0a3b, 1), // U+010a3b NEW_WIDTH(0x0a3f, 0), // U+010a3f NEW_WIDTH(0x0a40, 1), // U+010a40 NEW_WIDTH(0x0ae5, 0), // U+010ae5 NEW_WIDTH(0x0ae7, 1), // U+010ae7 NEW_WIDTH(0x0d24, 0), // U+010d24 NEW_WIDTH(0x0d28, 1), // U+010d28 NEW_WIDTH(0x0eab, 0), // U+010eab NEW_WIDTH(0x0ead, 1), // U+010ead NEW_WIDTH(0x0f46, 0), // U+010f46 NEW_WIDTH(0x0f51, 1), // U+010f51 NEW_WIDTH(0x0f82, 0), // U+010f82 NEW_WIDTH(0x0f86, 1), // U+010f86 NEW_WIDTH(0x1001, 0), // U+011001 NEW_WIDTH(0x1002, 1), // U+011002 NEW_WIDTH(0x1038, 0), // U+011038 NEW_WIDTH(0x1047, 1), // U+011047 NEW_WIDTH(0x1070, 0), // U+011070 NEW_WIDTH(0x1071, 1), // U+011071 NEW_WIDTH(0x1073, 0), // U+011073 NEW_WIDTH(0x1075, 1), // U+011075 NEW_WIDTH(0x107f, 0), // U+01107f NEW_WIDTH(0x1082, 1), // U+011082 NEW_WIDTH(0x10b3, 0), // U+0110b3 NEW_WIDTH(0x10b7, 1), // U+0110b7 NEW_WIDTH(0x10b9, 0), // U+0110b9 NEW_WIDTH(0x10bb, 1), // U+0110bb NEW_WIDTH(0x10bd, 0), // U+0110bd NEW_WIDTH(0x10be, 1), // U+0110be NEW_WIDTH(0x10c2, 0), // U+0110c2 NEW_WIDTH(0x10c3, 1), // U+0110c3 NEW_WIDTH(0x10cd, 0), // U+0110cd NEW_WIDTH(0x10ce, 1), // U+0110ce NEW_WIDTH(0x1100, 0), // U+011100 NEW_WIDTH(0x1103, 1), // U+011103 NEW_WIDTH(0x1127, 0), // U+011127 NEW_WIDTH(0x112c, 1), // U+01112c NEW_WIDTH(0x112d, 0), // U+01112d NEW_WIDTH(0x1135, 1), // U+011135 NEW_WIDTH(0x1173, 0), // U+011173 NEW_WIDTH(0x1174, 1), // U+011174 NEW_WIDTH(0x1180, 0), // U+011180 NEW_WIDTH(0x1182, 1), // U+011182 NEW_WIDTH(0x11b6, 0), // U+0111b6 NEW_WIDTH(0x11bf, 1), // U+0111bf NEW_WIDTH(0x11c9, 0), // U+0111c9 NEW_WIDTH(0x11cd, 1), // U+0111cd NEW_WIDTH(0x11cf, 0), // U+0111cf NEW_WIDTH(0x11d0, 1), // U+0111d0 NEW_WIDTH(0x122f, 0), // U+01122f NEW_WIDTH(0x1232, 1), // U+011232 NEW_WIDTH(0x1234, 0), // U+011234 NEW_WIDTH(0x1235, 1), // U+011235 NEW_WIDTH(0x1236, 0), // U+011236 NEW_WIDTH(0x1238, 1), // U+011238 NEW_WIDTH(0x123e, 0), // U+01123e NEW_WIDTH(0x123f, 1), // U+01123f NEW_WIDTH(0x12df, 0), // U+0112df NEW_WIDTH(0x12e0, 1), // U+0112e0 NEW_WIDTH(0x12e3, 0), // U+0112e3 NEW_WIDTH(0x12eb, 1), // U+0112eb NEW_WIDTH(0x1300, 0), // U+011300 NEW_WIDTH(0x1302, 1), // U+011302 NEW_WIDTH(0x133b, 0), // U+01133b NEW_WIDTH(0x133d, 1), // U+01133d NEW_WIDTH(0x1340, 0), // U+011340 NEW_WIDTH(0x1341, 1), // U+011341 NEW_WIDTH(0x1366, 0), // U+011366 NEW_WIDTH(0x136d, 1), // U+01136d NEW_WIDTH(0x1370, 0), // U+011370 NEW_WIDTH(0x1375, 1), // U+011375 NEW_WIDTH(0x1438, 0), // U+011438 NEW_WIDTH(0x1440, 1), // U+011440 NEW_WIDTH(0x1442, 0), // U+011442 NEW_WIDTH(0x1445, 1), // U+011445 NEW_WIDTH(0x1446, 0), // U+011446 NEW_WIDTH(0x1447, 1), // U+011447 NEW_WIDTH(0x145e, 0), // U+01145e NEW_WIDTH(0x145f, 1), // U+01145f NEW_WIDTH(0x14b3, 0), // U+0114b3 NEW_WIDTH(0x14b9, 1), // U+0114b9 NEW_WIDTH(0x14ba, 0), // U+0114ba NEW_WIDTH(0x14bb, 1), // U+0114bb NEW_WIDTH(0x14bf, 0), // U+0114bf NEW_WIDTH(0x14c1, 1), // U+0114c1 NEW_WIDTH(0x14c2, 0), // U+0114c2 NEW_WIDTH(0x14c4, 1), // U+0114c4 NEW_WIDTH(0x15b2, 0), // U+0115b2 NEW_WIDTH(0x15b6, 1), // U+0115b6 NEW_WIDTH(0x15bc, 0), // U+0115bc NEW_WIDTH(0x15be, 1), // U+0115be NEW_WIDTH(0x15bf, 0), // U+0115bf NEW_WIDTH(0x15c1, 1), // U+0115c1 NEW_WIDTH(0x15dc, 0), // U+0115dc NEW_WIDTH(0x15de, 1), // U+0115de NEW_WIDTH(0x1633, 0), // U+011633 NEW_WIDTH(0x163b, 1), // U+01163b NEW_WIDTH(0x163d, 0), // U+01163d NEW_WIDTH(0x163e, 1), // U+01163e NEW_WIDTH(0x163f, 0), // U+01163f NEW_WIDTH(0x1641, 1), // U+011641 NEW_WIDTH(0x16ab, 0), // U+0116ab NEW_WIDTH(0x16ac, 1), // U+0116ac NEW_WIDTH(0x16ad, 0), // U+0116ad NEW_WIDTH(0x16ae, 1), // U+0116ae NEW_WIDTH(0x16b0, 0), // U+0116b0 NEW_WIDTH(0x16b6, 1), // U+0116b6 NEW_WIDTH(0x16b7, 0), // U+0116b7 NEW_WIDTH(0x16b8, 1), // U+0116b8 NEW_WIDTH(0x171d, 0), // U+01171d NEW_WIDTH(0x1720, 1), // U+011720 NEW_WIDTH(0x1722, 0), // U+011722 NEW_WIDTH(0x1726, 1), // U+011726 NEW_WIDTH(0x1727, 0), // U+011727 NEW_WIDTH(0x172c, 1), // U+01172c NEW_WIDTH(0x182f, 0), // U+01182f NEW_WIDTH(0x1838, 1), // U+011838 NEW_WIDTH(0x1839, 0), // U+011839 NEW_WIDTH(0x183b, 1), // U+01183b NEW_WIDTH(0x193b, 0), // U+01193b NEW_WIDTH(0x193d, 1), // U+01193d NEW_WIDTH(0x193e, 0), // U+01193e NEW_WIDTH(0x193f, 1), // U+01193f NEW_WIDTH(0x1943, 0), // U+011943 NEW_WIDTH(0x1944, 1), // U+011944 NEW_WIDTH(0x19d4, 0), // U+0119d4 NEW_WIDTH(0x19d8, 1), // U+0119d8 NEW_WIDTH(0x19da, 0), // U+0119da NEW_WIDTH(0x19dc, 1), // U+0119dc NEW_WIDTH(0x19e0, 0), // U+0119e0 NEW_WIDTH(0x19e1, 1), // U+0119e1 NEW_WIDTH(0x1a01, 0), // U+011a01 NEW_WIDTH(0x1a0b, 1), // U+011a0b NEW_WIDTH(0x1a33, 0), // U+011a33 NEW_WIDTH(0x1a39, 1), // U+011a39 NEW_WIDTH(0x1a3b, 0), // U+011a3b NEW_WIDTH(0x1a3f, 1), // U+011a3f NEW_WIDTH(0x1a47, 0), // U+011a47 NEW_WIDTH(0x1a48, 1), // U+011a48 NEW_WIDTH(0x1a51, 0), // U+011a51 NEW_WIDTH(0x1a57, 1), // U+011a57 NEW_WIDTH(0x1a59, 0), // U+011a59 NEW_WIDTH(0x1a5c, 1), // U+011a5c NEW_WIDTH(0x1a8a, 0), // U+011a8a NEW_WIDTH(0x1a97, 1), // U+011a97 NEW_WIDTH(0x1a98, 0), // U+011a98 NEW_WIDTH(0x1a9a, 1), // U+011a9a NEW_WIDTH(0x1c30, 0), // U+011c30 NEW_WIDTH(0x1c37, 1), // U+011c37 NEW_WIDTH(0x1c38, 0), // U+011c38 NEW_WIDTH(0x1c3e, 1), // U+011c3e NEW_WIDTH(0x1c3f, 0), // U+011c3f NEW_WIDTH(0x1c40, 1), // U+011c40 NEW_WIDTH(0x1c92, 0), // U+011c92 NEW_WIDTH(0x1ca8, 1), // U+011ca8 NEW_WIDTH(0x1caa, 0), // U+011caa NEW_WIDTH(0x1cb1, 1), // U+011cb1 NEW_WIDTH(0x1cb2, 0), // U+011cb2 NEW_WIDTH(0x1cb4, 1), // U+011cb4 NEW_WIDTH(0x1cb5, 0), // U+011cb5 NEW_WIDTH(0x1cb7, 1), // U+011cb7 NEW_WIDTH(0x1d31, 0), // U+011d31 NEW_WIDTH(0x1d37, 1), // U+011d37 NEW_WIDTH(0x1d3a, 0), // U+011d3a NEW_WIDTH(0x1d3b, 1), // U+011d3b NEW_WIDTH(0x1d3c, 0), // U+011d3c NEW_WIDTH(0x1d3e, 1), // U+011d3e NEW_WIDTH(0x1d3f, 0), // U+011d3f NEW_WIDTH(0x1d46, 1), // U+011d46 NEW_WIDTH(0x1d47, 0), // U+011d47 NEW_WIDTH(0x1d48, 1), // U+011d48 NEW_WIDTH(0x1d90, 0), // U+011d90 NEW_WIDTH(0x1d92, 1), // U+011d92 NEW_WIDTH(0x1d95, 0), // U+011d95 NEW_WIDTH(0x1d96, 1), // U+011d96 NEW_WIDTH(0x1d97, 0), // U+011d97 NEW_WIDTH(0x1d98, 1), // U+011d98 NEW_WIDTH(0x1ef3, 0), // U+011ef3 NEW_WIDTH(0x1ef5, 1), // U+011ef5 NEW_WIDTH(0x3430, 0), // U+013430 NEW_WIDTH(0x3439, 1), // U+013439 // U+014000, NEW_WIDTH(0x0000, 1), // U+014000 NEW_WIDTH(0x2af0, 0), // U+016af0 NEW_WIDTH(0x2af5, 1), // U+016af5 NEW_WIDTH(0x2b30, 0), // U+016b30 NEW_WIDTH(0x2b37, 1), // U+016b37 NEW_WIDTH(0x2f4f, 0), // U+016f4f NEW_WIDTH(0x2f50, 1), // U+016f50 NEW_WIDTH(0x2f8f, 0), // U+016f8f NEW_WIDTH(0x2f93, 1), // U+016f93 NEW_WIDTH(0x2fe0, 2), // U+016fe0 NEW_WIDTH(0x2fe4, 0), // U+016fe4 NEW_WIDTH(0x2fe5, 1), // U+016fe5 NEW_WIDTH(0x2ff0, 2), // U+016ff0 NEW_WIDTH(0x2ff2, 1), // U+016ff2 NEW_WIDTH(0x3000, 2), // U+017000 // U+018000, NEW_WIDTH(0x0000, 2), // U+018000 NEW_WIDTH(0x07f8, 1), // U+0187f8 NEW_WIDTH(0x0800, 2), // U+018800 NEW_WIDTH(0x0cd6, 1), // U+018cd6 NEW_WIDTH(0x0d00, 2), // U+018d00 NEW_WIDTH(0x0d09, 1), // U+018d09 NEW_WIDTH(0x2ff0, 2), // U+01aff0 NEW_WIDTH(0x2ff4, 1), // U+01aff4 NEW_WIDTH(0x2ff5, 2), // U+01aff5 NEW_WIDTH(0x2ffc, 1), // U+01affc NEW_WIDTH(0x2ffd, 2), // U+01affd NEW_WIDTH(0x2fff, 1), // U+01afff NEW_WIDTH(0x3000, 2), // U+01b000 NEW_WIDTH(0x3123, 1), // U+01b123 NEW_WIDTH(0x3150, 2), // U+01b150 NEW_WIDTH(0x3153, 1), // U+01b153 NEW_WIDTH(0x3164, 2), // U+01b164 NEW_WIDTH(0x3168, 1), // U+01b168 NEW_WIDTH(0x3170, 2), // U+01b170 NEW_WIDTH(0x32fc, 1), // U+01b2fc NEW_WIDTH(0x3c9d, 0), // U+01bc9d NEW_WIDTH(0x3c9f, 1), // U+01bc9f NEW_WIDTH(0x3ca0, 0), // U+01bca0 NEW_WIDTH(0x3ca4, 1), // U+01bca4 // U+01c000, NEW_WIDTH(0x0000, 1), // U+01c000 NEW_WIDTH(0x0f00, 0), // U+01cf00 NEW_WIDTH(0x0f2e, 1), // U+01cf2e NEW_WIDTH(0x0f30, 0), // U+01cf30 NEW_WIDTH(0x0f47, 1), // U+01cf47 NEW_WIDTH(0x1167, 0), // U+01d167 NEW_WIDTH(0x116a, 1), // U+01d16a NEW_WIDTH(0x1173, 0), // U+01d173 NEW_WIDTH(0x1183, 1), // U+01d183 NEW_WIDTH(0x1185, 0), // U+01d185 NEW_WIDTH(0x118c, 1), // U+01d18c NEW_WIDTH(0x11aa, 0), // U+01d1aa NEW_WIDTH(0x11ae, 1), // U+01d1ae NEW_WIDTH(0x1242, 0), // U+01d242 NEW_WIDTH(0x1245, 1), // U+01d245 NEW_WIDTH(0x1a00, 0), // U+01da00 NEW_WIDTH(0x1a37, 1), // U+01da37 NEW_WIDTH(0x1a3b, 0), // U+01da3b NEW_WIDTH(0x1a6d, 1), // U+01da6d NEW_WIDTH(0x1a75, 0), // U+01da75 NEW_WIDTH(0x1a76, 1), // U+01da76 NEW_WIDTH(0x1a84, 0), // U+01da84 NEW_WIDTH(0x1a85, 1), // U+01da85 NEW_WIDTH(0x1a9b, 0), // U+01da9b NEW_WIDTH(0x1aa0, 1), // U+01daa0 NEW_WIDTH(0x1aa1, 0), // U+01daa1 NEW_WIDTH(0x1ab0, 1), // U+01dab0 NEW_WIDTH(0x2000, 0), // U+01e000 NEW_WIDTH(0x2007, 1), // U+01e007 NEW_WIDTH(0x2008, 0), // U+01e008 NEW_WIDTH(0x2019, 1), // U+01e019 NEW_WIDTH(0x201b, 0), // U+01e01b NEW_WIDTH(0x2022, 1), // U+01e022 NEW_WIDTH(0x2023, 0), // U+01e023 NEW_WIDTH(0x2025, 1), // U+01e025 NEW_WIDTH(0x2026, 0), // U+01e026 NEW_WIDTH(0x202b, 1), // U+01e02b NEW_WIDTH(0x2130, 0), // U+01e130 NEW_WIDTH(0x2137, 1), // U+01e137 NEW_WIDTH(0x22ae, 0), // U+01e2ae NEW_WIDTH(0x22af, 1), // U+01e2af NEW_WIDTH(0x22ec, 0), // U+01e2ec NEW_WIDTH(0x22f0, 1), // U+01e2f0 NEW_WIDTH(0x28d0, 0), // U+01e8d0 NEW_WIDTH(0x28d7, 1), // U+01e8d7 NEW_WIDTH(0x2944, 0), // U+01e944 NEW_WIDTH(0x294b, 1), // U+01e94b NEW_WIDTH(0x3004, 2), // U+01f004 NEW_WIDTH(0x3005, 1), // U+01f005 NEW_WIDTH(0x30cf, 2), // U+01f0cf NEW_WIDTH(0x30d0, 1), // U+01f0d0 NEW_WIDTH(0x318e, 2), // U+01f18e NEW_WIDTH(0x318f, 1), // U+01f18f NEW_WIDTH(0x3191, 2), // U+01f191 NEW_WIDTH(0x319b, 1), // U+01f19b NEW_WIDTH(0x3200, 2), // U+01f200 NEW_WIDTH(0x3203, 1), // U+01f203 NEW_WIDTH(0x3210, 2), // U+01f210 NEW_WIDTH(0x323c, 1), // U+01f23c NEW_WIDTH(0x3240, 2), // U+01f240 NEW_WIDTH(0x3249, 1), // U+01f249 NEW_WIDTH(0x3250, 2), // U+01f250 NEW_WIDTH(0x3252, 1), // U+01f252 NEW_WIDTH(0x3260, 2), // U+01f260 NEW_WIDTH(0x3266, 1), // U+01f266 NEW_WIDTH(0x3300, 2), // U+01f300 NEW_WIDTH(0x3321, 1), // U+01f321 NEW_WIDTH(0x332d, 2), // U+01f32d NEW_WIDTH(0x3336, 1), // U+01f336 NEW_WIDTH(0x3337, 2), // U+01f337 NEW_WIDTH(0x337d, 1), // U+01f37d NEW_WIDTH(0x337e, 2), // U+01f37e NEW_WIDTH(0x3394, 1), // U+01f394 NEW_WIDTH(0x33a0, 2), // U+01f3a0 NEW_WIDTH(0x33cb, 1), // U+01f3cb NEW_WIDTH(0x33cf, 2), // U+01f3cf NEW_WIDTH(0x33d4, 1), // U+01f3d4 NEW_WIDTH(0x33e0, 2), // U+01f3e0 NEW_WIDTH(0x33f1, 1), // U+01f3f1 NEW_WIDTH(0x33f4, 2), // U+01f3f4 NEW_WIDTH(0x33f5, 1), // U+01f3f5 NEW_WIDTH(0x33f8, 2), // U+01f3f8 NEW_WIDTH(0x343f, 1), // U+01f43f NEW_WIDTH(0x3440, 2), // U+01f440 NEW_WIDTH(0x3441, 1), // U+01f441 NEW_WIDTH(0x3442, 2), // U+01f442 NEW_WIDTH(0x34fd, 1), // U+01f4fd NEW_WIDTH(0x34ff, 2), // U+01f4ff NEW_WIDTH(0x353e, 1), // U+01f53e NEW_WIDTH(0x354b, 2), // U+01f54b NEW_WIDTH(0x354f, 1), // U+01f54f NEW_WIDTH(0x3550, 2), // U+01f550 NEW_WIDTH(0x3568, 1), // U+01f568 NEW_WIDTH(0x357a, 2), // U+01f57a NEW_WIDTH(0x357b, 1), // U+01f57b NEW_WIDTH(0x3595, 2), // U+01f595 NEW_WIDTH(0x3597, 1), // U+01f597 NEW_WIDTH(0x35a4, 2), // U+01f5a4 NEW_WIDTH(0x35a5, 1), // U+01f5a5 NEW_WIDTH(0x35fb, 2), // U+01f5fb NEW_WIDTH(0x3650, 1), // U+01f650 NEW_WIDTH(0x3680, 2), // U+01f680 NEW_WIDTH(0x36c6, 1), // U+01f6c6 NEW_WIDTH(0x36cc, 2), // U+01f6cc NEW_WIDTH(0x36cd, 1), // U+01f6cd NEW_WIDTH(0x36d0, 2), // U+01f6d0 NEW_WIDTH(0x36d3, 1), // U+01f6d3 NEW_WIDTH(0x36d5, 2), // U+01f6d5 NEW_WIDTH(0x36d8, 1), // U+01f6d8 NEW_WIDTH(0x36dd, 2), // U+01f6dd NEW_WIDTH(0x36e0, 1), // U+01f6e0 NEW_WIDTH(0x36eb, 2), // U+01f6eb NEW_WIDTH(0x36ed, 1), // U+01f6ed NEW_WIDTH(0x36f4, 2), // U+01f6f4 NEW_WIDTH(0x36fd, 1), // U+01f6fd NEW_WIDTH(0x37e0, 2), // U+01f7e0 NEW_WIDTH(0x37ec, 1), // U+01f7ec NEW_WIDTH(0x37f0, 2), // U+01f7f0 NEW_WIDTH(0x37f1, 1), // U+01f7f1 NEW_WIDTH(0x390c, 2), // U+01f90c NEW_WIDTH(0x393b, 1), // U+01f93b NEW_WIDTH(0x393c, 2), // U+01f93c NEW_WIDTH(0x3946, 1), // U+01f946 NEW_WIDTH(0x3947, 2), // U+01f947 NEW_WIDTH(0x3a00, 1), // U+01fa00 NEW_WIDTH(0x3a70, 2), // U+01fa70 NEW_WIDTH(0x3a75, 1), // U+01fa75 NEW_WIDTH(0x3a78, 2), // U+01fa78 NEW_WIDTH(0x3a7d, 1), // U+01fa7d NEW_WIDTH(0x3a80, 2), // U+01fa80 NEW_WIDTH(0x3a87, 1), // U+01fa87 NEW_WIDTH(0x3a90, 2), // U+01fa90 NEW_WIDTH(0x3aad, 1), // U+01faad NEW_WIDTH(0x3ab0, 2), // U+01fab0 NEW_WIDTH(0x3abb, 1), // U+01fabb NEW_WIDTH(0x3ac0, 2), // U+01fac0 NEW_WIDTH(0x3ac6, 1), // U+01fac6 NEW_WIDTH(0x3ad0, 2), // U+01fad0 NEW_WIDTH(0x3ada, 1), // U+01fada NEW_WIDTH(0x3ae0, 2), // U+01fae0 NEW_WIDTH(0x3ae8, 1), // U+01fae8 NEW_WIDTH(0x3af0, 2), // U+01faf0 NEW_WIDTH(0x3af7, 1), // U+01faf7 // U+020000, NEW_WIDTH(0x0000, 2), // U+020000 // U+024000, NEW_WIDTH(0x0000, 2), // U+024000 // U+028000, NEW_WIDTH(0x0000, 2), // U+028000 // U+02c000, NEW_WIDTH(0x0000, 2), // U+02c000 NEW_WIDTH(0x3ffe, 1), // U+02fffe // U+030000, NEW_WIDTH(0x0000, 2), // U+030000 // U+034000, NEW_WIDTH(0x0000, 2), // U+034000 // U+038000, NEW_WIDTH(0x0000, 2), // U+038000 // U+03c000, NEW_WIDTH(0x0000, 2), // U+03c000 NEW_WIDTH(0x3ffe, 1), // U+03fffe // U+040000, NEW_WIDTH(0x0000, 1), // U+040000 // U+044000, NEW_WIDTH(0x0000, 1), // U+044000 // U+048000, NEW_WIDTH(0x0000, 1), // U+048000 // U+04c000, NEW_WIDTH(0x0000, 1), // U+04c000 // U+050000, NEW_WIDTH(0x0000, 1), // U+050000 // U+054000, NEW_WIDTH(0x0000, 1), // U+054000 // U+058000, NEW_WIDTH(0x0000, 1), // U+058000 // U+05c000, NEW_WIDTH(0x0000, 1), // U+05c000 // U+060000, NEW_WIDTH(0x0000, 1), // U+060000 // U+064000, NEW_WIDTH(0x0000, 1), // U+064000 // U+068000, NEW_WIDTH(0x0000, 1), // U+068000 // U+06c000, NEW_WIDTH(0x0000, 1), // U+06c000 // U+070000, NEW_WIDTH(0x0000, 1), // U+070000 // U+074000, NEW_WIDTH(0x0000, 1), // U+074000 // U+078000, NEW_WIDTH(0x0000, 1), // U+078000 // U+07c000, NEW_WIDTH(0x0000, 1), // U+07c000 // U+080000, NEW_WIDTH(0x0000, 1), // U+080000 // U+084000, NEW_WIDTH(0x0000, 1), // U+084000 // U+088000, NEW_WIDTH(0x0000, 1), // U+088000 // U+08c000, NEW_WIDTH(0x0000, 1), // U+08c000 // U+090000, NEW_WIDTH(0x0000, 1), // U+090000 // U+094000, NEW_WIDTH(0x0000, 1), // U+094000 // U+098000, NEW_WIDTH(0x0000, 1), // U+098000 // U+09c000, NEW_WIDTH(0x0000, 1), // U+09c000 // U+0a0000, NEW_WIDTH(0x0000, 1), // U+0a0000 // U+0a4000, NEW_WIDTH(0x0000, 1), // U+0a4000 // U+0a8000, NEW_WIDTH(0x0000, 1), // U+0a8000 // U+0ac000, NEW_WIDTH(0x0000, 1), // U+0ac000 // U+0b0000, NEW_WIDTH(0x0000, 1), // U+0b0000 // U+0b4000, NEW_WIDTH(0x0000, 1), // U+0b4000 // U+0b8000, NEW_WIDTH(0x0000, 1), // U+0b8000 // U+0bc000, NEW_WIDTH(0x0000, 1), // U+0bc000 // U+0c0000, NEW_WIDTH(0x0000, 1), // U+0c0000 // U+0c4000, NEW_WIDTH(0x0000, 1), // U+0c4000 // U+0c8000, NEW_WIDTH(0x0000, 1), // U+0c8000 // U+0cc000, NEW_WIDTH(0x0000, 1), // U+0cc000 // U+0d0000, NEW_WIDTH(0x0000, 1), // U+0d0000 // U+0d4000, NEW_WIDTH(0x0000, 1), // U+0d4000 // U+0d8000, NEW_WIDTH(0x0000, 1), // U+0d8000 // U+0dc000, NEW_WIDTH(0x0000, 1), // U+0dc000 // U+0e0000, NEW_WIDTH(0x0000, 1), // U+0e0000 NEW_WIDTH(0x0001, 0), // U+0e0001 NEW_WIDTH(0x0002, 1), // U+0e0002 NEW_WIDTH(0x0020, 0), // U+0e0020 NEW_WIDTH(0x0080, 1), // U+0e0080 NEW_WIDTH(0x0100, 0), // U+0e0100 NEW_WIDTH(0x01f0, 1), // U+0e01f0 // U+0e4000, NEW_WIDTH(0x0000, 1), // U+0e4000 // U+0e8000, NEW_WIDTH(0x0000, 1), // U+0e8000 // U+0ec000, NEW_WIDTH(0x0000, 1), // U+0ec000 // U+0f0000, NEW_WIDTH(0x0000, 1), // U+0f0000 // U+0f4000, NEW_WIDTH(0x0000, 1), // U+0f4000 // U+0f8000, NEW_WIDTH(0x0000, 1), // U+0f8000 // U+0fc000, NEW_WIDTH(0x0000, 1), // U+0fc000 // U+100000, NEW_WIDTH(0x0000, 1), // U+100000 // U+104000, NEW_WIDTH(0x0000, 1), // U+104000 // U+108000, NEW_WIDTH(0x0000, 1), // U+108000 // U+10c000, NEW_WIDTH(0x0000, 1), // U+10c000 };termpaint-0.3.1/charclassification_konsole_2018.inc000066400000000000000000001110511477303547200222610ustar00rootroot00000000000000static const uint16_t termpaint_char_width_offsets_konsole_2018[0x45] = { 0, 454, 457, 532, 558, 727, 737, 749, 866, 867, 868, 869, 871, 872, 873, 874, 876, 877, 878, 879, 880, 881, 882, 883, 884, 885, 886, 887, 888, 889, 890, 891, 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, 912, 913, 914, 915, 916, 923, 924, 925, 926, 927, 928, 929, 930, 931, 932, 933, 934, }; static const uint16_t termpaint_char_width_data_konsole_2018[] = { // U+000000, NEW_WIDTH(0x0000, 1), // U+000000 NEW_WIDTH(0x0001, -1), // U+000001 NEW_WIDTH(0x0020, 1), // U+000020 NEW_WIDTH(0x007f, -1), // U+00007f NEW_WIDTH(0x00a0, 1), // U+0000a0 NEW_WIDTH(0x0300, 0), // U+000300 NEW_WIDTH(0x0370, 1), // U+000370 NEW_WIDTH(0x0483, 0), // U+000483 NEW_WIDTH(0x048a, 1), // U+00048a NEW_WIDTH(0x0591, 0), // U+000591 NEW_WIDTH(0x05be, 1), // U+0005be NEW_WIDTH(0x05bf, 0), // U+0005bf NEW_WIDTH(0x05c0, 1), // U+0005c0 NEW_WIDTH(0x05c1, 0), // U+0005c1 NEW_WIDTH(0x05c3, 1), // U+0005c3 NEW_WIDTH(0x05c4, 0), // U+0005c4 NEW_WIDTH(0x05c6, 1), // U+0005c6 NEW_WIDTH(0x05c7, 0), // U+0005c7 NEW_WIDTH(0x05c8, 1), // U+0005c8 NEW_WIDTH(0x0600, 0), // U+000600 NEW_WIDTH(0x0606, 1), // U+000606 NEW_WIDTH(0x0610, 0), // U+000610 NEW_WIDTH(0x061b, 1), // U+00061b NEW_WIDTH(0x061c, 0), // U+00061c NEW_WIDTH(0x061d, 1), // U+00061d NEW_WIDTH(0x064b, 0), // U+00064b NEW_WIDTH(0x0660, 1), // U+000660 NEW_WIDTH(0x0670, 0), // U+000670 NEW_WIDTH(0x0671, 1), // U+000671 NEW_WIDTH(0x06d6, 0), // U+0006d6 NEW_WIDTH(0x06de, 1), // U+0006de NEW_WIDTH(0x06df, 0), // U+0006df NEW_WIDTH(0x06e5, 1), // U+0006e5 NEW_WIDTH(0x06e7, 0), // U+0006e7 NEW_WIDTH(0x06e9, 1), // U+0006e9 NEW_WIDTH(0x06ea, 0), // U+0006ea NEW_WIDTH(0x06ee, 1), // U+0006ee NEW_WIDTH(0x070f, 0), // U+00070f NEW_WIDTH(0x0710, 1), // U+000710 NEW_WIDTH(0x0711, 0), // U+000711 NEW_WIDTH(0x0712, 1), // U+000712 NEW_WIDTH(0x0730, 0), // U+000730 NEW_WIDTH(0x074b, 1), // U+00074b NEW_WIDTH(0x07a6, 0), // U+0007a6 NEW_WIDTH(0x07b1, 1), // U+0007b1 NEW_WIDTH(0x07eb, 0), // U+0007eb NEW_WIDTH(0x07f4, 1), // U+0007f4 NEW_WIDTH(0x07fd, 0), // U+0007fd NEW_WIDTH(0x07fe, 1), // U+0007fe NEW_WIDTH(0x0816, 0), // U+000816 NEW_WIDTH(0x081a, 1), // U+00081a NEW_WIDTH(0x081b, 0), // U+00081b NEW_WIDTH(0x0824, 1), // U+000824 NEW_WIDTH(0x0825, 0), // U+000825 NEW_WIDTH(0x0828, 1), // U+000828 NEW_WIDTH(0x0829, 0), // U+000829 NEW_WIDTH(0x082e, 1), // U+00082e NEW_WIDTH(0x0859, 0), // U+000859 NEW_WIDTH(0x085c, 1), // U+00085c NEW_WIDTH(0x08d3, 0), // U+0008d3 NEW_WIDTH(0x0903, 1), // U+000903 NEW_WIDTH(0x093a, 0), // U+00093a NEW_WIDTH(0x093b, 1), // U+00093b NEW_WIDTH(0x093c, 0), // U+00093c NEW_WIDTH(0x093d, 1), // U+00093d NEW_WIDTH(0x0941, 0), // U+000941 NEW_WIDTH(0x0949, 1), // U+000949 NEW_WIDTH(0x094d, 0), // U+00094d NEW_WIDTH(0x094e, 1), // U+00094e NEW_WIDTH(0x0951, 0), // U+000951 NEW_WIDTH(0x0958, 1), // U+000958 NEW_WIDTH(0x0962, 0), // U+000962 NEW_WIDTH(0x0964, 1), // U+000964 NEW_WIDTH(0x0981, 0), // U+000981 NEW_WIDTH(0x0982, 1), // U+000982 NEW_WIDTH(0x09bc, 0), // U+0009bc NEW_WIDTH(0x09bd, 1), // U+0009bd NEW_WIDTH(0x09c1, 0), // U+0009c1 NEW_WIDTH(0x09c5, 1), // U+0009c5 NEW_WIDTH(0x09cd, 0), // U+0009cd NEW_WIDTH(0x09ce, 1), // U+0009ce NEW_WIDTH(0x09e2, 0), // U+0009e2 NEW_WIDTH(0x09e4, 1), // U+0009e4 NEW_WIDTH(0x09fe, 0), // U+0009fe NEW_WIDTH(0x09ff, 1), // U+0009ff NEW_WIDTH(0x0a01, 0), // U+000a01 NEW_WIDTH(0x0a03, 1), // U+000a03 NEW_WIDTH(0x0a3c, 0), // U+000a3c NEW_WIDTH(0x0a3d, 1), // U+000a3d NEW_WIDTH(0x0a41, 0), // U+000a41 NEW_WIDTH(0x0a43, 1), // U+000a43 NEW_WIDTH(0x0a47, 0), // U+000a47 NEW_WIDTH(0x0a49, 1), // U+000a49 NEW_WIDTH(0x0a4b, 0), // U+000a4b NEW_WIDTH(0x0a4e, 1), // U+000a4e NEW_WIDTH(0x0a51, 0), // U+000a51 NEW_WIDTH(0x0a52, 1), // U+000a52 NEW_WIDTH(0x0a70, 0), // U+000a70 NEW_WIDTH(0x0a72, 1), // U+000a72 NEW_WIDTH(0x0a75, 0), // U+000a75 NEW_WIDTH(0x0a76, 1), // U+000a76 NEW_WIDTH(0x0a81, 0), // U+000a81 NEW_WIDTH(0x0a83, 1), // U+000a83 NEW_WIDTH(0x0abc, 0), // U+000abc NEW_WIDTH(0x0abd, 1), // U+000abd NEW_WIDTH(0x0ac1, 0), // U+000ac1 NEW_WIDTH(0x0ac6, 1), // U+000ac6 NEW_WIDTH(0x0ac7, 0), // U+000ac7 NEW_WIDTH(0x0ac9, 1), // U+000ac9 NEW_WIDTH(0x0acd, 0), // U+000acd NEW_WIDTH(0x0ace, 1), // U+000ace NEW_WIDTH(0x0ae2, 0), // U+000ae2 NEW_WIDTH(0x0ae4, 1), // U+000ae4 NEW_WIDTH(0x0afa, 0), // U+000afa NEW_WIDTH(0x0b00, 1), // U+000b00 NEW_WIDTH(0x0b01, 0), // U+000b01 NEW_WIDTH(0x0b02, 1), // U+000b02 NEW_WIDTH(0x0b3c, 0), // U+000b3c NEW_WIDTH(0x0b3d, 1), // U+000b3d NEW_WIDTH(0x0b3f, 0), // U+000b3f NEW_WIDTH(0x0b40, 1), // U+000b40 NEW_WIDTH(0x0b41, 0), // U+000b41 NEW_WIDTH(0x0b45, 1), // U+000b45 NEW_WIDTH(0x0b4d, 0), // U+000b4d NEW_WIDTH(0x0b4e, 1), // U+000b4e NEW_WIDTH(0x0b56, 0), // U+000b56 NEW_WIDTH(0x0b57, 1), // U+000b57 NEW_WIDTH(0x0b62, 0), // U+000b62 NEW_WIDTH(0x0b64, 1), // U+000b64 NEW_WIDTH(0x0b82, 0), // U+000b82 NEW_WIDTH(0x0b83, 1), // U+000b83 NEW_WIDTH(0x0bc0, 0), // U+000bc0 NEW_WIDTH(0x0bc1, 1), // U+000bc1 NEW_WIDTH(0x0bcd, 0), // U+000bcd NEW_WIDTH(0x0bce, 1), // U+000bce NEW_WIDTH(0x0c00, 0), // U+000c00 NEW_WIDTH(0x0c01, 1), // U+000c01 NEW_WIDTH(0x0c04, 0), // U+000c04 NEW_WIDTH(0x0c05, 1), // U+000c05 NEW_WIDTH(0x0c3e, 0), // U+000c3e NEW_WIDTH(0x0c41, 1), // U+000c41 NEW_WIDTH(0x0c46, 0), // U+000c46 NEW_WIDTH(0x0c49, 1), // U+000c49 NEW_WIDTH(0x0c4a, 0), // U+000c4a NEW_WIDTH(0x0c4e, 1), // U+000c4e NEW_WIDTH(0x0c55, 0), // U+000c55 NEW_WIDTH(0x0c57, 1), // U+000c57 NEW_WIDTH(0x0c62, 0), // U+000c62 NEW_WIDTH(0x0c64, 1), // U+000c64 NEW_WIDTH(0x0c81, 0), // U+000c81 NEW_WIDTH(0x0c82, 1), // U+000c82 NEW_WIDTH(0x0cbc, 0), // U+000cbc NEW_WIDTH(0x0cbd, 1), // U+000cbd NEW_WIDTH(0x0cbf, 0), // U+000cbf NEW_WIDTH(0x0cc0, 1), // U+000cc0 NEW_WIDTH(0x0cc6, 0), // U+000cc6 NEW_WIDTH(0x0cc7, 1), // U+000cc7 NEW_WIDTH(0x0ccc, 0), // U+000ccc NEW_WIDTH(0x0cce, 1), // U+000cce NEW_WIDTH(0x0ce2, 0), // U+000ce2 NEW_WIDTH(0x0ce4, 1), // U+000ce4 NEW_WIDTH(0x0d00, 0), // U+000d00 NEW_WIDTH(0x0d02, 1), // U+000d02 NEW_WIDTH(0x0d3b, 0), // U+000d3b NEW_WIDTH(0x0d3d, 1), // U+000d3d NEW_WIDTH(0x0d41, 0), // U+000d41 NEW_WIDTH(0x0d45, 1), // U+000d45 NEW_WIDTH(0x0d4d, 0), // U+000d4d NEW_WIDTH(0x0d4e, 1), // U+000d4e NEW_WIDTH(0x0d62, 0), // U+000d62 NEW_WIDTH(0x0d64, 1), // U+000d64 NEW_WIDTH(0x0dca, 0), // U+000dca NEW_WIDTH(0x0dcb, 1), // U+000dcb NEW_WIDTH(0x0dd2, 0), // U+000dd2 NEW_WIDTH(0x0dd5, 1), // U+000dd5 NEW_WIDTH(0x0dd6, 0), // U+000dd6 NEW_WIDTH(0x0dd7, 1), // U+000dd7 NEW_WIDTH(0x0e31, 0), // U+000e31 NEW_WIDTH(0x0e32, 1), // U+000e32 NEW_WIDTH(0x0e34, 0), // U+000e34 NEW_WIDTH(0x0e3b, 1), // U+000e3b NEW_WIDTH(0x0e47, 0), // U+000e47 NEW_WIDTH(0x0e4f, 1), // U+000e4f NEW_WIDTH(0x0eb1, 0), // U+000eb1 NEW_WIDTH(0x0eb2, 1), // U+000eb2 NEW_WIDTH(0x0eb4, 0), // U+000eb4 NEW_WIDTH(0x0eba, 1), // U+000eba NEW_WIDTH(0x0ebb, 0), // U+000ebb NEW_WIDTH(0x0ebd, 1), // U+000ebd NEW_WIDTH(0x0ec8, 0), // U+000ec8 NEW_WIDTH(0x0ece, 1), // U+000ece NEW_WIDTH(0x0f18, 0), // U+000f18 NEW_WIDTH(0x0f1a, 1), // U+000f1a NEW_WIDTH(0x0f35, 0), // U+000f35 NEW_WIDTH(0x0f36, 1), // U+000f36 NEW_WIDTH(0x0f37, 0), // U+000f37 NEW_WIDTH(0x0f38, 1), // U+000f38 NEW_WIDTH(0x0f39, 0), // U+000f39 NEW_WIDTH(0x0f3a, 1), // U+000f3a NEW_WIDTH(0x0f71, 0), // U+000f71 NEW_WIDTH(0x0f7f, 1), // U+000f7f NEW_WIDTH(0x0f80, 0), // U+000f80 NEW_WIDTH(0x0f85, 1), // U+000f85 NEW_WIDTH(0x0f86, 0), // U+000f86 NEW_WIDTH(0x0f88, 1), // U+000f88 NEW_WIDTH(0x0f8d, 0), // U+000f8d NEW_WIDTH(0x0f98, 1), // U+000f98 NEW_WIDTH(0x0f99, 0), // U+000f99 NEW_WIDTH(0x0fbd, 1), // U+000fbd NEW_WIDTH(0x0fc6, 0), // U+000fc6 NEW_WIDTH(0x0fc7, 1), // U+000fc7 NEW_WIDTH(0x102d, 0), // U+00102d NEW_WIDTH(0x1031, 1), // U+001031 NEW_WIDTH(0x1032, 0), // U+001032 NEW_WIDTH(0x1038, 1), // U+001038 NEW_WIDTH(0x1039, 0), // U+001039 NEW_WIDTH(0x103b, 1), // U+00103b NEW_WIDTH(0x103d, 0), // U+00103d NEW_WIDTH(0x103f, 1), // U+00103f NEW_WIDTH(0x1058, 0), // U+001058 NEW_WIDTH(0x105a, 1), // U+00105a NEW_WIDTH(0x105e, 0), // U+00105e NEW_WIDTH(0x1061, 1), // U+001061 NEW_WIDTH(0x1071, 0), // U+001071 NEW_WIDTH(0x1075, 1), // U+001075 NEW_WIDTH(0x1082, 0), // U+001082 NEW_WIDTH(0x1083, 1), // U+001083 NEW_WIDTH(0x1085, 0), // U+001085 NEW_WIDTH(0x1087, 1), // U+001087 NEW_WIDTH(0x108d, 0), // U+00108d NEW_WIDTH(0x108e, 1), // U+00108e NEW_WIDTH(0x109d, 0), // U+00109d NEW_WIDTH(0x109e, 1), // U+00109e NEW_WIDTH(0x1100, 2), // U+001100 NEW_WIDTH(0x1160, 0), // U+001160 NEW_WIDTH(0x1161, 1), // U+001161 NEW_WIDTH(0x135d, 0), // U+00135d NEW_WIDTH(0x1360, 1), // U+001360 NEW_WIDTH(0x1712, 0), // U+001712 NEW_WIDTH(0x1715, 1), // U+001715 NEW_WIDTH(0x1732, 0), // U+001732 NEW_WIDTH(0x1735, 1), // U+001735 NEW_WIDTH(0x1752, 0), // U+001752 NEW_WIDTH(0x1754, 1), // U+001754 NEW_WIDTH(0x1772, 0), // U+001772 NEW_WIDTH(0x1774, 1), // U+001774 NEW_WIDTH(0x17b4, 0), // U+0017b4 NEW_WIDTH(0x17b6, 1), // U+0017b6 NEW_WIDTH(0x17b7, 0), // U+0017b7 NEW_WIDTH(0x17be, 1), // U+0017be NEW_WIDTH(0x17c6, 0), // U+0017c6 NEW_WIDTH(0x17c7, 1), // U+0017c7 NEW_WIDTH(0x17c9, 0), // U+0017c9 NEW_WIDTH(0x17d4, 1), // U+0017d4 NEW_WIDTH(0x17dd, 0), // U+0017dd NEW_WIDTH(0x17de, 1), // U+0017de NEW_WIDTH(0x180b, 0), // U+00180b NEW_WIDTH(0x180f, 1), // U+00180f NEW_WIDTH(0x1885, 0), // U+001885 NEW_WIDTH(0x1887, 1), // U+001887 NEW_WIDTH(0x18a9, 0), // U+0018a9 NEW_WIDTH(0x18aa, 1), // U+0018aa NEW_WIDTH(0x1920, 0), // U+001920 NEW_WIDTH(0x1923, 1), // U+001923 NEW_WIDTH(0x1927, 0), // U+001927 NEW_WIDTH(0x1929, 1), // U+001929 NEW_WIDTH(0x1932, 0), // U+001932 NEW_WIDTH(0x1933, 1), // U+001933 NEW_WIDTH(0x1939, 0), // U+001939 NEW_WIDTH(0x193c, 1), // U+00193c NEW_WIDTH(0x1a17, 0), // U+001a17 NEW_WIDTH(0x1a19, 1), // U+001a19 NEW_WIDTH(0x1a1b, 0), // U+001a1b NEW_WIDTH(0x1a1c, 1), // U+001a1c NEW_WIDTH(0x1a56, 0), // U+001a56 NEW_WIDTH(0x1a57, 1), // U+001a57 NEW_WIDTH(0x1a58, 0), // U+001a58 NEW_WIDTH(0x1a5f, 1), // U+001a5f NEW_WIDTH(0x1a60, 0), // U+001a60 NEW_WIDTH(0x1a61, 1), // U+001a61 NEW_WIDTH(0x1a62, 0), // U+001a62 NEW_WIDTH(0x1a63, 1), // U+001a63 NEW_WIDTH(0x1a65, 0), // U+001a65 NEW_WIDTH(0x1a6d, 1), // U+001a6d NEW_WIDTH(0x1a73, 0), // U+001a73 NEW_WIDTH(0x1a7d, 1), // U+001a7d NEW_WIDTH(0x1a7f, 0), // U+001a7f NEW_WIDTH(0x1a80, 1), // U+001a80 NEW_WIDTH(0x1ab0, 0), // U+001ab0 NEW_WIDTH(0x1abf, 1), // U+001abf NEW_WIDTH(0x1b00, 0), // U+001b00 NEW_WIDTH(0x1b04, 1), // U+001b04 NEW_WIDTH(0x1b34, 0), // U+001b34 NEW_WIDTH(0x1b35, 1), // U+001b35 NEW_WIDTH(0x1b36, 0), // U+001b36 NEW_WIDTH(0x1b3b, 1), // U+001b3b NEW_WIDTH(0x1b3c, 0), // U+001b3c NEW_WIDTH(0x1b3d, 1), // U+001b3d NEW_WIDTH(0x1b42, 0), // U+001b42 NEW_WIDTH(0x1b43, 1), // U+001b43 NEW_WIDTH(0x1b6b, 0), // U+001b6b NEW_WIDTH(0x1b74, 1), // U+001b74 NEW_WIDTH(0x1b80, 0), // U+001b80 NEW_WIDTH(0x1b82, 1), // U+001b82 NEW_WIDTH(0x1ba2, 0), // U+001ba2 NEW_WIDTH(0x1ba6, 1), // U+001ba6 NEW_WIDTH(0x1ba8, 0), // U+001ba8 NEW_WIDTH(0x1baa, 1), // U+001baa NEW_WIDTH(0x1bab, 0), // U+001bab NEW_WIDTH(0x1bae, 1), // U+001bae NEW_WIDTH(0x1be6, 0), // U+001be6 NEW_WIDTH(0x1be7, 1), // U+001be7 NEW_WIDTH(0x1be8, 0), // U+001be8 NEW_WIDTH(0x1bea, 1), // U+001bea NEW_WIDTH(0x1bed, 0), // U+001bed NEW_WIDTH(0x1bee, 1), // U+001bee NEW_WIDTH(0x1bef, 0), // U+001bef NEW_WIDTH(0x1bf2, 1), // U+001bf2 NEW_WIDTH(0x1c2c, 0), // U+001c2c NEW_WIDTH(0x1c34, 1), // U+001c34 NEW_WIDTH(0x1c36, 0), // U+001c36 NEW_WIDTH(0x1c38, 1), // U+001c38 NEW_WIDTH(0x1cd0, 0), // U+001cd0 NEW_WIDTH(0x1cd3, 1), // U+001cd3 NEW_WIDTH(0x1cd4, 0), // U+001cd4 NEW_WIDTH(0x1ce1, 1), // U+001ce1 NEW_WIDTH(0x1ce2, 0), // U+001ce2 NEW_WIDTH(0x1ce9, 1), // U+001ce9 NEW_WIDTH(0x1ced, 0), // U+001ced NEW_WIDTH(0x1cee, 1), // U+001cee NEW_WIDTH(0x1cf4, 0), // U+001cf4 NEW_WIDTH(0x1cf5, 1), // U+001cf5 NEW_WIDTH(0x1cf8, 0), // U+001cf8 NEW_WIDTH(0x1cfa, 1), // U+001cfa NEW_WIDTH(0x1dc0, 0), // U+001dc0 NEW_WIDTH(0x1dfa, 1), // U+001dfa NEW_WIDTH(0x1dfb, 0), // U+001dfb NEW_WIDTH(0x1e00, 1), // U+001e00 NEW_WIDTH(0x200b, 0), // U+00200b NEW_WIDTH(0x2010, 1), // U+002010 NEW_WIDTH(0x202a, 0), // U+00202a NEW_WIDTH(0x202f, 1), // U+00202f NEW_WIDTH(0x2060, 0), // U+002060 NEW_WIDTH(0x2065, 1), // U+002065 NEW_WIDTH(0x2066, 0), // U+002066 NEW_WIDTH(0x2070, 1), // U+002070 NEW_WIDTH(0x20d0, 0), // U+0020d0 NEW_WIDTH(0x20f1, 1), // U+0020f1 NEW_WIDTH(0x231a, 2), // U+00231a NEW_WIDTH(0x231c, 1), // U+00231c NEW_WIDTH(0x2329, 2), // U+002329 NEW_WIDTH(0x232b, 1), // U+00232b NEW_WIDTH(0x23e9, 2), // U+0023e9 NEW_WIDTH(0x23ed, 1), // U+0023ed NEW_WIDTH(0x23f0, 2), // U+0023f0 NEW_WIDTH(0x23f1, 1), // U+0023f1 NEW_WIDTH(0x23f3, 2), // U+0023f3 NEW_WIDTH(0x23f4, 1), // U+0023f4 NEW_WIDTH(0x25fd, 2), // U+0025fd NEW_WIDTH(0x25ff, 1), // U+0025ff NEW_WIDTH(0x2614, 2), // U+002614 NEW_WIDTH(0x2616, 1), // U+002616 NEW_WIDTH(0x2648, 2), // U+002648 NEW_WIDTH(0x2654, 1), // U+002654 NEW_WIDTH(0x267f, 2), // U+00267f NEW_WIDTH(0x2680, 1), // U+002680 NEW_WIDTH(0x2693, 2), // U+002693 NEW_WIDTH(0x2694, 1), // U+002694 NEW_WIDTH(0x26a1, 2), // U+0026a1 NEW_WIDTH(0x26a2, 1), // U+0026a2 NEW_WIDTH(0x26aa, 2), // U+0026aa NEW_WIDTH(0x26ac, 1), // U+0026ac NEW_WIDTH(0x26bd, 2), // U+0026bd NEW_WIDTH(0x26bf, 1), // U+0026bf NEW_WIDTH(0x26c4, 2), // U+0026c4 NEW_WIDTH(0x26c6, 1), // U+0026c6 NEW_WIDTH(0x26ce, 2), // U+0026ce NEW_WIDTH(0x26cf, 1), // U+0026cf NEW_WIDTH(0x26d4, 2), // U+0026d4 NEW_WIDTH(0x26d5, 1), // U+0026d5 NEW_WIDTH(0x26ea, 2), // U+0026ea NEW_WIDTH(0x26eb, 1), // U+0026eb NEW_WIDTH(0x26f2, 2), // U+0026f2 NEW_WIDTH(0x26f4, 1), // U+0026f4 NEW_WIDTH(0x26f5, 2), // U+0026f5 NEW_WIDTH(0x26f6, 1), // U+0026f6 NEW_WIDTH(0x26fa, 2), // U+0026fa NEW_WIDTH(0x26fb, 1), // U+0026fb NEW_WIDTH(0x26fd, 2), // U+0026fd NEW_WIDTH(0x26fe, 1), // U+0026fe NEW_WIDTH(0x2705, 2), // U+002705 NEW_WIDTH(0x2706, 1), // U+002706 NEW_WIDTH(0x270a, 2), // U+00270a NEW_WIDTH(0x270c, 1), // U+00270c NEW_WIDTH(0x2728, 2), // U+002728 NEW_WIDTH(0x2729, 1), // U+002729 NEW_WIDTH(0x274c, 2), // U+00274c NEW_WIDTH(0x274d, 1), // U+00274d NEW_WIDTH(0x274e, 2), // U+00274e NEW_WIDTH(0x274f, 1), // U+00274f NEW_WIDTH(0x2753, 2), // U+002753 NEW_WIDTH(0x2756, 1), // U+002756 NEW_WIDTH(0x2757, 2), // U+002757 NEW_WIDTH(0x2758, 1), // U+002758 NEW_WIDTH(0x2795, 2), // U+002795 NEW_WIDTH(0x2798, 1), // U+002798 NEW_WIDTH(0x27b0, 2), // U+0027b0 NEW_WIDTH(0x27b1, 1), // U+0027b1 NEW_WIDTH(0x27bf, 2), // U+0027bf NEW_WIDTH(0x27c0, 1), // U+0027c0 NEW_WIDTH(0x2b1b, 2), // U+002b1b NEW_WIDTH(0x2b1d, 1), // U+002b1d NEW_WIDTH(0x2b50, 2), // U+002b50 NEW_WIDTH(0x2b51, 1), // U+002b51 NEW_WIDTH(0x2b55, 2), // U+002b55 NEW_WIDTH(0x2b56, 1), // U+002b56 NEW_WIDTH(0x2cef, 0), // U+002cef NEW_WIDTH(0x2cf2, 1), // U+002cf2 NEW_WIDTH(0x2d7f, 0), // U+002d7f NEW_WIDTH(0x2d80, 1), // U+002d80 NEW_WIDTH(0x2de0, 0), // U+002de0 NEW_WIDTH(0x2e00, 1), // U+002e00 NEW_WIDTH(0x2e80, 2), // U+002e80 NEW_WIDTH(0x2e9a, 1), // U+002e9a NEW_WIDTH(0x2e9b, 2), // U+002e9b NEW_WIDTH(0x2ef4, 1), // U+002ef4 NEW_WIDTH(0x2f00, 2), // U+002f00 NEW_WIDTH(0x2fd6, 1), // U+002fd6 NEW_WIDTH(0x2ff0, 2), // U+002ff0 NEW_WIDTH(0x2ffc, 1), // U+002ffc NEW_WIDTH(0x3000, 2), // U+003000 NEW_WIDTH(0x302a, 0), // U+00302a NEW_WIDTH(0x302e, 2), // U+00302e NEW_WIDTH(0x303f, 1), // U+00303f NEW_WIDTH(0x3041, 2), // U+003041 NEW_WIDTH(0x3097, 1), // U+003097 NEW_WIDTH(0x3099, 0), // U+003099 NEW_WIDTH(0x309b, 2), // U+00309b NEW_WIDTH(0x3100, 1), // U+003100 NEW_WIDTH(0x3105, 2), // U+003105 NEW_WIDTH(0x3130, 1), // U+003130 NEW_WIDTH(0x3131, 2), // U+003131 NEW_WIDTH(0x318f, 1), // U+00318f NEW_WIDTH(0x3190, 2), // U+003190 NEW_WIDTH(0x31bb, 1), // U+0031bb NEW_WIDTH(0x31c0, 2), // U+0031c0 NEW_WIDTH(0x31e4, 1), // U+0031e4 NEW_WIDTH(0x31f0, 2), // U+0031f0 NEW_WIDTH(0x321f, 1), // U+00321f NEW_WIDTH(0x3220, 2), // U+003220 NEW_WIDTH(0x3248, 1), // U+003248 NEW_WIDTH(0x3250, 2), // U+003250 NEW_WIDTH(0x32ff, 1), // U+0032ff NEW_WIDTH(0x3300, 2), // U+003300 // U+004000, NEW_WIDTH(0x0000, 2), // U+004000 NEW_WIDTH(0x0dc0, 1), // U+004dc0 NEW_WIDTH(0x0e00, 2), // U+004e00 // U+008000, NEW_WIDTH(0x0000, 2), // U+008000 NEW_WIDTH(0x248d, 1), // U+00a48d NEW_WIDTH(0x2490, 2), // U+00a490 NEW_WIDTH(0x24c7, 1), // U+00a4c7 NEW_WIDTH(0x266f, 0), // U+00a66f NEW_WIDTH(0x2673, 1), // U+00a673 NEW_WIDTH(0x2674, 0), // U+00a674 NEW_WIDTH(0x267e, 1), // U+00a67e NEW_WIDTH(0x269e, 0), // U+00a69e NEW_WIDTH(0x26a0, 1), // U+00a6a0 NEW_WIDTH(0x26f0, 0), // U+00a6f0 NEW_WIDTH(0x26f2, 1), // U+00a6f2 NEW_WIDTH(0x2802, 0), // U+00a802 NEW_WIDTH(0x2803, 1), // U+00a803 NEW_WIDTH(0x2806, 0), // U+00a806 NEW_WIDTH(0x2807, 1), // U+00a807 NEW_WIDTH(0x280b, 0), // U+00a80b NEW_WIDTH(0x280c, 1), // U+00a80c NEW_WIDTH(0x2825, 0), // U+00a825 NEW_WIDTH(0x2827, 1), // U+00a827 NEW_WIDTH(0x28c4, 0), // U+00a8c4 NEW_WIDTH(0x28c6, 1), // U+00a8c6 NEW_WIDTH(0x28e0, 0), // U+00a8e0 NEW_WIDTH(0x28f2, 1), // U+00a8f2 NEW_WIDTH(0x28ff, 0), // U+00a8ff NEW_WIDTH(0x2900, 1), // U+00a900 NEW_WIDTH(0x2926, 0), // U+00a926 NEW_WIDTH(0x292e, 1), // U+00a92e NEW_WIDTH(0x2947, 0), // U+00a947 NEW_WIDTH(0x2952, 1), // U+00a952 NEW_WIDTH(0x2960, 2), // U+00a960 NEW_WIDTH(0x297d, 1), // U+00a97d NEW_WIDTH(0x2980, 0), // U+00a980 NEW_WIDTH(0x2983, 1), // U+00a983 NEW_WIDTH(0x29b3, 0), // U+00a9b3 NEW_WIDTH(0x29b4, 1), // U+00a9b4 NEW_WIDTH(0x29b6, 0), // U+00a9b6 NEW_WIDTH(0x29ba, 1), // U+00a9ba NEW_WIDTH(0x29bc, 0), // U+00a9bc NEW_WIDTH(0x29bd, 1), // U+00a9bd NEW_WIDTH(0x29e5, 0), // U+00a9e5 NEW_WIDTH(0x29e6, 1), // U+00a9e6 NEW_WIDTH(0x2a29, 0), // U+00aa29 NEW_WIDTH(0x2a2f, 1), // U+00aa2f NEW_WIDTH(0x2a31, 0), // U+00aa31 NEW_WIDTH(0x2a33, 1), // U+00aa33 NEW_WIDTH(0x2a35, 0), // U+00aa35 NEW_WIDTH(0x2a37, 1), // U+00aa37 NEW_WIDTH(0x2a43, 0), // U+00aa43 NEW_WIDTH(0x2a44, 1), // U+00aa44 NEW_WIDTH(0x2a4c, 0), // U+00aa4c NEW_WIDTH(0x2a4d, 1), // U+00aa4d NEW_WIDTH(0x2a7c, 0), // U+00aa7c NEW_WIDTH(0x2a7d, 1), // U+00aa7d NEW_WIDTH(0x2ab0, 0), // U+00aab0 NEW_WIDTH(0x2ab1, 1), // U+00aab1 NEW_WIDTH(0x2ab2, 0), // U+00aab2 NEW_WIDTH(0x2ab5, 1), // U+00aab5 NEW_WIDTH(0x2ab7, 0), // U+00aab7 NEW_WIDTH(0x2ab9, 1), // U+00aab9 NEW_WIDTH(0x2abe, 0), // U+00aabe NEW_WIDTH(0x2ac0, 1), // U+00aac0 NEW_WIDTH(0x2ac1, 0), // U+00aac1 NEW_WIDTH(0x2ac2, 1), // U+00aac2 NEW_WIDTH(0x2aec, 0), // U+00aaec NEW_WIDTH(0x2aee, 1), // U+00aaee NEW_WIDTH(0x2af6, 0), // U+00aaf6 NEW_WIDTH(0x2af7, 1), // U+00aaf7 NEW_WIDTH(0x2be5, 0), // U+00abe5 NEW_WIDTH(0x2be6, 1), // U+00abe6 NEW_WIDTH(0x2be8, 0), // U+00abe8 NEW_WIDTH(0x2be9, 1), // U+00abe9 NEW_WIDTH(0x2bed, 0), // U+00abed NEW_WIDTH(0x2bee, 1), // U+00abee NEW_WIDTH(0x2c00, 2), // U+00ac00 // U+00c000, NEW_WIDTH(0x0000, 2), // U+00c000 NEW_WIDTH(0x17a4, 1), // U+00d7a4 NEW_WIDTH(0x1800, -1), // U+00d800 NEW_WIDTH(0x2000, 1), // U+00e000 NEW_WIDTH(0x3900, 2), // U+00f900 NEW_WIDTH(0x3b00, 1), // U+00fb00 NEW_WIDTH(0x3b1e, 0), // U+00fb1e NEW_WIDTH(0x3b1f, 1), // U+00fb1f NEW_WIDTH(0x3e00, 0), // U+00fe00 NEW_WIDTH(0x3e10, 2), // U+00fe10 NEW_WIDTH(0x3e1a, 1), // U+00fe1a NEW_WIDTH(0x3e20, 0), // U+00fe20 NEW_WIDTH(0x3e30, 2), // U+00fe30 NEW_WIDTH(0x3e53, 1), // U+00fe53 NEW_WIDTH(0x3e54, 2), // U+00fe54 NEW_WIDTH(0x3e67, 1), // U+00fe67 NEW_WIDTH(0x3e68, 2), // U+00fe68 NEW_WIDTH(0x3e6c, 1), // U+00fe6c NEW_WIDTH(0x3eff, 0), // U+00feff NEW_WIDTH(0x3f00, 1), // U+00ff00 NEW_WIDTH(0x3f01, 2), // U+00ff01 NEW_WIDTH(0x3f61, 1), // U+00ff61 NEW_WIDTH(0x3fe0, 2), // U+00ffe0 NEW_WIDTH(0x3fe7, 1), // U+00ffe7 NEW_WIDTH(0x3ff9, 0), // U+00fff9 NEW_WIDTH(0x3ffc, 1), // U+00fffc // U+010000, NEW_WIDTH(0x0000, 1), // U+010000 NEW_WIDTH(0x01fd, 0), // U+0101fd NEW_WIDTH(0x01fe, 1), // U+0101fe NEW_WIDTH(0x02e0, 0), // U+0102e0 NEW_WIDTH(0x02e1, 1), // U+0102e1 NEW_WIDTH(0x0376, 0), // U+010376 NEW_WIDTH(0x037b, 1), // U+01037b NEW_WIDTH(0x0a01, 0), // U+010a01 NEW_WIDTH(0x0a04, 1), // U+010a04 NEW_WIDTH(0x0a05, 0), // U+010a05 NEW_WIDTH(0x0a07, 1), // U+010a07 NEW_WIDTH(0x0a0c, 0), // U+010a0c NEW_WIDTH(0x0a10, 1), // U+010a10 NEW_WIDTH(0x0a38, 0), // U+010a38 NEW_WIDTH(0x0a3b, 1), // U+010a3b NEW_WIDTH(0x0a3f, 0), // U+010a3f NEW_WIDTH(0x0a40, 1), // U+010a40 NEW_WIDTH(0x0ae5, 0), // U+010ae5 NEW_WIDTH(0x0ae7, 1), // U+010ae7 NEW_WIDTH(0x0d24, 0), // U+010d24 NEW_WIDTH(0x0d28, 1), // U+010d28 NEW_WIDTH(0x0f46, 0), // U+010f46 NEW_WIDTH(0x0f51, 1), // U+010f51 NEW_WIDTH(0x1001, 0), // U+011001 NEW_WIDTH(0x1002, 1), // U+011002 NEW_WIDTH(0x1038, 0), // U+011038 NEW_WIDTH(0x1047, 1), // U+011047 NEW_WIDTH(0x107f, 0), // U+01107f NEW_WIDTH(0x1082, 1), // U+011082 NEW_WIDTH(0x10b3, 0), // U+0110b3 NEW_WIDTH(0x10b7, 1), // U+0110b7 NEW_WIDTH(0x10b9, 0), // U+0110b9 NEW_WIDTH(0x10bb, 1), // U+0110bb NEW_WIDTH(0x10bd, 0), // U+0110bd NEW_WIDTH(0x10be, 1), // U+0110be NEW_WIDTH(0x10cd, 0), // U+0110cd NEW_WIDTH(0x10ce, 1), // U+0110ce NEW_WIDTH(0x1100, 0), // U+011100 NEW_WIDTH(0x1103, 1), // U+011103 NEW_WIDTH(0x1127, 0), // U+011127 NEW_WIDTH(0x112c, 1), // U+01112c NEW_WIDTH(0x112d, 0), // U+01112d NEW_WIDTH(0x1135, 1), // U+011135 NEW_WIDTH(0x1173, 0), // U+011173 NEW_WIDTH(0x1174, 1), // U+011174 NEW_WIDTH(0x1180, 0), // U+011180 NEW_WIDTH(0x1182, 1), // U+011182 NEW_WIDTH(0x11b6, 0), // U+0111b6 NEW_WIDTH(0x11bf, 1), // U+0111bf NEW_WIDTH(0x11c9, 0), // U+0111c9 NEW_WIDTH(0x11cd, 1), // U+0111cd NEW_WIDTH(0x122f, 0), // U+01122f NEW_WIDTH(0x1232, 1), // U+011232 NEW_WIDTH(0x1234, 0), // U+011234 NEW_WIDTH(0x1235, 1), // U+011235 NEW_WIDTH(0x1236, 0), // U+011236 NEW_WIDTH(0x1238, 1), // U+011238 NEW_WIDTH(0x123e, 0), // U+01123e NEW_WIDTH(0x123f, 1), // U+01123f NEW_WIDTH(0x12df, 0), // U+0112df NEW_WIDTH(0x12e0, 1), // U+0112e0 NEW_WIDTH(0x12e3, 0), // U+0112e3 NEW_WIDTH(0x12eb, 1), // U+0112eb NEW_WIDTH(0x1300, 0), // U+011300 NEW_WIDTH(0x1302, 1), // U+011302 NEW_WIDTH(0x133b, 0), // U+01133b NEW_WIDTH(0x133d, 1), // U+01133d NEW_WIDTH(0x1340, 0), // U+011340 NEW_WIDTH(0x1341, 1), // U+011341 NEW_WIDTH(0x1366, 0), // U+011366 NEW_WIDTH(0x136d, 1), // U+01136d NEW_WIDTH(0x1370, 0), // U+011370 NEW_WIDTH(0x1375, 1), // U+011375 NEW_WIDTH(0x1438, 0), // U+011438 NEW_WIDTH(0x1440, 1), // U+011440 NEW_WIDTH(0x1442, 0), // U+011442 NEW_WIDTH(0x1445, 1), // U+011445 NEW_WIDTH(0x1446, 0), // U+011446 NEW_WIDTH(0x1447, 1), // U+011447 NEW_WIDTH(0x145e, 0), // U+01145e NEW_WIDTH(0x145f, 1), // U+01145f NEW_WIDTH(0x14b3, 0), // U+0114b3 NEW_WIDTH(0x14b9, 1), // U+0114b9 NEW_WIDTH(0x14ba, 0), // U+0114ba NEW_WIDTH(0x14bb, 1), // U+0114bb NEW_WIDTH(0x14bf, 0), // U+0114bf NEW_WIDTH(0x14c1, 1), // U+0114c1 NEW_WIDTH(0x14c2, 0), // U+0114c2 NEW_WIDTH(0x14c4, 1), // U+0114c4 NEW_WIDTH(0x15b2, 0), // U+0115b2 NEW_WIDTH(0x15b6, 1), // U+0115b6 NEW_WIDTH(0x15bc, 0), // U+0115bc NEW_WIDTH(0x15be, 1), // U+0115be NEW_WIDTH(0x15bf, 0), // U+0115bf NEW_WIDTH(0x15c1, 1), // U+0115c1 NEW_WIDTH(0x15dc, 0), // U+0115dc NEW_WIDTH(0x15de, 1), // U+0115de NEW_WIDTH(0x1633, 0), // U+011633 NEW_WIDTH(0x163b, 1), // U+01163b NEW_WIDTH(0x163d, 0), // U+01163d NEW_WIDTH(0x163e, 1), // U+01163e NEW_WIDTH(0x163f, 0), // U+01163f NEW_WIDTH(0x1641, 1), // U+011641 NEW_WIDTH(0x16ab, 0), // U+0116ab NEW_WIDTH(0x16ac, 1), // U+0116ac NEW_WIDTH(0x16ad, 0), // U+0116ad NEW_WIDTH(0x16ae, 1), // U+0116ae NEW_WIDTH(0x16b0, 0), // U+0116b0 NEW_WIDTH(0x16b6, 1), // U+0116b6 NEW_WIDTH(0x16b7, 0), // U+0116b7 NEW_WIDTH(0x16b8, 1), // U+0116b8 NEW_WIDTH(0x171d, 0), // U+01171d NEW_WIDTH(0x1720, 1), // U+011720 NEW_WIDTH(0x1722, 0), // U+011722 NEW_WIDTH(0x1726, 1), // U+011726 NEW_WIDTH(0x1727, 0), // U+011727 NEW_WIDTH(0x172c, 1), // U+01172c NEW_WIDTH(0x182f, 0), // U+01182f NEW_WIDTH(0x1838, 1), // U+011838 NEW_WIDTH(0x1839, 0), // U+011839 NEW_WIDTH(0x183b, 1), // U+01183b NEW_WIDTH(0x1a01, 0), // U+011a01 NEW_WIDTH(0x1a0b, 1), // U+011a0b NEW_WIDTH(0x1a33, 0), // U+011a33 NEW_WIDTH(0x1a39, 1), // U+011a39 NEW_WIDTH(0x1a3b, 0), // U+011a3b NEW_WIDTH(0x1a3f, 1), // U+011a3f NEW_WIDTH(0x1a47, 0), // U+011a47 NEW_WIDTH(0x1a48, 1), // U+011a48 NEW_WIDTH(0x1a51, 0), // U+011a51 NEW_WIDTH(0x1a57, 1), // U+011a57 NEW_WIDTH(0x1a59, 0), // U+011a59 NEW_WIDTH(0x1a5c, 1), // U+011a5c NEW_WIDTH(0x1a8a, 0), // U+011a8a NEW_WIDTH(0x1a97, 1), // U+011a97 NEW_WIDTH(0x1a98, 0), // U+011a98 NEW_WIDTH(0x1a9a, 1), // U+011a9a NEW_WIDTH(0x1c30, 0), // U+011c30 NEW_WIDTH(0x1c37, 1), // U+011c37 NEW_WIDTH(0x1c38, 0), // U+011c38 NEW_WIDTH(0x1c3e, 1), // U+011c3e NEW_WIDTH(0x1c3f, 0), // U+011c3f NEW_WIDTH(0x1c40, 1), // U+011c40 NEW_WIDTH(0x1c92, 0), // U+011c92 NEW_WIDTH(0x1ca8, 1), // U+011ca8 NEW_WIDTH(0x1caa, 0), // U+011caa NEW_WIDTH(0x1cb1, 1), // U+011cb1 NEW_WIDTH(0x1cb2, 0), // U+011cb2 NEW_WIDTH(0x1cb4, 1), // U+011cb4 NEW_WIDTH(0x1cb5, 0), // U+011cb5 NEW_WIDTH(0x1cb7, 1), // U+011cb7 NEW_WIDTH(0x1d31, 0), // U+011d31 NEW_WIDTH(0x1d37, 1), // U+011d37 NEW_WIDTH(0x1d3a, 0), // U+011d3a NEW_WIDTH(0x1d3b, 1), // U+011d3b NEW_WIDTH(0x1d3c, 0), // U+011d3c NEW_WIDTH(0x1d3e, 1), // U+011d3e NEW_WIDTH(0x1d3f, 0), // U+011d3f NEW_WIDTH(0x1d46, 1), // U+011d46 NEW_WIDTH(0x1d47, 0), // U+011d47 NEW_WIDTH(0x1d48, 1), // U+011d48 NEW_WIDTH(0x1d90, 0), // U+011d90 NEW_WIDTH(0x1d92, 1), // U+011d92 NEW_WIDTH(0x1d95, 0), // U+011d95 NEW_WIDTH(0x1d96, 1), // U+011d96 NEW_WIDTH(0x1d97, 0), // U+011d97 NEW_WIDTH(0x1d98, 1), // U+011d98 NEW_WIDTH(0x1ef3, 0), // U+011ef3 NEW_WIDTH(0x1ef5, 1), // U+011ef5 // U+014000, NEW_WIDTH(0x0000, 1), // U+014000 NEW_WIDTH(0x2af0, 0), // U+016af0 NEW_WIDTH(0x2af5, 1), // U+016af5 NEW_WIDTH(0x2b30, 0), // U+016b30 NEW_WIDTH(0x2b37, 1), // U+016b37 NEW_WIDTH(0x2f8f, 0), // U+016f8f NEW_WIDTH(0x2f93, 1), // U+016f93 NEW_WIDTH(0x2fe0, 2), // U+016fe0 NEW_WIDTH(0x2fe2, 1), // U+016fe2 NEW_WIDTH(0x3000, 2), // U+017000 // U+018000, NEW_WIDTH(0x0000, 2), // U+018000 NEW_WIDTH(0x07f2, 1), // U+0187f2 NEW_WIDTH(0x0800, 2), // U+018800 NEW_WIDTH(0x0af3, 1), // U+018af3 NEW_WIDTH(0x3000, 2), // U+01b000 NEW_WIDTH(0x311f, 1), // U+01b11f NEW_WIDTH(0x3170, 2), // U+01b170 NEW_WIDTH(0x32fc, 1), // U+01b2fc NEW_WIDTH(0x3c9d, 0), // U+01bc9d NEW_WIDTH(0x3c9f, 1), // U+01bc9f NEW_WIDTH(0x3ca0, 0), // U+01bca0 NEW_WIDTH(0x3ca4, 1), // U+01bca4 // U+01c000, NEW_WIDTH(0x0000, 1), // U+01c000 NEW_WIDTH(0x1167, 0), // U+01d167 NEW_WIDTH(0x116a, 1), // U+01d16a NEW_WIDTH(0x1173, 0), // U+01d173 NEW_WIDTH(0x1183, 1), // U+01d183 NEW_WIDTH(0x1185, 0), // U+01d185 NEW_WIDTH(0x118c, 1), // U+01d18c NEW_WIDTH(0x11aa, 0), // U+01d1aa NEW_WIDTH(0x11ae, 1), // U+01d1ae NEW_WIDTH(0x1242, 0), // U+01d242 NEW_WIDTH(0x1245, 1), // U+01d245 NEW_WIDTH(0x1a00, 0), // U+01da00 NEW_WIDTH(0x1a37, 1), // U+01da37 NEW_WIDTH(0x1a3b, 0), // U+01da3b NEW_WIDTH(0x1a6d, 1), // U+01da6d NEW_WIDTH(0x1a75, 0), // U+01da75 NEW_WIDTH(0x1a76, 1), // U+01da76 NEW_WIDTH(0x1a84, 0), // U+01da84 NEW_WIDTH(0x1a85, 1), // U+01da85 NEW_WIDTH(0x1a9b, 0), // U+01da9b NEW_WIDTH(0x1aa0, 1), // U+01daa0 NEW_WIDTH(0x1aa1, 0), // U+01daa1 NEW_WIDTH(0x1ab0, 1), // U+01dab0 NEW_WIDTH(0x2000, 0), // U+01e000 NEW_WIDTH(0x2007, 1), // U+01e007 NEW_WIDTH(0x2008, 0), // U+01e008 NEW_WIDTH(0x2019, 1), // U+01e019 NEW_WIDTH(0x201b, 0), // U+01e01b NEW_WIDTH(0x2022, 1), // U+01e022 NEW_WIDTH(0x2023, 0), // U+01e023 NEW_WIDTH(0x2025, 1), // U+01e025 NEW_WIDTH(0x2026, 0), // U+01e026 NEW_WIDTH(0x202b, 1), // U+01e02b NEW_WIDTH(0x28d0, 0), // U+01e8d0 NEW_WIDTH(0x28d7, 1), // U+01e8d7 NEW_WIDTH(0x2944, 0), // U+01e944 NEW_WIDTH(0x294b, 1), // U+01e94b NEW_WIDTH(0x3004, 2), // U+01f004 NEW_WIDTH(0x3005, 1), // U+01f005 NEW_WIDTH(0x30cf, 2), // U+01f0cf NEW_WIDTH(0x30d0, 1), // U+01f0d0 NEW_WIDTH(0x318e, 2), // U+01f18e NEW_WIDTH(0x318f, 1), // U+01f18f NEW_WIDTH(0x3191, 2), // U+01f191 NEW_WIDTH(0x319b, 1), // U+01f19b NEW_WIDTH(0x31e6, 2), // U+01f1e6 NEW_WIDTH(0x3203, 1), // U+01f203 NEW_WIDTH(0x3210, 2), // U+01f210 NEW_WIDTH(0x323c, 1), // U+01f23c NEW_WIDTH(0x3240, 2), // U+01f240 NEW_WIDTH(0x3249, 1), // U+01f249 NEW_WIDTH(0x3250, 2), // U+01f250 NEW_WIDTH(0x3252, 1), // U+01f252 NEW_WIDTH(0x3260, 2), // U+01f260 NEW_WIDTH(0x3266, 1), // U+01f266 NEW_WIDTH(0x3300, 2), // U+01f300 NEW_WIDTH(0x3321, 1), // U+01f321 NEW_WIDTH(0x332d, 2), // U+01f32d NEW_WIDTH(0x3336, 1), // U+01f336 NEW_WIDTH(0x3337, 2), // U+01f337 NEW_WIDTH(0x337d, 1), // U+01f37d NEW_WIDTH(0x337e, 2), // U+01f37e NEW_WIDTH(0x3394, 1), // U+01f394 NEW_WIDTH(0x33a0, 2), // U+01f3a0 NEW_WIDTH(0x33cb, 1), // U+01f3cb NEW_WIDTH(0x33cf, 2), // U+01f3cf NEW_WIDTH(0x33d4, 1), // U+01f3d4 NEW_WIDTH(0x33e0, 2), // U+01f3e0 NEW_WIDTH(0x33f1, 1), // U+01f3f1 NEW_WIDTH(0x33f4, 2), // U+01f3f4 NEW_WIDTH(0x33f5, 1), // U+01f3f5 NEW_WIDTH(0x33f8, 2), // U+01f3f8 NEW_WIDTH(0x343f, 1), // U+01f43f NEW_WIDTH(0x3440, 2), // U+01f440 NEW_WIDTH(0x3441, 1), // U+01f441 NEW_WIDTH(0x3442, 2), // U+01f442 NEW_WIDTH(0x34fd, 1), // U+01f4fd NEW_WIDTH(0x34ff, 2), // U+01f4ff NEW_WIDTH(0x353e, 1), // U+01f53e NEW_WIDTH(0x354b, 2), // U+01f54b NEW_WIDTH(0x354f, 1), // U+01f54f NEW_WIDTH(0x3550, 2), // U+01f550 NEW_WIDTH(0x3568, 1), // U+01f568 NEW_WIDTH(0x357a, 2), // U+01f57a NEW_WIDTH(0x357b, 1), // U+01f57b NEW_WIDTH(0x3595, 2), // U+01f595 NEW_WIDTH(0x3597, 1), // U+01f597 NEW_WIDTH(0x35a4, 2), // U+01f5a4 NEW_WIDTH(0x35a5, 1), // U+01f5a5 NEW_WIDTH(0x35fb, 2), // U+01f5fb NEW_WIDTH(0x3650, 1), // U+01f650 NEW_WIDTH(0x3680, 2), // U+01f680 NEW_WIDTH(0x36c6, 1), // U+01f6c6 NEW_WIDTH(0x36cc, 2), // U+01f6cc NEW_WIDTH(0x36cd, 1), // U+01f6cd NEW_WIDTH(0x36d0, 2), // U+01f6d0 NEW_WIDTH(0x36d3, 1), // U+01f6d3 NEW_WIDTH(0x36eb, 2), // U+01f6eb NEW_WIDTH(0x36ed, 1), // U+01f6ed NEW_WIDTH(0x36f4, 2), // U+01f6f4 NEW_WIDTH(0x36fa, 1), // U+01f6fa NEW_WIDTH(0x3910, 2), // U+01f910 NEW_WIDTH(0x393f, 1), // U+01f93f NEW_WIDTH(0x3940, 2), // U+01f940 NEW_WIDTH(0x3971, 1), // U+01f971 NEW_WIDTH(0x3973, 2), // U+01f973 NEW_WIDTH(0x3977, 1), // U+01f977 NEW_WIDTH(0x397a, 2), // U+01f97a NEW_WIDTH(0x397b, 1), // U+01f97b NEW_WIDTH(0x397c, 2), // U+01f97c NEW_WIDTH(0x39a3, 1), // U+01f9a3 NEW_WIDTH(0x39b0, 2), // U+01f9b0 NEW_WIDTH(0x39ba, 1), // U+01f9ba NEW_WIDTH(0x39c0, 2), // U+01f9c0 NEW_WIDTH(0x39c3, 1), // U+01f9c3 NEW_WIDTH(0x39d0, 2), // U+01f9d0 NEW_WIDTH(0x3a00, 1), // U+01fa00 // U+020000, NEW_WIDTH(0x0000, 2), // U+020000 // U+024000, NEW_WIDTH(0x0000, 2), // U+024000 // U+028000, NEW_WIDTH(0x0000, 2), // U+028000 // U+02c000, NEW_WIDTH(0x0000, 2), // U+02c000 NEW_WIDTH(0x3ffe, 1), // U+02fffe // U+030000, NEW_WIDTH(0x0000, 2), // U+030000 // U+034000, NEW_WIDTH(0x0000, 2), // U+034000 // U+038000, NEW_WIDTH(0x0000, 2), // U+038000 // U+03c000, NEW_WIDTH(0x0000, 2), // U+03c000 NEW_WIDTH(0x3ffe, 1), // U+03fffe // U+040000, NEW_WIDTH(0x0000, 1), // U+040000 // U+044000, NEW_WIDTH(0x0000, 1), // U+044000 // U+048000, NEW_WIDTH(0x0000, 1), // U+048000 // U+04c000, NEW_WIDTH(0x0000, 1), // U+04c000 // U+050000, NEW_WIDTH(0x0000, 1), // U+050000 // U+054000, NEW_WIDTH(0x0000, 1), // U+054000 // U+058000, NEW_WIDTH(0x0000, 1), // U+058000 // U+05c000, NEW_WIDTH(0x0000, 1), // U+05c000 // U+060000, NEW_WIDTH(0x0000, 1), // U+060000 // U+064000, NEW_WIDTH(0x0000, 1), // U+064000 // U+068000, NEW_WIDTH(0x0000, 1), // U+068000 // U+06c000, NEW_WIDTH(0x0000, 1), // U+06c000 // U+070000, NEW_WIDTH(0x0000, 1), // U+070000 // U+074000, NEW_WIDTH(0x0000, 1), // U+074000 // U+078000, NEW_WIDTH(0x0000, 1), // U+078000 // U+07c000, NEW_WIDTH(0x0000, 1), // U+07c000 // U+080000, NEW_WIDTH(0x0000, 1), // U+080000 // U+084000, NEW_WIDTH(0x0000, 1), // U+084000 // U+088000, NEW_WIDTH(0x0000, 1), // U+088000 // U+08c000, NEW_WIDTH(0x0000, 1), // U+08c000 // U+090000, NEW_WIDTH(0x0000, 1), // U+090000 // U+094000, NEW_WIDTH(0x0000, 1), // U+094000 // U+098000, NEW_WIDTH(0x0000, 1), // U+098000 // U+09c000, NEW_WIDTH(0x0000, 1), // U+09c000 // U+0a0000, NEW_WIDTH(0x0000, 1), // U+0a0000 // U+0a4000, NEW_WIDTH(0x0000, 1), // U+0a4000 // U+0a8000, NEW_WIDTH(0x0000, 1), // U+0a8000 // U+0ac000, NEW_WIDTH(0x0000, 1), // U+0ac000 // U+0b0000, NEW_WIDTH(0x0000, 1), // U+0b0000 // U+0b4000, NEW_WIDTH(0x0000, 1), // U+0b4000 // U+0b8000, NEW_WIDTH(0x0000, 1), // U+0b8000 // U+0bc000, NEW_WIDTH(0x0000, 1), // U+0bc000 // U+0c0000, NEW_WIDTH(0x0000, 1), // U+0c0000 // U+0c4000, NEW_WIDTH(0x0000, 1), // U+0c4000 // U+0c8000, NEW_WIDTH(0x0000, 1), // U+0c8000 // U+0cc000, NEW_WIDTH(0x0000, 1), // U+0cc000 // U+0d0000, NEW_WIDTH(0x0000, 1), // U+0d0000 // U+0d4000, NEW_WIDTH(0x0000, 1), // U+0d4000 // U+0d8000, NEW_WIDTH(0x0000, 1), // U+0d8000 // U+0dc000, NEW_WIDTH(0x0000, 1), // U+0dc000 // U+0e0000, NEW_WIDTH(0x0000, 1), // U+0e0000 NEW_WIDTH(0x0001, 0), // U+0e0001 NEW_WIDTH(0x0002, 1), // U+0e0002 NEW_WIDTH(0x0020, 0), // U+0e0020 NEW_WIDTH(0x0080, 1), // U+0e0080 NEW_WIDTH(0x0100, 0), // U+0e0100 NEW_WIDTH(0x01f0, 1), // U+0e01f0 // U+0e4000, NEW_WIDTH(0x0000, 1), // U+0e4000 // U+0e8000, NEW_WIDTH(0x0000, 1), // U+0e8000 // U+0ec000, NEW_WIDTH(0x0000, 1), // U+0ec000 // U+0f0000, NEW_WIDTH(0x0000, 1), // U+0f0000 // U+0f4000, NEW_WIDTH(0x0000, 1), // U+0f4000 // U+0f8000, NEW_WIDTH(0x0000, 1), // U+0f8000 // U+0fc000, NEW_WIDTH(0x0000, 1), // U+0fc000 // U+100000, NEW_WIDTH(0x0000, 1), // U+100000 // U+104000, NEW_WIDTH(0x0000, 1), // U+104000 // U+108000, NEW_WIDTH(0x0000, 1), // U+108000 // U+10c000, NEW_WIDTH(0x0000, 1), // U+10c000 };termpaint-0.3.1/charclassification_konsole_2022.inc000066400000000000000000001165251477303547200222670ustar00rootroot00000000000000static const uint16_t termpaint_char_width_offsets_konsole_2022[0x45] = { 0, 454, 457, 534, 564, 759, 774, 798, 941, 942, 943, 944, 946, 947, 948, 949, 951, 952, 953, 954, 955, 956, 957, 958, 959, 960, 961, 962, 963, 964, 965, 966, 967, 968, 969, 970, 971, 972, 973, 974, 975, 976, 977, 978, 979, 980, 981, 982, 983, 984, 985, 986, 987, 988, 989, 990, 991, 998, 999, 1000, 1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008, 1009, }; static const uint16_t termpaint_char_width_data_konsole_2022[] = { // U+000000, NEW_WIDTH(0x0000, 1), // U+000000 NEW_WIDTH(0x0001, -1), // U+000001 NEW_WIDTH(0x0020, 1), // U+000020 NEW_WIDTH(0x007f, -1), // U+00007f NEW_WIDTH(0x00a0, 1), // U+0000a0 NEW_WIDTH(0x0300, 0), // U+000300 NEW_WIDTH(0x0370, 1), // U+000370 NEW_WIDTH(0x0483, 0), // U+000483 NEW_WIDTH(0x048a, 1), // U+00048a NEW_WIDTH(0x0591, 0), // U+000591 NEW_WIDTH(0x05be, 1), // U+0005be NEW_WIDTH(0x05bf, 0), // U+0005bf NEW_WIDTH(0x05c0, 1), // U+0005c0 NEW_WIDTH(0x05c1, 0), // U+0005c1 NEW_WIDTH(0x05c3, 1), // U+0005c3 NEW_WIDTH(0x05c4, 0), // U+0005c4 NEW_WIDTH(0x05c6, 1), // U+0005c6 NEW_WIDTH(0x05c7, 0), // U+0005c7 NEW_WIDTH(0x05c8, 1), // U+0005c8 NEW_WIDTH(0x0600, 0), // U+000600 NEW_WIDTH(0x0606, 1), // U+000606 NEW_WIDTH(0x0610, 0), // U+000610 NEW_WIDTH(0x061b, 1), // U+00061b NEW_WIDTH(0x061c, 0), // U+00061c NEW_WIDTH(0x061d, 1), // U+00061d NEW_WIDTH(0x064b, 0), // U+00064b NEW_WIDTH(0x0660, 1), // U+000660 NEW_WIDTH(0x0670, 0), // U+000670 NEW_WIDTH(0x0671, 1), // U+000671 NEW_WIDTH(0x06d6, 0), // U+0006d6 NEW_WIDTH(0x06de, 1), // U+0006de NEW_WIDTH(0x06df, 0), // U+0006df NEW_WIDTH(0x06e5, 1), // U+0006e5 NEW_WIDTH(0x06e7, 0), // U+0006e7 NEW_WIDTH(0x06e9, 1), // U+0006e9 NEW_WIDTH(0x06ea, 0), // U+0006ea NEW_WIDTH(0x06ee, 1), // U+0006ee NEW_WIDTH(0x070f, 0), // U+00070f NEW_WIDTH(0x0710, 1), // U+000710 NEW_WIDTH(0x0711, 0), // U+000711 NEW_WIDTH(0x0712, 1), // U+000712 NEW_WIDTH(0x0730, 0), // U+000730 NEW_WIDTH(0x074b, 1), // U+00074b NEW_WIDTH(0x07a6, 0), // U+0007a6 NEW_WIDTH(0x07b1, 1), // U+0007b1 NEW_WIDTH(0x07eb, 0), // U+0007eb NEW_WIDTH(0x07f4, 1), // U+0007f4 NEW_WIDTH(0x07fd, 0), // U+0007fd NEW_WIDTH(0x07fe, 1), // U+0007fe NEW_WIDTH(0x0816, 0), // U+000816 NEW_WIDTH(0x081a, 1), // U+00081a NEW_WIDTH(0x081b, 0), // U+00081b NEW_WIDTH(0x0824, 1), // U+000824 NEW_WIDTH(0x0825, 0), // U+000825 NEW_WIDTH(0x0828, 1), // U+000828 NEW_WIDTH(0x0829, 0), // U+000829 NEW_WIDTH(0x082e, 1), // U+00082e NEW_WIDTH(0x0859, 0), // U+000859 NEW_WIDTH(0x085c, 1), // U+00085c NEW_WIDTH(0x0890, 0), // U+000890 NEW_WIDTH(0x0892, 1), // U+000892 NEW_WIDTH(0x0898, 0), // U+000898 NEW_WIDTH(0x08a0, 1), // U+0008a0 NEW_WIDTH(0x08ca, 0), // U+0008ca NEW_WIDTH(0x0903, 1), // U+000903 NEW_WIDTH(0x093a, 0), // U+00093a NEW_WIDTH(0x093b, 1), // U+00093b NEW_WIDTH(0x093c, 0), // U+00093c NEW_WIDTH(0x093d, 1), // U+00093d NEW_WIDTH(0x0941, 0), // U+000941 NEW_WIDTH(0x0949, 1), // U+000949 NEW_WIDTH(0x094d, 0), // U+00094d NEW_WIDTH(0x094e, 1), // U+00094e NEW_WIDTH(0x0951, 0), // U+000951 NEW_WIDTH(0x0958, 1), // U+000958 NEW_WIDTH(0x0962, 0), // U+000962 NEW_WIDTH(0x0964, 1), // U+000964 NEW_WIDTH(0x0981, 0), // U+000981 NEW_WIDTH(0x0982, 1), // U+000982 NEW_WIDTH(0x09bc, 0), // U+0009bc NEW_WIDTH(0x09bd, 1), // U+0009bd NEW_WIDTH(0x09c1, 0), // U+0009c1 NEW_WIDTH(0x09c5, 1), // U+0009c5 NEW_WIDTH(0x09cd, 0), // U+0009cd NEW_WIDTH(0x09ce, 1), // U+0009ce NEW_WIDTH(0x09e2, 0), // U+0009e2 NEW_WIDTH(0x09e4, 1), // U+0009e4 NEW_WIDTH(0x09fe, 0), // U+0009fe NEW_WIDTH(0x09ff, 1), // U+0009ff NEW_WIDTH(0x0a01, 0), // U+000a01 NEW_WIDTH(0x0a03, 1), // U+000a03 NEW_WIDTH(0x0a3c, 0), // U+000a3c NEW_WIDTH(0x0a3d, 1), // U+000a3d NEW_WIDTH(0x0a41, 0), // U+000a41 NEW_WIDTH(0x0a43, 1), // U+000a43 NEW_WIDTH(0x0a47, 0), // U+000a47 NEW_WIDTH(0x0a49, 1), // U+000a49 NEW_WIDTH(0x0a4b, 0), // U+000a4b NEW_WIDTH(0x0a4e, 1), // U+000a4e NEW_WIDTH(0x0a51, 0), // U+000a51 NEW_WIDTH(0x0a52, 1), // U+000a52 NEW_WIDTH(0x0a70, 0), // U+000a70 NEW_WIDTH(0x0a72, 1), // U+000a72 NEW_WIDTH(0x0a75, 0), // U+000a75 NEW_WIDTH(0x0a76, 1), // U+000a76 NEW_WIDTH(0x0a81, 0), // U+000a81 NEW_WIDTH(0x0a83, 1), // U+000a83 NEW_WIDTH(0x0abc, 0), // U+000abc NEW_WIDTH(0x0abd, 1), // U+000abd NEW_WIDTH(0x0ac1, 0), // U+000ac1 NEW_WIDTH(0x0ac6, 1), // U+000ac6 NEW_WIDTH(0x0ac7, 0), // U+000ac7 NEW_WIDTH(0x0ac9, 1), // U+000ac9 NEW_WIDTH(0x0acd, 0), // U+000acd NEW_WIDTH(0x0ace, 1), // U+000ace NEW_WIDTH(0x0ae2, 0), // U+000ae2 NEW_WIDTH(0x0ae4, 1), // U+000ae4 NEW_WIDTH(0x0afa, 0), // U+000afa NEW_WIDTH(0x0b00, 1), // U+000b00 NEW_WIDTH(0x0b01, 0), // U+000b01 NEW_WIDTH(0x0b02, 1), // U+000b02 NEW_WIDTH(0x0b3c, 0), // U+000b3c NEW_WIDTH(0x0b3d, 1), // U+000b3d NEW_WIDTH(0x0b3f, 0), // U+000b3f NEW_WIDTH(0x0b40, 1), // U+000b40 NEW_WIDTH(0x0b41, 0), // U+000b41 NEW_WIDTH(0x0b45, 1), // U+000b45 NEW_WIDTH(0x0b4d, 0), // U+000b4d NEW_WIDTH(0x0b4e, 1), // U+000b4e NEW_WIDTH(0x0b55, 0), // U+000b55 NEW_WIDTH(0x0b57, 1), // U+000b57 NEW_WIDTH(0x0b62, 0), // U+000b62 NEW_WIDTH(0x0b64, 1), // U+000b64 NEW_WIDTH(0x0b82, 0), // U+000b82 NEW_WIDTH(0x0b83, 1), // U+000b83 NEW_WIDTH(0x0bc0, 0), // U+000bc0 NEW_WIDTH(0x0bc1, 1), // U+000bc1 NEW_WIDTH(0x0bcd, 0), // U+000bcd NEW_WIDTH(0x0bce, 1), // U+000bce NEW_WIDTH(0x0c00, 0), // U+000c00 NEW_WIDTH(0x0c01, 1), // U+000c01 NEW_WIDTH(0x0c04, 0), // U+000c04 NEW_WIDTH(0x0c05, 1), // U+000c05 NEW_WIDTH(0x0c3c, 0), // U+000c3c NEW_WIDTH(0x0c3d, 1), // U+000c3d NEW_WIDTH(0x0c3e, 0), // U+000c3e NEW_WIDTH(0x0c41, 1), // U+000c41 NEW_WIDTH(0x0c46, 0), // U+000c46 NEW_WIDTH(0x0c49, 1), // U+000c49 NEW_WIDTH(0x0c4a, 0), // U+000c4a NEW_WIDTH(0x0c4e, 1), // U+000c4e NEW_WIDTH(0x0c55, 0), // U+000c55 NEW_WIDTH(0x0c57, 1), // U+000c57 NEW_WIDTH(0x0c62, 0), // U+000c62 NEW_WIDTH(0x0c64, 1), // U+000c64 NEW_WIDTH(0x0c81, 0), // U+000c81 NEW_WIDTH(0x0c82, 1), // U+000c82 NEW_WIDTH(0x0cbc, 0), // U+000cbc NEW_WIDTH(0x0cbd, 1), // U+000cbd NEW_WIDTH(0x0cbf, 0), // U+000cbf NEW_WIDTH(0x0cc0, 1), // U+000cc0 NEW_WIDTH(0x0cc6, 0), // U+000cc6 NEW_WIDTH(0x0cc7, 1), // U+000cc7 NEW_WIDTH(0x0ccc, 0), // U+000ccc NEW_WIDTH(0x0cce, 1), // U+000cce NEW_WIDTH(0x0ce2, 0), // U+000ce2 NEW_WIDTH(0x0ce4, 1), // U+000ce4 NEW_WIDTH(0x0d00, 0), // U+000d00 NEW_WIDTH(0x0d02, 1), // U+000d02 NEW_WIDTH(0x0d3b, 0), // U+000d3b NEW_WIDTH(0x0d3d, 1), // U+000d3d NEW_WIDTH(0x0d41, 0), // U+000d41 NEW_WIDTH(0x0d45, 1), // U+000d45 NEW_WIDTH(0x0d4d, 0), // U+000d4d NEW_WIDTH(0x0d4e, 1), // U+000d4e NEW_WIDTH(0x0d62, 0), // U+000d62 NEW_WIDTH(0x0d64, 1), // U+000d64 NEW_WIDTH(0x0d81, 0), // U+000d81 NEW_WIDTH(0x0d82, 1), // U+000d82 NEW_WIDTH(0x0dca, 0), // U+000dca NEW_WIDTH(0x0dcb, 1), // U+000dcb NEW_WIDTH(0x0dd2, 0), // U+000dd2 NEW_WIDTH(0x0dd5, 1), // U+000dd5 NEW_WIDTH(0x0dd6, 0), // U+000dd6 NEW_WIDTH(0x0dd7, 1), // U+000dd7 NEW_WIDTH(0x0e31, 0), // U+000e31 NEW_WIDTH(0x0e32, 1), // U+000e32 NEW_WIDTH(0x0e34, 0), // U+000e34 NEW_WIDTH(0x0e3b, 1), // U+000e3b NEW_WIDTH(0x0e47, 0), // U+000e47 NEW_WIDTH(0x0e4f, 1), // U+000e4f NEW_WIDTH(0x0eb1, 0), // U+000eb1 NEW_WIDTH(0x0eb2, 1), // U+000eb2 NEW_WIDTH(0x0eb4, 0), // U+000eb4 NEW_WIDTH(0x0ebd, 1), // U+000ebd NEW_WIDTH(0x0ec8, 0), // U+000ec8 NEW_WIDTH(0x0ece, 1), // U+000ece NEW_WIDTH(0x0f18, 0), // U+000f18 NEW_WIDTH(0x0f1a, 1), // U+000f1a NEW_WIDTH(0x0f35, 0), // U+000f35 NEW_WIDTH(0x0f36, 1), // U+000f36 NEW_WIDTH(0x0f37, 0), // U+000f37 NEW_WIDTH(0x0f38, 1), // U+000f38 NEW_WIDTH(0x0f39, 0), // U+000f39 NEW_WIDTH(0x0f3a, 1), // U+000f3a NEW_WIDTH(0x0f71, 0), // U+000f71 NEW_WIDTH(0x0f7f, 1), // U+000f7f NEW_WIDTH(0x0f80, 0), // U+000f80 NEW_WIDTH(0x0f85, 1), // U+000f85 NEW_WIDTH(0x0f86, 0), // U+000f86 NEW_WIDTH(0x0f88, 1), // U+000f88 NEW_WIDTH(0x0f8d, 0), // U+000f8d NEW_WIDTH(0x0f98, 1), // U+000f98 NEW_WIDTH(0x0f99, 0), // U+000f99 NEW_WIDTH(0x0fbd, 1), // U+000fbd NEW_WIDTH(0x0fc6, 0), // U+000fc6 NEW_WIDTH(0x0fc7, 1), // U+000fc7 NEW_WIDTH(0x102d, 0), // U+00102d NEW_WIDTH(0x1031, 1), // U+001031 NEW_WIDTH(0x1032, 0), // U+001032 NEW_WIDTH(0x1038, 1), // U+001038 NEW_WIDTH(0x1039, 0), // U+001039 NEW_WIDTH(0x103b, 1), // U+00103b NEW_WIDTH(0x103d, 0), // U+00103d NEW_WIDTH(0x103f, 1), // U+00103f NEW_WIDTH(0x1058, 0), // U+001058 NEW_WIDTH(0x105a, 1), // U+00105a NEW_WIDTH(0x105e, 0), // U+00105e NEW_WIDTH(0x1061, 1), // U+001061 NEW_WIDTH(0x1071, 0), // U+001071 NEW_WIDTH(0x1075, 1), // U+001075 NEW_WIDTH(0x1082, 0), // U+001082 NEW_WIDTH(0x1083, 1), // U+001083 NEW_WIDTH(0x1085, 0), // U+001085 NEW_WIDTH(0x1087, 1), // U+001087 NEW_WIDTH(0x108d, 0), // U+00108d NEW_WIDTH(0x108e, 1), // U+00108e NEW_WIDTH(0x109d, 0), // U+00109d NEW_WIDTH(0x109e, 1), // U+00109e NEW_WIDTH(0x1100, 2), // U+001100 NEW_WIDTH(0x1160, 0), // U+001160 NEW_WIDTH(0x1200, 1), // U+001200 NEW_WIDTH(0x135d, 0), // U+00135d NEW_WIDTH(0x1360, 1), // U+001360 NEW_WIDTH(0x1712, 0), // U+001712 NEW_WIDTH(0x1715, 1), // U+001715 NEW_WIDTH(0x1732, 0), // U+001732 NEW_WIDTH(0x1734, 1), // U+001734 NEW_WIDTH(0x1752, 0), // U+001752 NEW_WIDTH(0x1754, 1), // U+001754 NEW_WIDTH(0x1772, 0), // U+001772 NEW_WIDTH(0x1774, 1), // U+001774 NEW_WIDTH(0x17b4, 0), // U+0017b4 NEW_WIDTH(0x17b6, 1), // U+0017b6 NEW_WIDTH(0x17b7, 0), // U+0017b7 NEW_WIDTH(0x17be, 1), // U+0017be NEW_WIDTH(0x17c6, 0), // U+0017c6 NEW_WIDTH(0x17c7, 1), // U+0017c7 NEW_WIDTH(0x17c9, 0), // U+0017c9 NEW_WIDTH(0x17d4, 1), // U+0017d4 NEW_WIDTH(0x17dd, 0), // U+0017dd NEW_WIDTH(0x17de, 1), // U+0017de NEW_WIDTH(0x180b, 0), // U+00180b NEW_WIDTH(0x1810, 1), // U+001810 NEW_WIDTH(0x1885, 0), // U+001885 NEW_WIDTH(0x1887, 1), // U+001887 NEW_WIDTH(0x18a9, 0), // U+0018a9 NEW_WIDTH(0x18aa, 1), // U+0018aa NEW_WIDTH(0x1920, 0), // U+001920 NEW_WIDTH(0x1923, 1), // U+001923 NEW_WIDTH(0x1927, 0), // U+001927 NEW_WIDTH(0x1929, 1), // U+001929 NEW_WIDTH(0x1932, 0), // U+001932 NEW_WIDTH(0x1933, 1), // U+001933 NEW_WIDTH(0x1939, 0), // U+001939 NEW_WIDTH(0x193c, 1), // U+00193c NEW_WIDTH(0x1a17, 0), // U+001a17 NEW_WIDTH(0x1a19, 1), // U+001a19 NEW_WIDTH(0x1a1b, 0), // U+001a1b NEW_WIDTH(0x1a1c, 1), // U+001a1c NEW_WIDTH(0x1a56, 0), // U+001a56 NEW_WIDTH(0x1a57, 1), // U+001a57 NEW_WIDTH(0x1a58, 0), // U+001a58 NEW_WIDTH(0x1a5f, 1), // U+001a5f NEW_WIDTH(0x1a60, 0), // U+001a60 NEW_WIDTH(0x1a61, 1), // U+001a61 NEW_WIDTH(0x1a62, 0), // U+001a62 NEW_WIDTH(0x1a63, 1), // U+001a63 NEW_WIDTH(0x1a65, 0), // U+001a65 NEW_WIDTH(0x1a6d, 1), // U+001a6d NEW_WIDTH(0x1a73, 0), // U+001a73 NEW_WIDTH(0x1a7d, 1), // U+001a7d NEW_WIDTH(0x1a7f, 0), // U+001a7f NEW_WIDTH(0x1a80, 1), // U+001a80 NEW_WIDTH(0x1ab0, 0), // U+001ab0 NEW_WIDTH(0x1acf, 1), // U+001acf NEW_WIDTH(0x1b00, 0), // U+001b00 NEW_WIDTH(0x1b04, 1), // U+001b04 NEW_WIDTH(0x1b34, 0), // U+001b34 NEW_WIDTH(0x1b35, 1), // U+001b35 NEW_WIDTH(0x1b36, 0), // U+001b36 NEW_WIDTH(0x1b3b, 1), // U+001b3b NEW_WIDTH(0x1b3c, 0), // U+001b3c NEW_WIDTH(0x1b3d, 1), // U+001b3d NEW_WIDTH(0x1b42, 0), // U+001b42 NEW_WIDTH(0x1b43, 1), // U+001b43 NEW_WIDTH(0x1b6b, 0), // U+001b6b NEW_WIDTH(0x1b74, 1), // U+001b74 NEW_WIDTH(0x1b80, 0), // U+001b80 NEW_WIDTH(0x1b82, 1), // U+001b82 NEW_WIDTH(0x1ba2, 0), // U+001ba2 NEW_WIDTH(0x1ba6, 1), // U+001ba6 NEW_WIDTH(0x1ba8, 0), // U+001ba8 NEW_WIDTH(0x1baa, 1), // U+001baa NEW_WIDTH(0x1bab, 0), // U+001bab NEW_WIDTH(0x1bae, 1), // U+001bae NEW_WIDTH(0x1be6, 0), // U+001be6 NEW_WIDTH(0x1be7, 1), // U+001be7 NEW_WIDTH(0x1be8, 0), // U+001be8 NEW_WIDTH(0x1bea, 1), // U+001bea NEW_WIDTH(0x1bed, 0), // U+001bed NEW_WIDTH(0x1bee, 1), // U+001bee NEW_WIDTH(0x1bef, 0), // U+001bef NEW_WIDTH(0x1bf2, 1), // U+001bf2 NEW_WIDTH(0x1c2c, 0), // U+001c2c NEW_WIDTH(0x1c34, 1), // U+001c34 NEW_WIDTH(0x1c36, 0), // U+001c36 NEW_WIDTH(0x1c38, 1), // U+001c38 NEW_WIDTH(0x1cd0, 0), // U+001cd0 NEW_WIDTH(0x1cd3, 1), // U+001cd3 NEW_WIDTH(0x1cd4, 0), // U+001cd4 NEW_WIDTH(0x1ce1, 1), // U+001ce1 NEW_WIDTH(0x1ce2, 0), // U+001ce2 NEW_WIDTH(0x1ce9, 1), // U+001ce9 NEW_WIDTH(0x1ced, 0), // U+001ced NEW_WIDTH(0x1cee, 1), // U+001cee NEW_WIDTH(0x1cf4, 0), // U+001cf4 NEW_WIDTH(0x1cf5, 1), // U+001cf5 NEW_WIDTH(0x1cf8, 0), // U+001cf8 NEW_WIDTH(0x1cfa, 1), // U+001cfa NEW_WIDTH(0x1dc0, 0), // U+001dc0 NEW_WIDTH(0x1e00, 1), // U+001e00 NEW_WIDTH(0x200b, 0), // U+00200b NEW_WIDTH(0x2010, 1), // U+002010 NEW_WIDTH(0x202a, 0), // U+00202a NEW_WIDTH(0x202f, 1), // U+00202f NEW_WIDTH(0x2060, 0), // U+002060 NEW_WIDTH(0x2065, 1), // U+002065 NEW_WIDTH(0x2066, 0), // U+002066 NEW_WIDTH(0x2070, 1), // U+002070 NEW_WIDTH(0x20d0, 0), // U+0020d0 NEW_WIDTH(0x20f1, 1), // U+0020f1 NEW_WIDTH(0x231a, 2), // U+00231a NEW_WIDTH(0x231c, 1), // U+00231c NEW_WIDTH(0x2329, 2), // U+002329 NEW_WIDTH(0x232b, 1), // U+00232b NEW_WIDTH(0x23e9, 2), // U+0023e9 NEW_WIDTH(0x23ed, 1), // U+0023ed NEW_WIDTH(0x23f0, 2), // U+0023f0 NEW_WIDTH(0x23f1, 1), // U+0023f1 NEW_WIDTH(0x23f3, 2), // U+0023f3 NEW_WIDTH(0x23f4, 1), // U+0023f4 NEW_WIDTH(0x25fd, 2), // U+0025fd NEW_WIDTH(0x25ff, 1), // U+0025ff NEW_WIDTH(0x2614, 2), // U+002614 NEW_WIDTH(0x2616, 1), // U+002616 NEW_WIDTH(0x2648, 2), // U+002648 NEW_WIDTH(0x2654, 1), // U+002654 NEW_WIDTH(0x267f, 2), // U+00267f NEW_WIDTH(0x2680, 1), // U+002680 NEW_WIDTH(0x2693, 2), // U+002693 NEW_WIDTH(0x2694, 1), // U+002694 NEW_WIDTH(0x26a1, 2), // U+0026a1 NEW_WIDTH(0x26a2, 1), // U+0026a2 NEW_WIDTH(0x26aa, 2), // U+0026aa NEW_WIDTH(0x26ac, 1), // U+0026ac NEW_WIDTH(0x26bd, 2), // U+0026bd NEW_WIDTH(0x26bf, 1), // U+0026bf NEW_WIDTH(0x26c4, 2), // U+0026c4 NEW_WIDTH(0x26c6, 1), // U+0026c6 NEW_WIDTH(0x26ce, 2), // U+0026ce NEW_WIDTH(0x26cf, 1), // U+0026cf NEW_WIDTH(0x26d4, 2), // U+0026d4 NEW_WIDTH(0x26d5, 1), // U+0026d5 NEW_WIDTH(0x26ea, 2), // U+0026ea NEW_WIDTH(0x26eb, 1), // U+0026eb NEW_WIDTH(0x26f2, 2), // U+0026f2 NEW_WIDTH(0x26f4, 1), // U+0026f4 NEW_WIDTH(0x26f5, 2), // U+0026f5 NEW_WIDTH(0x26f6, 1), // U+0026f6 NEW_WIDTH(0x26fa, 2), // U+0026fa NEW_WIDTH(0x26fb, 1), // U+0026fb NEW_WIDTH(0x26fd, 2), // U+0026fd NEW_WIDTH(0x26fe, 1), // U+0026fe NEW_WIDTH(0x2705, 2), // U+002705 NEW_WIDTH(0x2706, 1), // U+002706 NEW_WIDTH(0x270a, 2), // U+00270a NEW_WIDTH(0x270c, 1), // U+00270c NEW_WIDTH(0x2728, 2), // U+002728 NEW_WIDTH(0x2729, 1), // U+002729 NEW_WIDTH(0x274c, 2), // U+00274c NEW_WIDTH(0x274d, 1), // U+00274d NEW_WIDTH(0x274e, 2), // U+00274e NEW_WIDTH(0x274f, 1), // U+00274f NEW_WIDTH(0x2753, 2), // U+002753 NEW_WIDTH(0x2756, 1), // U+002756 NEW_WIDTH(0x2757, 2), // U+002757 NEW_WIDTH(0x2758, 1), // U+002758 NEW_WIDTH(0x2795, 2), // U+002795 NEW_WIDTH(0x2798, 1), // U+002798 NEW_WIDTH(0x27b0, 2), // U+0027b0 NEW_WIDTH(0x27b1, 1), // U+0027b1 NEW_WIDTH(0x27bf, 2), // U+0027bf NEW_WIDTH(0x27c0, 1), // U+0027c0 NEW_WIDTH(0x2b1b, 2), // U+002b1b NEW_WIDTH(0x2b1d, 1), // U+002b1d NEW_WIDTH(0x2b50, 2), // U+002b50 NEW_WIDTH(0x2b51, 1), // U+002b51 NEW_WIDTH(0x2b55, 2), // U+002b55 NEW_WIDTH(0x2b56, 1), // U+002b56 NEW_WIDTH(0x2cef, 0), // U+002cef NEW_WIDTH(0x2cf2, 1), // U+002cf2 NEW_WIDTH(0x2d7f, 0), // U+002d7f NEW_WIDTH(0x2d80, 1), // U+002d80 NEW_WIDTH(0x2de0, 0), // U+002de0 NEW_WIDTH(0x2e00, 1), // U+002e00 NEW_WIDTH(0x2e80, 2), // U+002e80 NEW_WIDTH(0x2e9a, 1), // U+002e9a NEW_WIDTH(0x2e9b, 2), // U+002e9b NEW_WIDTH(0x2ef4, 1), // U+002ef4 NEW_WIDTH(0x2f00, 2), // U+002f00 NEW_WIDTH(0x2fd6, 1), // U+002fd6 NEW_WIDTH(0x2ff0, 2), // U+002ff0 NEW_WIDTH(0x2ffc, 1), // U+002ffc NEW_WIDTH(0x3000, 2), // U+003000 NEW_WIDTH(0x302a, 0), // U+00302a NEW_WIDTH(0x302e, 2), // U+00302e NEW_WIDTH(0x303f, 1), // U+00303f NEW_WIDTH(0x3041, 2), // U+003041 NEW_WIDTH(0x3097, 1), // U+003097 NEW_WIDTH(0x3099, 0), // U+003099 NEW_WIDTH(0x309b, 2), // U+00309b NEW_WIDTH(0x3100, 1), // U+003100 NEW_WIDTH(0x3105, 2), // U+003105 NEW_WIDTH(0x3130, 1), // U+003130 NEW_WIDTH(0x3131, 2), // U+003131 NEW_WIDTH(0x318f, 1), // U+00318f NEW_WIDTH(0x3190, 2), // U+003190 NEW_WIDTH(0x31e4, 1), // U+0031e4 NEW_WIDTH(0x31f0, 2), // U+0031f0 NEW_WIDTH(0x321f, 1), // U+00321f NEW_WIDTH(0x3220, 2), // U+003220 NEW_WIDTH(0x3248, 1), // U+003248 NEW_WIDTH(0x3250, 2), // U+003250 // U+004000, NEW_WIDTH(0x0000, 2), // U+004000 NEW_WIDTH(0x0dc0, 1), // U+004dc0 NEW_WIDTH(0x0e00, 2), // U+004e00 // U+008000, NEW_WIDTH(0x0000, 2), // U+008000 NEW_WIDTH(0x248d, 1), // U+00a48d NEW_WIDTH(0x2490, 2), // U+00a490 NEW_WIDTH(0x24c7, 1), // U+00a4c7 NEW_WIDTH(0x266f, 0), // U+00a66f NEW_WIDTH(0x2673, 1), // U+00a673 NEW_WIDTH(0x2674, 0), // U+00a674 NEW_WIDTH(0x267e, 1), // U+00a67e NEW_WIDTH(0x269e, 0), // U+00a69e NEW_WIDTH(0x26a0, 1), // U+00a6a0 NEW_WIDTH(0x26f0, 0), // U+00a6f0 NEW_WIDTH(0x26f2, 1), // U+00a6f2 NEW_WIDTH(0x2802, 0), // U+00a802 NEW_WIDTH(0x2803, 1), // U+00a803 NEW_WIDTH(0x2806, 0), // U+00a806 NEW_WIDTH(0x2807, 1), // U+00a807 NEW_WIDTH(0x280b, 0), // U+00a80b NEW_WIDTH(0x280c, 1), // U+00a80c NEW_WIDTH(0x2825, 0), // U+00a825 NEW_WIDTH(0x2827, 1), // U+00a827 NEW_WIDTH(0x282c, 0), // U+00a82c NEW_WIDTH(0x282d, 1), // U+00a82d NEW_WIDTH(0x28c4, 0), // U+00a8c4 NEW_WIDTH(0x28c6, 1), // U+00a8c6 NEW_WIDTH(0x28e0, 0), // U+00a8e0 NEW_WIDTH(0x28f2, 1), // U+00a8f2 NEW_WIDTH(0x28ff, 0), // U+00a8ff NEW_WIDTH(0x2900, 1), // U+00a900 NEW_WIDTH(0x2926, 0), // U+00a926 NEW_WIDTH(0x292e, 1), // U+00a92e NEW_WIDTH(0x2947, 0), // U+00a947 NEW_WIDTH(0x2952, 1), // U+00a952 NEW_WIDTH(0x2960, 2), // U+00a960 NEW_WIDTH(0x297d, 1), // U+00a97d NEW_WIDTH(0x2980, 0), // U+00a980 NEW_WIDTH(0x2983, 1), // U+00a983 NEW_WIDTH(0x29b3, 0), // U+00a9b3 NEW_WIDTH(0x29b4, 1), // U+00a9b4 NEW_WIDTH(0x29b6, 0), // U+00a9b6 NEW_WIDTH(0x29ba, 1), // U+00a9ba NEW_WIDTH(0x29bc, 0), // U+00a9bc NEW_WIDTH(0x29be, 1), // U+00a9be NEW_WIDTH(0x29e5, 0), // U+00a9e5 NEW_WIDTH(0x29e6, 1), // U+00a9e6 NEW_WIDTH(0x2a29, 0), // U+00aa29 NEW_WIDTH(0x2a2f, 1), // U+00aa2f NEW_WIDTH(0x2a31, 0), // U+00aa31 NEW_WIDTH(0x2a33, 1), // U+00aa33 NEW_WIDTH(0x2a35, 0), // U+00aa35 NEW_WIDTH(0x2a37, 1), // U+00aa37 NEW_WIDTH(0x2a43, 0), // U+00aa43 NEW_WIDTH(0x2a44, 1), // U+00aa44 NEW_WIDTH(0x2a4c, 0), // U+00aa4c NEW_WIDTH(0x2a4d, 1), // U+00aa4d NEW_WIDTH(0x2a7c, 0), // U+00aa7c NEW_WIDTH(0x2a7d, 1), // U+00aa7d NEW_WIDTH(0x2ab0, 0), // U+00aab0 NEW_WIDTH(0x2ab1, 1), // U+00aab1 NEW_WIDTH(0x2ab2, 0), // U+00aab2 NEW_WIDTH(0x2ab5, 1), // U+00aab5 NEW_WIDTH(0x2ab7, 0), // U+00aab7 NEW_WIDTH(0x2ab9, 1), // U+00aab9 NEW_WIDTH(0x2abe, 0), // U+00aabe NEW_WIDTH(0x2ac0, 1), // U+00aac0 NEW_WIDTH(0x2ac1, 0), // U+00aac1 NEW_WIDTH(0x2ac2, 1), // U+00aac2 NEW_WIDTH(0x2aec, 0), // U+00aaec NEW_WIDTH(0x2aee, 1), // U+00aaee NEW_WIDTH(0x2af6, 0), // U+00aaf6 NEW_WIDTH(0x2af7, 1), // U+00aaf7 NEW_WIDTH(0x2be5, 0), // U+00abe5 NEW_WIDTH(0x2be6, 1), // U+00abe6 NEW_WIDTH(0x2be8, 0), // U+00abe8 NEW_WIDTH(0x2be9, 1), // U+00abe9 NEW_WIDTH(0x2bed, 0), // U+00abed NEW_WIDTH(0x2bee, 1), // U+00abee NEW_WIDTH(0x2c00, 2), // U+00ac00 // U+00c000, NEW_WIDTH(0x0000, 2), // U+00c000 NEW_WIDTH(0x17a4, 1), // U+00d7a4 NEW_WIDTH(0x17b0, 0), // U+00d7b0 NEW_WIDTH(0x17c7, 1), // U+00d7c7 NEW_WIDTH(0x17cb, 0), // U+00d7cb NEW_WIDTH(0x17fc, 1), // U+00d7fc NEW_WIDTH(0x1800, -1), // U+00d800 NEW_WIDTH(0x2000, 1), // U+00e000 NEW_WIDTH(0x3900, 2), // U+00f900 NEW_WIDTH(0x3b00, 1), // U+00fb00 NEW_WIDTH(0x3b1e, 0), // U+00fb1e NEW_WIDTH(0x3b1f, 1), // U+00fb1f NEW_WIDTH(0x3e00, 0), // U+00fe00 NEW_WIDTH(0x3e10, 2), // U+00fe10 NEW_WIDTH(0x3e1a, 1), // U+00fe1a NEW_WIDTH(0x3e20, 0), // U+00fe20 NEW_WIDTH(0x3e30, 2), // U+00fe30 NEW_WIDTH(0x3e53, 1), // U+00fe53 NEW_WIDTH(0x3e54, 2), // U+00fe54 NEW_WIDTH(0x3e67, 1), // U+00fe67 NEW_WIDTH(0x3e68, 2), // U+00fe68 NEW_WIDTH(0x3e6c, 1), // U+00fe6c NEW_WIDTH(0x3eff, 0), // U+00feff NEW_WIDTH(0x3f00, 1), // U+00ff00 NEW_WIDTH(0x3f01, 2), // U+00ff01 NEW_WIDTH(0x3f61, 1), // U+00ff61 NEW_WIDTH(0x3fe0, 2), // U+00ffe0 NEW_WIDTH(0x3fe7, 1), // U+00ffe7 NEW_WIDTH(0x3ff9, 0), // U+00fff9 NEW_WIDTH(0x3ffc, 1), // U+00fffc // U+010000, NEW_WIDTH(0x0000, 1), // U+010000 NEW_WIDTH(0x01fd, 0), // U+0101fd NEW_WIDTH(0x01fe, 1), // U+0101fe NEW_WIDTH(0x02e0, 0), // U+0102e0 NEW_WIDTH(0x02e1, 1), // U+0102e1 NEW_WIDTH(0x0376, 0), // U+010376 NEW_WIDTH(0x037b, 1), // U+01037b NEW_WIDTH(0x0a01, 0), // U+010a01 NEW_WIDTH(0x0a04, 1), // U+010a04 NEW_WIDTH(0x0a05, 0), // U+010a05 NEW_WIDTH(0x0a07, 1), // U+010a07 NEW_WIDTH(0x0a0c, 0), // U+010a0c NEW_WIDTH(0x0a10, 1), // U+010a10 NEW_WIDTH(0x0a38, 0), // U+010a38 NEW_WIDTH(0x0a3b, 1), // U+010a3b NEW_WIDTH(0x0a3f, 0), // U+010a3f NEW_WIDTH(0x0a40, 1), // U+010a40 NEW_WIDTH(0x0ae5, 0), // U+010ae5 NEW_WIDTH(0x0ae7, 1), // U+010ae7 NEW_WIDTH(0x0d24, 0), // U+010d24 NEW_WIDTH(0x0d28, 1), // U+010d28 NEW_WIDTH(0x0eab, 0), // U+010eab NEW_WIDTH(0x0ead, 1), // U+010ead NEW_WIDTH(0x0f46, 0), // U+010f46 NEW_WIDTH(0x0f51, 1), // U+010f51 NEW_WIDTH(0x0f82, 0), // U+010f82 NEW_WIDTH(0x0f86, 1), // U+010f86 NEW_WIDTH(0x1001, 0), // U+011001 NEW_WIDTH(0x1002, 1), // U+011002 NEW_WIDTH(0x1038, 0), // U+011038 NEW_WIDTH(0x1047, 1), // U+011047 NEW_WIDTH(0x1070, 0), // U+011070 NEW_WIDTH(0x1071, 1), // U+011071 NEW_WIDTH(0x1073, 0), // U+011073 NEW_WIDTH(0x1075, 1), // U+011075 NEW_WIDTH(0x107f, 0), // U+01107f NEW_WIDTH(0x1082, 1), // U+011082 NEW_WIDTH(0x10b3, 0), // U+0110b3 NEW_WIDTH(0x10b7, 1), // U+0110b7 NEW_WIDTH(0x10b9, 0), // U+0110b9 NEW_WIDTH(0x10bb, 1), // U+0110bb NEW_WIDTH(0x10bd, 0), // U+0110bd NEW_WIDTH(0x10be, 1), // U+0110be NEW_WIDTH(0x10c2, 0), // U+0110c2 NEW_WIDTH(0x10c3, 1), // U+0110c3 NEW_WIDTH(0x10cd, 0), // U+0110cd NEW_WIDTH(0x10ce, 1), // U+0110ce NEW_WIDTH(0x1100, 0), // U+011100 NEW_WIDTH(0x1103, 1), // U+011103 NEW_WIDTH(0x1127, 0), // U+011127 NEW_WIDTH(0x112c, 1), // U+01112c NEW_WIDTH(0x112d, 0), // U+01112d NEW_WIDTH(0x1135, 1), // U+011135 NEW_WIDTH(0x1173, 0), // U+011173 NEW_WIDTH(0x1174, 1), // U+011174 NEW_WIDTH(0x1180, 0), // U+011180 NEW_WIDTH(0x1182, 1), // U+011182 NEW_WIDTH(0x11b6, 0), // U+0111b6 NEW_WIDTH(0x11bf, 1), // U+0111bf NEW_WIDTH(0x11c9, 0), // U+0111c9 NEW_WIDTH(0x11cd, 1), // U+0111cd NEW_WIDTH(0x11cf, 0), // U+0111cf NEW_WIDTH(0x11d0, 1), // U+0111d0 NEW_WIDTH(0x122f, 0), // U+01122f NEW_WIDTH(0x1232, 1), // U+011232 NEW_WIDTH(0x1234, 0), // U+011234 NEW_WIDTH(0x1235, 1), // U+011235 NEW_WIDTH(0x1236, 0), // U+011236 NEW_WIDTH(0x1238, 1), // U+011238 NEW_WIDTH(0x123e, 0), // U+01123e NEW_WIDTH(0x123f, 1), // U+01123f NEW_WIDTH(0x12df, 0), // U+0112df NEW_WIDTH(0x12e0, 1), // U+0112e0 NEW_WIDTH(0x12e3, 0), // U+0112e3 NEW_WIDTH(0x12eb, 1), // U+0112eb NEW_WIDTH(0x1300, 0), // U+011300 NEW_WIDTH(0x1302, 1), // U+011302 NEW_WIDTH(0x133b, 0), // U+01133b NEW_WIDTH(0x133d, 1), // U+01133d NEW_WIDTH(0x1340, 0), // U+011340 NEW_WIDTH(0x1341, 1), // U+011341 NEW_WIDTH(0x1366, 0), // U+011366 NEW_WIDTH(0x136d, 1), // U+01136d NEW_WIDTH(0x1370, 0), // U+011370 NEW_WIDTH(0x1375, 1), // U+011375 NEW_WIDTH(0x1438, 0), // U+011438 NEW_WIDTH(0x1440, 1), // U+011440 NEW_WIDTH(0x1442, 0), // U+011442 NEW_WIDTH(0x1445, 1), // U+011445 NEW_WIDTH(0x1446, 0), // U+011446 NEW_WIDTH(0x1447, 1), // U+011447 NEW_WIDTH(0x145e, 0), // U+01145e NEW_WIDTH(0x145f, 1), // U+01145f NEW_WIDTH(0x14b3, 0), // U+0114b3 NEW_WIDTH(0x14b9, 1), // U+0114b9 NEW_WIDTH(0x14ba, 0), // U+0114ba NEW_WIDTH(0x14bb, 1), // U+0114bb NEW_WIDTH(0x14bf, 0), // U+0114bf NEW_WIDTH(0x14c1, 1), // U+0114c1 NEW_WIDTH(0x14c2, 0), // U+0114c2 NEW_WIDTH(0x14c4, 1), // U+0114c4 NEW_WIDTH(0x15b2, 0), // U+0115b2 NEW_WIDTH(0x15b6, 1), // U+0115b6 NEW_WIDTH(0x15bc, 0), // U+0115bc NEW_WIDTH(0x15be, 1), // U+0115be NEW_WIDTH(0x15bf, 0), // U+0115bf NEW_WIDTH(0x15c1, 1), // U+0115c1 NEW_WIDTH(0x15dc, 0), // U+0115dc NEW_WIDTH(0x15de, 1), // U+0115de NEW_WIDTH(0x1633, 0), // U+011633 NEW_WIDTH(0x163b, 1), // U+01163b NEW_WIDTH(0x163d, 0), // U+01163d NEW_WIDTH(0x163e, 1), // U+01163e NEW_WIDTH(0x163f, 0), // U+01163f NEW_WIDTH(0x1641, 1), // U+011641 NEW_WIDTH(0x16ab, 0), // U+0116ab NEW_WIDTH(0x16ac, 1), // U+0116ac NEW_WIDTH(0x16ad, 0), // U+0116ad NEW_WIDTH(0x16ae, 1), // U+0116ae NEW_WIDTH(0x16b0, 0), // U+0116b0 NEW_WIDTH(0x16b6, 1), // U+0116b6 NEW_WIDTH(0x16b7, 0), // U+0116b7 NEW_WIDTH(0x16b8, 1), // U+0116b8 NEW_WIDTH(0x171d, 0), // U+01171d NEW_WIDTH(0x1720, 1), // U+011720 NEW_WIDTH(0x1722, 0), // U+011722 NEW_WIDTH(0x1726, 1), // U+011726 NEW_WIDTH(0x1727, 0), // U+011727 NEW_WIDTH(0x172c, 1), // U+01172c NEW_WIDTH(0x182f, 0), // U+01182f NEW_WIDTH(0x1838, 1), // U+011838 NEW_WIDTH(0x1839, 0), // U+011839 NEW_WIDTH(0x183b, 1), // U+01183b NEW_WIDTH(0x193b, 0), // U+01193b NEW_WIDTH(0x193d, 1), // U+01193d NEW_WIDTH(0x193e, 0), // U+01193e NEW_WIDTH(0x193f, 1), // U+01193f NEW_WIDTH(0x1943, 0), // U+011943 NEW_WIDTH(0x1944, 1), // U+011944 NEW_WIDTH(0x19d4, 0), // U+0119d4 NEW_WIDTH(0x19d8, 1), // U+0119d8 NEW_WIDTH(0x19da, 0), // U+0119da NEW_WIDTH(0x19dc, 1), // U+0119dc NEW_WIDTH(0x19e0, 0), // U+0119e0 NEW_WIDTH(0x19e1, 1), // U+0119e1 NEW_WIDTH(0x1a01, 0), // U+011a01 NEW_WIDTH(0x1a0b, 1), // U+011a0b NEW_WIDTH(0x1a33, 0), // U+011a33 NEW_WIDTH(0x1a39, 1), // U+011a39 NEW_WIDTH(0x1a3b, 0), // U+011a3b NEW_WIDTH(0x1a3f, 1), // U+011a3f NEW_WIDTH(0x1a47, 0), // U+011a47 NEW_WIDTH(0x1a48, 1), // U+011a48 NEW_WIDTH(0x1a51, 0), // U+011a51 NEW_WIDTH(0x1a57, 1), // U+011a57 NEW_WIDTH(0x1a59, 0), // U+011a59 NEW_WIDTH(0x1a5c, 1), // U+011a5c NEW_WIDTH(0x1a8a, 0), // U+011a8a NEW_WIDTH(0x1a97, 1), // U+011a97 NEW_WIDTH(0x1a98, 0), // U+011a98 NEW_WIDTH(0x1a9a, 1), // U+011a9a NEW_WIDTH(0x1c30, 0), // U+011c30 NEW_WIDTH(0x1c37, 1), // U+011c37 NEW_WIDTH(0x1c38, 0), // U+011c38 NEW_WIDTH(0x1c3e, 1), // U+011c3e NEW_WIDTH(0x1c3f, 0), // U+011c3f NEW_WIDTH(0x1c40, 1), // U+011c40 NEW_WIDTH(0x1c92, 0), // U+011c92 NEW_WIDTH(0x1ca8, 1), // U+011ca8 NEW_WIDTH(0x1caa, 0), // U+011caa NEW_WIDTH(0x1cb1, 1), // U+011cb1 NEW_WIDTH(0x1cb2, 0), // U+011cb2 NEW_WIDTH(0x1cb4, 1), // U+011cb4 NEW_WIDTH(0x1cb5, 0), // U+011cb5 NEW_WIDTH(0x1cb7, 1), // U+011cb7 NEW_WIDTH(0x1d31, 0), // U+011d31 NEW_WIDTH(0x1d37, 1), // U+011d37 NEW_WIDTH(0x1d3a, 0), // U+011d3a NEW_WIDTH(0x1d3b, 1), // U+011d3b NEW_WIDTH(0x1d3c, 0), // U+011d3c NEW_WIDTH(0x1d3e, 1), // U+011d3e NEW_WIDTH(0x1d3f, 0), // U+011d3f NEW_WIDTH(0x1d46, 1), // U+011d46 NEW_WIDTH(0x1d47, 0), // U+011d47 NEW_WIDTH(0x1d48, 1), // U+011d48 NEW_WIDTH(0x1d90, 0), // U+011d90 NEW_WIDTH(0x1d92, 1), // U+011d92 NEW_WIDTH(0x1d95, 0), // U+011d95 NEW_WIDTH(0x1d96, 1), // U+011d96 NEW_WIDTH(0x1d97, 0), // U+011d97 NEW_WIDTH(0x1d98, 1), // U+011d98 NEW_WIDTH(0x1ef3, 0), // U+011ef3 NEW_WIDTH(0x1ef5, 1), // U+011ef5 NEW_WIDTH(0x3430, 0), // U+013430 NEW_WIDTH(0x3439, 1), // U+013439 // U+014000, NEW_WIDTH(0x0000, 1), // U+014000 NEW_WIDTH(0x2af0, 0), // U+016af0 NEW_WIDTH(0x2af5, 1), // U+016af5 NEW_WIDTH(0x2b30, 0), // U+016b30 NEW_WIDTH(0x2b37, 1), // U+016b37 NEW_WIDTH(0x2f4f, 0), // U+016f4f NEW_WIDTH(0x2f50, 1), // U+016f50 NEW_WIDTH(0x2f8f, 0), // U+016f8f NEW_WIDTH(0x2f93, 1), // U+016f93 NEW_WIDTH(0x2fe0, 2), // U+016fe0 NEW_WIDTH(0x2fe4, 0), // U+016fe4 NEW_WIDTH(0x2fe5, 1), // U+016fe5 NEW_WIDTH(0x2ff0, 2), // U+016ff0 NEW_WIDTH(0x2ff2, 1), // U+016ff2 NEW_WIDTH(0x3000, 2), // U+017000 // U+018000, NEW_WIDTH(0x0000, 2), // U+018000 NEW_WIDTH(0x07f8, 1), // U+0187f8 NEW_WIDTH(0x0800, 2), // U+018800 NEW_WIDTH(0x0cd6, 1), // U+018cd6 NEW_WIDTH(0x0d00, 2), // U+018d00 NEW_WIDTH(0x0d09, 1), // U+018d09 NEW_WIDTH(0x2ff0, 2), // U+01aff0 NEW_WIDTH(0x2ff4, 1), // U+01aff4 NEW_WIDTH(0x2ff5, 2), // U+01aff5 NEW_WIDTH(0x2ffc, 1), // U+01affc NEW_WIDTH(0x2ffd, 2), // U+01affd NEW_WIDTH(0x2fff, 1), // U+01afff NEW_WIDTH(0x3000, 2), // U+01b000 NEW_WIDTH(0x3123, 1), // U+01b123 NEW_WIDTH(0x3150, 2), // U+01b150 NEW_WIDTH(0x3153, 1), // U+01b153 NEW_WIDTH(0x3164, 2), // U+01b164 NEW_WIDTH(0x3168, 1), // U+01b168 NEW_WIDTH(0x3170, 2), // U+01b170 NEW_WIDTH(0x32fc, 1), // U+01b2fc NEW_WIDTH(0x3c9d, 0), // U+01bc9d NEW_WIDTH(0x3c9f, 1), // U+01bc9f NEW_WIDTH(0x3ca0, 0), // U+01bca0 NEW_WIDTH(0x3ca4, 1), // U+01bca4 // U+01c000, NEW_WIDTH(0x0000, 1), // U+01c000 NEW_WIDTH(0x0f00, 0), // U+01cf00 NEW_WIDTH(0x0f2e, 1), // U+01cf2e NEW_WIDTH(0x0f30, 0), // U+01cf30 NEW_WIDTH(0x0f47, 1), // U+01cf47 NEW_WIDTH(0x1167, 0), // U+01d167 NEW_WIDTH(0x116a, 1), // U+01d16a NEW_WIDTH(0x1173, 0), // U+01d173 NEW_WIDTH(0x1183, 1), // U+01d183 NEW_WIDTH(0x1185, 0), // U+01d185 NEW_WIDTH(0x118c, 1), // U+01d18c NEW_WIDTH(0x11aa, 0), // U+01d1aa NEW_WIDTH(0x11ae, 1), // U+01d1ae NEW_WIDTH(0x1242, 0), // U+01d242 NEW_WIDTH(0x1245, 1), // U+01d245 NEW_WIDTH(0x1a00, 0), // U+01da00 NEW_WIDTH(0x1a37, 1), // U+01da37 NEW_WIDTH(0x1a3b, 0), // U+01da3b NEW_WIDTH(0x1a6d, 1), // U+01da6d NEW_WIDTH(0x1a75, 0), // U+01da75 NEW_WIDTH(0x1a76, 1), // U+01da76 NEW_WIDTH(0x1a84, 0), // U+01da84 NEW_WIDTH(0x1a85, 1), // U+01da85 NEW_WIDTH(0x1a9b, 0), // U+01da9b NEW_WIDTH(0x1aa0, 1), // U+01daa0 NEW_WIDTH(0x1aa1, 0), // U+01daa1 NEW_WIDTH(0x1ab0, 1), // U+01dab0 NEW_WIDTH(0x2000, 0), // U+01e000 NEW_WIDTH(0x2007, 1), // U+01e007 NEW_WIDTH(0x2008, 0), // U+01e008 NEW_WIDTH(0x2019, 1), // U+01e019 NEW_WIDTH(0x201b, 0), // U+01e01b NEW_WIDTH(0x2022, 1), // U+01e022 NEW_WIDTH(0x2023, 0), // U+01e023 NEW_WIDTH(0x2025, 1), // U+01e025 NEW_WIDTH(0x2026, 0), // U+01e026 NEW_WIDTH(0x202b, 1), // U+01e02b NEW_WIDTH(0x2130, 0), // U+01e130 NEW_WIDTH(0x2137, 1), // U+01e137 NEW_WIDTH(0x22ae, 0), // U+01e2ae NEW_WIDTH(0x22af, 1), // U+01e2af NEW_WIDTH(0x22ec, 0), // U+01e2ec NEW_WIDTH(0x22f0, 1), // U+01e2f0 NEW_WIDTH(0x28d0, 0), // U+01e8d0 NEW_WIDTH(0x28d7, 1), // U+01e8d7 NEW_WIDTH(0x2944, 0), // U+01e944 NEW_WIDTH(0x294b, 1), // U+01e94b NEW_WIDTH(0x3004, 2), // U+01f004 NEW_WIDTH(0x3005, 1), // U+01f005 NEW_WIDTH(0x30cf, 2), // U+01f0cf NEW_WIDTH(0x30d0, 1), // U+01f0d0 NEW_WIDTH(0x318e, 2), // U+01f18e NEW_WIDTH(0x318f, 1), // U+01f18f NEW_WIDTH(0x3191, 2), // U+01f191 NEW_WIDTH(0x319b, 1), // U+01f19b NEW_WIDTH(0x31e6, 2), // U+01f1e6 NEW_WIDTH(0x3203, 1), // U+01f203 NEW_WIDTH(0x3210, 2), // U+01f210 NEW_WIDTH(0x323c, 1), // U+01f23c NEW_WIDTH(0x3240, 2), // U+01f240 NEW_WIDTH(0x3249, 1), // U+01f249 NEW_WIDTH(0x3250, 2), // U+01f250 NEW_WIDTH(0x3252, 1), // U+01f252 NEW_WIDTH(0x3260, 2), // U+01f260 NEW_WIDTH(0x3266, 1), // U+01f266 NEW_WIDTH(0x3300, 2), // U+01f300 NEW_WIDTH(0x3321, 1), // U+01f321 NEW_WIDTH(0x332d, 2), // U+01f32d NEW_WIDTH(0x3336, 1), // U+01f336 NEW_WIDTH(0x3337, 2), // U+01f337 NEW_WIDTH(0x337d, 1), // U+01f37d NEW_WIDTH(0x337e, 2), // U+01f37e NEW_WIDTH(0x3394, 1), // U+01f394 NEW_WIDTH(0x33a0, 2), // U+01f3a0 NEW_WIDTH(0x33cb, 1), // U+01f3cb NEW_WIDTH(0x33cf, 2), // U+01f3cf NEW_WIDTH(0x33d4, 1), // U+01f3d4 NEW_WIDTH(0x33e0, 2), // U+01f3e0 NEW_WIDTH(0x33f1, 1), // U+01f3f1 NEW_WIDTH(0x33f4, 2), // U+01f3f4 NEW_WIDTH(0x33f5, 1), // U+01f3f5 NEW_WIDTH(0x33f8, 2), // U+01f3f8 NEW_WIDTH(0x343f, 1), // U+01f43f NEW_WIDTH(0x3440, 2), // U+01f440 NEW_WIDTH(0x3441, 1), // U+01f441 NEW_WIDTH(0x3442, 2), // U+01f442 NEW_WIDTH(0x34fd, 1), // U+01f4fd NEW_WIDTH(0x34ff, 2), // U+01f4ff NEW_WIDTH(0x353e, 1), // U+01f53e NEW_WIDTH(0x354b, 2), // U+01f54b NEW_WIDTH(0x354f, 1), // U+01f54f NEW_WIDTH(0x3550, 2), // U+01f550 NEW_WIDTH(0x3568, 1), // U+01f568 NEW_WIDTH(0x357a, 2), // U+01f57a NEW_WIDTH(0x357b, 1), // U+01f57b NEW_WIDTH(0x3595, 2), // U+01f595 NEW_WIDTH(0x3597, 1), // U+01f597 NEW_WIDTH(0x35a4, 2), // U+01f5a4 NEW_WIDTH(0x35a5, 1), // U+01f5a5 NEW_WIDTH(0x35fb, 2), // U+01f5fb NEW_WIDTH(0x3650, 1), // U+01f650 NEW_WIDTH(0x3680, 2), // U+01f680 NEW_WIDTH(0x36c6, 1), // U+01f6c6 NEW_WIDTH(0x36cc, 2), // U+01f6cc NEW_WIDTH(0x36cd, 1), // U+01f6cd NEW_WIDTH(0x36d0, 2), // U+01f6d0 NEW_WIDTH(0x36d3, 1), // U+01f6d3 NEW_WIDTH(0x36d5, 2), // U+01f6d5 NEW_WIDTH(0x36d8, 1), // U+01f6d8 NEW_WIDTH(0x36dd, 2), // U+01f6dd NEW_WIDTH(0x36e0, 1), // U+01f6e0 NEW_WIDTH(0x36eb, 2), // U+01f6eb NEW_WIDTH(0x36ed, 1), // U+01f6ed NEW_WIDTH(0x36f4, 2), // U+01f6f4 NEW_WIDTH(0x36fd, 1), // U+01f6fd NEW_WIDTH(0x37e0, 2), // U+01f7e0 NEW_WIDTH(0x37ec, 1), // U+01f7ec NEW_WIDTH(0x37f0, 2), // U+01f7f0 NEW_WIDTH(0x37f1, 1), // U+01f7f1 NEW_WIDTH(0x390c, 2), // U+01f90c NEW_WIDTH(0x393b, 1), // U+01f93b NEW_WIDTH(0x393c, 2), // U+01f93c NEW_WIDTH(0x3946, 1), // U+01f946 NEW_WIDTH(0x3947, 2), // U+01f947 NEW_WIDTH(0x3a00, 1), // U+01fa00 NEW_WIDTH(0x3a70, 2), // U+01fa70 NEW_WIDTH(0x3a75, 1), // U+01fa75 NEW_WIDTH(0x3a78, 2), // U+01fa78 NEW_WIDTH(0x3a7d, 1), // U+01fa7d NEW_WIDTH(0x3a80, 2), // U+01fa80 NEW_WIDTH(0x3a87, 1), // U+01fa87 NEW_WIDTH(0x3a90, 2), // U+01fa90 NEW_WIDTH(0x3aad, 1), // U+01faad NEW_WIDTH(0x3ab0, 2), // U+01fab0 NEW_WIDTH(0x3abb, 1), // U+01fabb NEW_WIDTH(0x3ac0, 2), // U+01fac0 NEW_WIDTH(0x3ac6, 1), // U+01fac6 NEW_WIDTH(0x3ad0, 2), // U+01fad0 NEW_WIDTH(0x3ada, 1), // U+01fada NEW_WIDTH(0x3ae0, 2), // U+01fae0 NEW_WIDTH(0x3ae8, 1), // U+01fae8 NEW_WIDTH(0x3af0, 2), // U+01faf0 NEW_WIDTH(0x3af7, 1), // U+01faf7 // U+020000, NEW_WIDTH(0x0000, 2), // U+020000 // U+024000, NEW_WIDTH(0x0000, 2), // U+024000 // U+028000, NEW_WIDTH(0x0000, 2), // U+028000 // U+02c000, NEW_WIDTH(0x0000, 2), // U+02c000 NEW_WIDTH(0x3ffe, 1), // U+02fffe // U+030000, NEW_WIDTH(0x0000, 2), // U+030000 // U+034000, NEW_WIDTH(0x0000, 2), // U+034000 // U+038000, NEW_WIDTH(0x0000, 2), // U+038000 // U+03c000, NEW_WIDTH(0x0000, 2), // U+03c000 NEW_WIDTH(0x3ffe, 1), // U+03fffe // U+040000, NEW_WIDTH(0x0000, 1), // U+040000 // U+044000, NEW_WIDTH(0x0000, 1), // U+044000 // U+048000, NEW_WIDTH(0x0000, 1), // U+048000 // U+04c000, NEW_WIDTH(0x0000, 1), // U+04c000 // U+050000, NEW_WIDTH(0x0000, 1), // U+050000 // U+054000, NEW_WIDTH(0x0000, 1), // U+054000 // U+058000, NEW_WIDTH(0x0000, 1), // U+058000 // U+05c000, NEW_WIDTH(0x0000, 1), // U+05c000 // U+060000, NEW_WIDTH(0x0000, 1), // U+060000 // U+064000, NEW_WIDTH(0x0000, 1), // U+064000 // U+068000, NEW_WIDTH(0x0000, 1), // U+068000 // U+06c000, NEW_WIDTH(0x0000, 1), // U+06c000 // U+070000, NEW_WIDTH(0x0000, 1), // U+070000 // U+074000, NEW_WIDTH(0x0000, 1), // U+074000 // U+078000, NEW_WIDTH(0x0000, 1), // U+078000 // U+07c000, NEW_WIDTH(0x0000, 1), // U+07c000 // U+080000, NEW_WIDTH(0x0000, 1), // U+080000 // U+084000, NEW_WIDTH(0x0000, 1), // U+084000 // U+088000, NEW_WIDTH(0x0000, 1), // U+088000 // U+08c000, NEW_WIDTH(0x0000, 1), // U+08c000 // U+090000, NEW_WIDTH(0x0000, 1), // U+090000 // U+094000, NEW_WIDTH(0x0000, 1), // U+094000 // U+098000, NEW_WIDTH(0x0000, 1), // U+098000 // U+09c000, NEW_WIDTH(0x0000, 1), // U+09c000 // U+0a0000, NEW_WIDTH(0x0000, 1), // U+0a0000 // U+0a4000, NEW_WIDTH(0x0000, 1), // U+0a4000 // U+0a8000, NEW_WIDTH(0x0000, 1), // U+0a8000 // U+0ac000, NEW_WIDTH(0x0000, 1), // U+0ac000 // U+0b0000, NEW_WIDTH(0x0000, 1), // U+0b0000 // U+0b4000, NEW_WIDTH(0x0000, 1), // U+0b4000 // U+0b8000, NEW_WIDTH(0x0000, 1), // U+0b8000 // U+0bc000, NEW_WIDTH(0x0000, 1), // U+0bc000 // U+0c0000, NEW_WIDTH(0x0000, 1), // U+0c0000 // U+0c4000, NEW_WIDTH(0x0000, 1), // U+0c4000 // U+0c8000, NEW_WIDTH(0x0000, 1), // U+0c8000 // U+0cc000, NEW_WIDTH(0x0000, 1), // U+0cc000 // U+0d0000, NEW_WIDTH(0x0000, 1), // U+0d0000 // U+0d4000, NEW_WIDTH(0x0000, 1), // U+0d4000 // U+0d8000, NEW_WIDTH(0x0000, 1), // U+0d8000 // U+0dc000, NEW_WIDTH(0x0000, 1), // U+0dc000 // U+0e0000, NEW_WIDTH(0x0000, 1), // U+0e0000 NEW_WIDTH(0x0001, 0), // U+0e0001 NEW_WIDTH(0x0002, 1), // U+0e0002 NEW_WIDTH(0x0020, 0), // U+0e0020 NEW_WIDTH(0x0080, 1), // U+0e0080 NEW_WIDTH(0x0100, 0), // U+0e0100 NEW_WIDTH(0x01f0, 1), // U+0e01f0 // U+0e4000, NEW_WIDTH(0x0000, 1), // U+0e4000 // U+0e8000, NEW_WIDTH(0x0000, 1), // U+0e8000 // U+0ec000, NEW_WIDTH(0x0000, 1), // U+0ec000 // U+0f0000, NEW_WIDTH(0x0000, 1), // U+0f0000 // U+0f4000, NEW_WIDTH(0x0000, 1), // U+0f4000 // U+0f8000, NEW_WIDTH(0x0000, 1), // U+0f8000 // U+0fc000, NEW_WIDTH(0x0000, 1), // U+0fc000 // U+100000, NEW_WIDTH(0x0000, 1), // U+100000 // U+104000, NEW_WIDTH(0x0000, 1), // U+104000 // U+108000, NEW_WIDTH(0x0000, 1), // U+108000 // U+10c000, NEW_WIDTH(0x0000, 1), // U+10c000 };termpaint-0.3.1/debugwin.py000077500000000000000000000024661477303547200157230ustar00rootroot00000000000000#! /usr/bin/env python3 # SPDX-License-Identifier: BSL-1.0 import sys # Don't display anything before receiving the first line. # If everything goes well this process will stay silent. initial_input = sys.stdin.readline() if not initial_input: sys.exit(0) import tkinter as tk from tkinter.constants import END, N, E, W from tkinter import ttk root = tk.Tk() root.title("Termpaint Error Log") root.columnconfigure(0, weight=1) root.rowconfigure(0, weight=1) def BtnFunc(): text.config(state="normal") text.delete('1.0', END) text.config(state="disabled") def ReadStdIn(srcobj, mask, *arg): input = srcobj.readline() if not input: root.after(10000, lambda: sys.exit(0)) input = "program terminated, closing window in 10 secondes" root.tk.deletefilehandler(sys.stdin) text.config(state="normal") text.insert(END, input) text.config(state="disabled") text.see(END) text = tk.Text(root, width=60, height=10, state='disabled') btn = tk.Button(root, text="Clear", command=BtnFunc) text.grid(column=0, row=0, sticky=(N, E, W)) btn.grid(column=0, row=1, sticky=(E, W)) text.config(state="normal") text.insert(END, initial_input) text.config(state="disabled") text.see(END) filehandler = root.tk.createfilehandler(sys.stdin, tk.READABLE, ReadStdIn) root.mainloop() termpaint-0.3.1/demo/000077500000000000000000000000001477303547200144565ustar00rootroot00000000000000termpaint-0.3.1/demo/attrs.c000066400000000000000000000541761477303547200157740ustar00rootroot00000000000000// Feel free to copy from this example to your own code // SPDX-License-Identifier: 0BSD OR BSL-1.0 OR MIT-0 #include #include #include #include #include #include "termpaint.h" #include "termpaintx.h" #include "termpaintx_ttyrescue.h" termpaint_terminal *terminal; termpaint_surface *surface; termpaint_integration *integration; bool quit; typedef struct event_ { int type; int modifier; const char *string; struct event_* next; } event; event* event_current; void event_callback(void *userdata, termpaint_event *tp_event) { (void)userdata; // remember tp_event is only valid while this callback runs, so copy everything we need. event *my_event = NULL; if (tp_event->type == TERMPAINT_EV_CHAR) { my_event = malloc(sizeof(event)); my_event->type = tp_event->type; my_event->modifier = tp_event->c.modifier; my_event->string = strndup(tp_event->c.string, tp_event->c.length); my_event->next = NULL; } else if (tp_event->type == TERMPAINT_EV_KEY) { my_event = malloc(sizeof(event)); my_event->type = tp_event->type; my_event->modifier = tp_event->key.modifier; my_event->string = strdup(tp_event->key.atom); my_event->next = NULL; } if (my_event) { event* prev = event_current; while (prev->next) { prev = prev->next; } prev->next = my_event; } } bool init(void) { event_current = malloc(sizeof(event)); event_current->next = NULL; event_current->string = NULL; integration = termpaintx_full_integration_setup_terminal_fullscreen("+kbdsigint +kbdsigtstp", event_callback, NULL, &terminal); surface = termpaint_terminal_get_surface(terminal); return 1; } void cleanup(void) { termpaint_terminal_free_with_restore(terminal); while (event_current) { free((void*)event_current->string); event* next = event_current->next; free(event_current); event_current = next; } } event* key_wait(void) { termpaint_terminal_flush(terminal, false); while (!event_current->next) { if (!termpaintx_full_integration_do_iteration(integration)) { // some kind of error cleanup(); exit(1); } } free((void*)event_current->string); event* next = event_current->next; free(event_current); event_current = next; return next; } void write_sample(termpaint_attr* attr_ui, termpaint_attr* attr_sample, int line, char const* name, int style) { termpaint_surface_write_with_attr(surface, 0, line, name, attr_ui); termpaint_attr_reset_style(attr_sample); termpaint_attr_set_style(attr_sample, style); termpaint_surface_write_with_attr(surface, 11, line, "Sample", attr_sample); } void repaint_samples(termpaint_attr* attr_ui, termpaint_attr* attr_sample) { write_sample(attr_ui, attr_sample, 3, "No Style:", 0); write_sample(attr_ui, attr_sample, 4, "Bold:", TERMPAINT_STYLE_BOLD); write_sample(attr_ui, attr_sample, 5, "Italic:", TERMPAINT_STYLE_ITALIC); write_sample(attr_ui, attr_sample, 6, "Blinking:", TERMPAINT_STYLE_BLINK); write_sample(attr_ui, attr_sample, 7, "Underline:", TERMPAINT_STYLE_UNDERLINE); write_sample(attr_ui, attr_sample, 8, "Strikeout:", TERMPAINT_STYLE_STRIKE); write_sample(attr_ui, attr_sample, 9, "Inverse:", TERMPAINT_STYLE_INVERSE); write_sample(attr_ui, attr_sample, 11, "Overline:", TERMPAINT_STYLE_OVERLINE); write_sample(attr_ui, attr_sample, 12, "Dbl under:", TERMPAINT_STYLE_UNDERLINE_DBL); write_sample(attr_ui, attr_sample, 13, "curly:", TERMPAINT_STYLE_UNDERLINE_CURLY); // There is not yet explicit support for URLs, so use the low level patch interface termpaint_attr* attr_url = termpaint_attr_clone(attr_sample); termpaint_attr_set_patch(attr_url, true, "\033]8;;http://example.com\a", "\033]8;;\a"); write_sample(attr_ui, attr_url, 14, "url:", 0); termpaint_attr_free(attr_url); } void repaint_all(termpaint_attr* attr_ui, termpaint_attr* attr_sample) { termpaint_surface_clear_with_attr(surface, attr_ui); termpaint_surface_write_with_attr(surface, 1, 0, "Attribute Demo", attr_ui); repaint_samples(attr_ui, attr_sample); termpaint_surface_write_with_attr(surface, 25, 2, "Select Color", attr_ui); termpaint_surface_write_with_attr(surface, 2, 16, "q: Quit", attr_ui); } void update_current_key_display(termpaint_attr* attr_ui, event *evt) { if (evt->type == TERMPAINT_EV_CHAR || evt->type == TERMPAINT_EV_KEY) { char buff[100]; snprintf(buff, 100, "%-20.20s mod: %d", evt->string, evt->modifier); termpaint_surface_write_with_attr(surface, 0, 23, "Last key: ", attr_ui); termpaint_surface_write_with_attr(surface, 11, 23, buff, attr_ui); } } void named_color_menu(termpaint_attr* attr_ui, termpaint_attr* attr_to_change, int which_color) { int color = 0; while (!quit) { { termpaint_attr* preview = termpaint_attr_new(0, TERMPAINT_INDEXED_COLOR + color); termpaint_surface_write_with_attr(surface, 50, 7, " ", preview); termpaint_attr_free(preview); } termpaint_surface_write_with_attr(surface, 25, 7, " Black", attr_ui); termpaint_surface_write_with_attr(surface, 25, 8, " Red", attr_ui); termpaint_surface_write_with_attr(surface, 25, 9, " Green", attr_ui); termpaint_surface_write_with_attr(surface, 25, 10, " Yellow", attr_ui); termpaint_surface_write_with_attr(surface, 25, 11, " Blue", attr_ui); termpaint_surface_write_with_attr(surface, 25, 12, " Magenta", attr_ui); termpaint_surface_write_with_attr(surface, 25, 13, " Cyan", attr_ui); termpaint_surface_write_with_attr(surface, 25, 14, " Light Grey", attr_ui); termpaint_surface_write_with_attr(surface, 25, 15, " Dark Grey", attr_ui); termpaint_surface_write_with_attr(surface, 25, 16, " Bright Red", attr_ui); termpaint_surface_write_with_attr(surface, 25, 17, " Bright Green", attr_ui); termpaint_surface_write_with_attr(surface, 25, 18, " Bright Yellow", attr_ui); termpaint_surface_write_with_attr(surface, 25, 19, " Bright Blue", attr_ui); termpaint_surface_write_with_attr(surface, 25, 20, " Bright Magenta", attr_ui); termpaint_surface_write_with_attr(surface, 25, 21, " Bright Cyan", attr_ui); termpaint_surface_write_with_attr(surface, 25, 22, " White", attr_ui); termpaint_surface_write_with_attr(surface, 25, 7 + color, "*", attr_ui); event *evt = key_wait(); update_current_key_display(attr_ui, evt); if (evt->type == TERMPAINT_EV_CHAR && strcmp(evt->string, "q") == 0) { quit = true; } if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "ArrowUp") == 0) { color = color - 1; if (color < 0) { color = 15; } } if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "ArrowDown") == 0) { color = (color + 1) % 16; } if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "Enter") == 0) { int tp_color = TERMPAINT_NAMED_COLOR + color; if (which_color == 0) { termpaint_attr_set_fg(attr_to_change, tp_color); } else if (which_color == 1) { termpaint_attr_set_bg(attr_to_change, tp_color); } else { termpaint_attr_set_deco(attr_to_change, tp_color); } return; } if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "Escape") == 0) { return; } } } void indexed_color_menu(termpaint_attr* attr_ui, termpaint_attr* attr_to_change, int which_color) { int color = 0; termpaint_surface_write_with_attr(surface, 25, 7, " 0", attr_ui); termpaint_surface_write_with_attr(surface, 25, 8, " 16", attr_ui); termpaint_surface_write_with_attr(surface, 25, 9, " 32", attr_ui); termpaint_surface_write_with_attr(surface, 25, 10, " 48", attr_ui); termpaint_surface_write_with_attr(surface, 25, 11, " 64", attr_ui); termpaint_surface_write_with_attr(surface, 25, 12, " 80", attr_ui); termpaint_surface_write_with_attr(surface, 25, 13, " 96", attr_ui); termpaint_surface_write_with_attr(surface, 25, 14, "112", attr_ui); termpaint_surface_write_with_attr(surface, 25, 15, "128", attr_ui); termpaint_surface_write_with_attr(surface, 25, 16, "144", attr_ui); termpaint_surface_write_with_attr(surface, 25, 17, "160", attr_ui); termpaint_surface_write_with_attr(surface, 25, 18, "176", attr_ui); termpaint_surface_write_with_attr(surface, 25, 19, "192", attr_ui); termpaint_surface_write_with_attr(surface, 25, 20, "208", attr_ui); termpaint_surface_write_with_attr(surface, 25, 21, "224", attr_ui); termpaint_surface_write_with_attr(surface, 25, 22, "240", attr_ui); termpaint_surface_write_with_attr(surface, 29, 6, " 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15", attr_ui); while (!quit) { { termpaint_attr* preview = termpaint_attr_new(0, TERMPAINT_INDEXED_COLOR + color); termpaint_surface_write_with_attr(surface, 28, 6, " ", preview); termpaint_attr_free(preview); } termpaint_surface_clear_rect_with_attr(surface, 29, 7, 50, 16, attr_ui); char buff[11]; sprintf(buff, "%3d", color); termpaint_surface_write_with_attr(surface, 29 + (color % 16) * 3, 7 + (color / 16), buff, attr_ui); event *evt = key_wait(); update_current_key_display(attr_ui, evt); if (evt->type == TERMPAINT_EV_CHAR) { if (strcmp(evt->string, "q") == 0) { quit = true; } } else if (evt->type == TERMPAINT_EV_KEY) { if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "ArrowLeft") == 0) { color -= 1; if (color < 0) { color = 255; } } else if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "ArrowRight") == 0) { color = (color + 1) % 256; } else if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "ArrowUp") == 0) { color -= 16; if (color < 0) { color += 256; } } else if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "ArrowDown") == 0) { color = (color + 16) % 256; } if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "Enter") == 0) { int tp_color = TERMPAINT_INDEXED_COLOR + color; if (which_color == 0) { termpaint_attr_set_fg(attr_to_change, tp_color); } else if (which_color == 1) { termpaint_attr_set_bg(attr_to_change, tp_color); } else { termpaint_attr_set_deco(attr_to_change, tp_color); } return; } if (strcmp(evt->string, "Escape") == 0) { return; } } } } void rgb_color_menu(termpaint_attr* attr_ui, termpaint_attr* attr_to_change, int which_color) { int red = 0; int green = 0; int blue = 0; int *selected = &red; termpaint_surface_write_with_attr(surface, 29, 10, "left/right: select component", attr_ui); termpaint_surface_write_with_attr(surface, 29, 11, "up/down: adjust value", attr_ui); termpaint_surface_write_with_attr(surface, 29, 12, "page up/page down: adjust value (16 increments)", attr_ui); termpaint_surface_write_with_attr(surface, 29, 13, "esc: abort", attr_ui); termpaint_surface_write_with_attr(surface, 29, 14, "enter: activate color", attr_ui); while (!quit) { char buff[40]; sprintf(buff, "R: %3d G: %3d B: %3d", red, green, blue); termpaint_surface_write_with_attr(surface, 29, 7, buff, attr_ui); termpaint_surface_write_with_attr(surface, 29, 8, " ", attr_ui); { termpaint_attr* preview = termpaint_attr_new(0, TERMPAINT_RGB_COLOR(red, green, blue)); termpaint_surface_write_with_attr(surface, 52, 7, " ", preview); termpaint_attr_free(preview); } if (selected == &red) { termpaint_surface_write_with_attr(surface, 32, 8, "^^^", attr_ui); } else if (selected == &green) { termpaint_surface_write_with_attr(surface, 39, 8, "^^^", attr_ui); } else if (selected == &blue) { termpaint_surface_write_with_attr(surface, 46, 8, "^^^", attr_ui); } event *evt = key_wait(); update_current_key_display(attr_ui, evt); if (evt->type == TERMPAINT_EV_CHAR) { if (strcmp(evt->string, "q") == 0) { quit = true; } } else if (evt->type == TERMPAINT_EV_KEY) { if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "ArrowLeft") == 0) { if (selected == &green) { selected = &red; } else if (selected == &blue) { selected = &green; } } else if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "ArrowRight") == 0) { if (selected == &red) { selected = &green; } else if (selected == &green) { selected = &blue; } } else if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "ArrowUp") == 0) { *selected = (256 + *selected - 1) % 256; } else if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "ArrowDown") == 0) { *selected = (*selected + 1) % 256; } else if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "PageUp") == 0) { *selected = (256 + *selected - 16) % 256; } else if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "PageDown") == 0) { *selected = (*selected + 16) % 256; } if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "Enter") == 0) { int tp_color = TERMPAINT_RGB_COLOR(red, green, blue); if (which_color == 0) { termpaint_attr_set_fg(attr_to_change, tp_color); } else if (which_color == 1) { termpaint_attr_set_bg(attr_to_change, tp_color); } else { termpaint_attr_set_deco(attr_to_change, tp_color); } return; } if (strcmp(evt->string, "Escape") == 0) { return; } } } } void menu(termpaint_attr* attr_ui, termpaint_attr* attr_sample) { bool sample = true; bool reset = true; while (!quit) { if (reset) { repaint_all(attr_ui, attr_sample); termpaint_surface_write_with_attr(surface, 29, 14, "left/right: change select", attr_ui); termpaint_surface_write_with_attr(surface, 29, 15, "up/esc: undo choice", attr_ui); termpaint_surface_write_with_attr(surface, 29, 16, "enter: follow menu path", attr_ui); reset = false; } if (sample) { termpaint_surface_write_with_attr(surface, 25, 3, "* Sample", attr_ui); termpaint_surface_write_with_attr(surface, 40, 3, " UI", attr_ui); } else { termpaint_surface_write_with_attr(surface, 25, 3, " Sample", attr_ui); termpaint_surface_write_with_attr(surface, 40, 3, "* UI", attr_ui); } event *evt = key_wait(); update_current_key_display(attr_ui, evt); if (evt->type == TERMPAINT_EV_CHAR && strcmp(evt->string, "q") == 0) { quit = true; } if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "ArrowLeft") == 0 && !sample) { sample = true; } if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "ArrowRight") == 0 && sample) { sample = false; } if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "Enter") == 0) { int which_color = 0; termpaint_surface_write_with_attr(surface, 25, 4, "* Foreground", attr_ui); termpaint_surface_write_with_attr(surface, 40, 4, " Background", attr_ui); termpaint_surface_write_with_attr(surface, 54, 4, " Deco", attr_ui); while (!quit && !reset) { event *evt = key_wait(); update_current_key_display(attr_ui, evt); if (evt->type == TERMPAINT_EV_CHAR && strcmp(evt->string, "q") == 0) { quit = true; } if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "ArrowLeft") == 0 && which_color == 1) { termpaint_surface_write_with_attr(surface, 25, 4, "* Foreground", attr_ui); termpaint_surface_write_with_attr(surface, 40, 4, " Background", attr_ui); which_color = 0; } else if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "ArrowLeft") == 0 && which_color == 2) { termpaint_surface_write_with_attr(surface, 40, 4, "* Background", attr_ui); termpaint_surface_write_with_attr(surface, 54, 4, " Deco", attr_ui); which_color = 1; } if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "ArrowRight") == 0 && which_color == 0) { termpaint_surface_write_with_attr(surface, 25, 4, " Foreground", attr_ui); termpaint_surface_write_with_attr(surface, 40, 4, "* Background", attr_ui); which_color = 1; } else if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "ArrowRight") == 0 && which_color == 1) { termpaint_surface_write_with_attr(surface, 40, 4, " Background", attr_ui); termpaint_surface_write_with_attr(surface, 54, 4, "* Deco", attr_ui); which_color = 2; } if (evt->type == TERMPAINT_EV_KEY && (strcmp(evt->string, "ArrowUp") == 0 || strcmp(evt->string, "Escape") == 0)) { termpaint_surface_clear_rect_with_attr(surface, 25, 4, 35, 1, attr_ui); break; } if (evt->type == TERMPAINT_EV_KEY && strcmp(evt->string, "Enter") == 0) { int type = 0; termpaint_surface_write_with_attr(surface, 25, 5, "* Named", attr_ui); termpaint_surface_write_with_attr(surface, 40, 5, " Indexed", attr_ui); termpaint_surface_write_with_attr(surface, 53, 5, " RGB", attr_ui); while (!quit && !reset) { event *evt = key_wait(); update_current_key_display(attr_ui, evt); if (evt->type == TERMPAINT_EV_CHAR) { if (strcmp(evt->string, "q") == 0) { quit = true; } } else { if (strcmp(evt->string, "ArrowLeft") == 0 && type == 1) { type = 0; termpaint_surface_write_with_attr(surface, 25, 5, "* Named", attr_ui); termpaint_surface_write_with_attr(surface, 40, 5, " Indexed", attr_ui); } else if (strcmp(evt->string, "ArrowLeft") == 0 && type == 2) { type = 1; termpaint_surface_write_with_attr(surface, 40, 5, "* Indexed", attr_ui); termpaint_surface_write_with_attr(surface, 53, 5, " RGB", attr_ui); } else if (strcmp(evt->string, "ArrowRight") == 0 && type == 0) { type = 1; termpaint_surface_write_with_attr(surface, 25, 5, " Named", attr_ui); termpaint_surface_write_with_attr(surface, 40, 5, "* Indexed", attr_ui); } else if (strcmp(evt->string, "ArrowRight") == 0 && type == 1) { type = 2; termpaint_surface_write_with_attr(surface, 40, 5, " Indexed", attr_ui); termpaint_surface_write_with_attr(surface, 53, 5, "* RGB", attr_ui); } else if (strcmp(evt->string, "ArrowUp") == 0 || strcmp(evt->string, "Escape") == 0) { termpaint_surface_clear_rect_with_attr(surface, 25, 5, 35, 1, attr_ui); break; } else if (strcmp(evt->string, "Enter") == 0) { termpaint_surface_clear_rect_with_attr(surface, 29, 14, 25, 3, attr_ui); termpaint_attr* to_change = sample ? attr_sample : attr_ui; if (type == 0) { named_color_menu(attr_ui, to_change, which_color); } else if (type == 1) { indexed_color_menu(attr_ui, to_change, which_color); } else { rgb_color_menu(attr_ui, to_change, which_color); } reset = true; } } } } } } } } int main(int argc, char **argv) { (void)argc; (void)argv; if (!init()) { return 1; } termpaint_attr* attr_ui = termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_attr* attr_sample = termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); repaint_all(attr_ui, attr_sample); menu(attr_ui, attr_sample); termpaint_attr_free(attr_sample); attr_sample = NULL; termpaint_attr_free(attr_ui); attr_ui = NULL; cleanup(); return 0; } termpaint-0.3.1/demo/chars.c000066400000000000000000000103671477303547200157310ustar00rootroot00000000000000// Feel free to copy from this example to your own code // SPDX-License-Identifier: 0BSD OR BSL-1.0 OR MIT-0 #include #include #include #include #include #include "termpaint.h" #include "termpaintx.h" #include "termpaintx_ttyrescue.h" termpaint_terminal *terminal; termpaint_surface *surface; termpaint_integration *integration; bool quit; typedef struct event_ { int type; int modifier; const char *string; struct event_* next; } event; event* event_current; void event_callback(void *userdata, termpaint_event *tp_event) { (void)userdata; // remember tp_event is only valid while this callback runs, so copy everything we need. event *my_event = NULL; if (tp_event->type == TERMPAINT_EV_CHAR) { my_event = malloc(sizeof(event)); my_event->type = tp_event->type; my_event->modifier = tp_event->c.modifier; my_event->string = strndup(tp_event->c.string, tp_event->c.length); my_event->next = NULL; } else if (tp_event->type == TERMPAINT_EV_KEY) { my_event = malloc(sizeof(event)); my_event->type = tp_event->type; my_event->modifier = tp_event->key.modifier; my_event->string = strdup(tp_event->key.atom); my_event->next = NULL; } if (my_event) { event* prev = event_current; while (prev->next) { prev = prev->next; } prev->next = my_event; } } bool init(void) { event_current = malloc(sizeof(event)); event_current->next = NULL; event_current->string = NULL; integration = termpaintx_full_integration_setup_terminal_fullscreen("+kbdsigint +kbdsigtstp", event_callback, NULL, &terminal); surface = termpaint_terminal_get_surface(terminal); return 1; } void cleanup(void) { termpaint_terminal_free_with_restore(terminal); while (event_current) { free((void*)event_current->string); event* next = event_current->next; free(event_current); event_current = next; } } event* key_wait(void) { termpaint_terminal_flush(terminal, false); while (!event_current->next) { if (!termpaintx_full_integration_do_iteration(integration)) { // some kind of error cleanup(); exit(1); } } free((void*)event_current->string); event* next = event_current->next; free(event_current); event_current = next; return next; } int main(int argc, char **argv) { (void)argc; (void)argv; if (!init()) { return 1; } termpaint_surface_clear(surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(surface, 10, 3, "Samples:", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); // isolated U+0308 COMBINING DIAERESIS termpaint_surface_write_with_colors(surface, 10, 4, "\xcc\x88X", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); // 'a' + U+0308 COMBINING DIAERESIS termpaint_surface_write_with_colors(surface, 10, 5, "a\xcc\x88X", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); // 'a' + U+0308 COMBINING DIAERESIS + U+0324 COMBINING DIAERESIS BELOW termpaint_surface_write_with_colors(surface, 10, 6, "a\xcc\x88\xcc\xa4X", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); // 'a' + U+E0100 VARIATION SELECTOR-17 + U+E0101 VARIATION SELECTOR-18 (nonsense) termpaint_surface_write_with_colors(surface, 10, 7, "a\xf3\xa0\x84\x80\xf3\xa0\x84\x81X", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); // 'a' + U+E0100 VARIATION SELECTOR-17 + U+FE00 VARIATION SELECTOR-1 (nonsense) termpaint_surface_write_with_colors(surface, 10, 8, "a\xf3\xa0\x84\x80\xef\xb8\x80X", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); // 'a' + U+E0100 VARIATION SELECTOR-17 + U+FEFF ZERO WIDTH NO-BREAK SPACE (nonsense) termpaint_surface_write_with_colors(surface, 10, 9, "a\xf3\xa0\x84\x80\xef\xbb\xbfX", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(surface, 10, 10, "あ3あ67あX", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); key_wait(); cleanup(); return 0; } termpaint-0.3.1/demo/detect.c000066400000000000000000000125401477303547200160740ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include #include #include #include #include "termpaint.h" #include "termpaintx.h" void null_callback(void *ctx, termpaint_event *event) { (void)ctx; (void)event; } typedef struct { int id; const char *name; const char *short_name; _Bool state; } Cap; Cap caps[] = { #define C(name, s) { TERMPAINT_CAPABILITY_ ## name, #name, s, 0 } C(CSI_POSTFIX_MOD, "pf-mod"), C(TITLE_RESTORE, "title"), C(MAY_TRY_CURSOR_SHAPE_BAR, "cur-bar"), C(CURSOR_SHAPE_OSC50, "cur50"), C(EXTENDED_CHARSET, "extchset"), C(TRUECOLOR_MAYBE_SUPPORTED, "24maybe"), C(TRUECOLOR_SUPPORTED, "24sup"), C(88_COLOR, "88col"), C(CLEARED_COLORING, "clrcol"), C(7BIT_ST, "7bit-st"), C(MAY_TRY_TAGGED_PASTE, "taggedpaste"), C(CLEARED_COLORING_DEFCOLOR, "clrcoldef"), #undef C { 0, NULL, 0, 0 } }; char *debug = NULL; bool debug_used = false; void debug_log(termpaint_integration *integration, const char *data, int length) { (void)integration; if (debug_used && !debug) return; // memory allocaton failure if (debug) { const int oldlen = strlen(debug); char* debug_old = debug; debug = realloc(debug, oldlen + length + 1); if (debug) { memcpy(debug + oldlen, data, length); debug[oldlen + length] = 0; } else { free(debug_old); } } else { debug = strndup(data, length); } debug_used = true; } char *strdup_escaped(const char *tmp) { // escaping could quadruple size char *ret = malloc(strlen(tmp) * 4 + 1); if (!ret) { perror("malloc"); abort(); } char *dst = ret; for (; *tmp; tmp++) { if (*tmp >= ' ' && *tmp <= 126 && *tmp != '\\') { *dst = *tmp; ++dst; } else { dst += sprintf(dst, "\\x%02hhx", (unsigned char)*tmp); } } *dst = 0; return ret; } int main(int argc, char **argv) { (void)argc; (void)argv; termpaint_integration *integration = termpaintx_full_integration("+kbdsigint +kbdsigtstp"); if (!integration) { puts("Could not init!"); return 1; } termpaint_integration_set_logging_func(integration, debug_log); termpaint_terminal *terminal = termpaint_terminal_new(integration); termpaint_terminal_set_log_mask(terminal, TERMPAINT_LOG_AUTO_DETECT_TRACE | TERMPAINT_LOG_TRACE_RAW_INPUT); termpaint_terminal_set_event_cb(terminal, null_callback, NULL); termpaintx_full_integration_set_terminal(integration, terminal); termpaint_terminal_auto_detect(terminal); termpaintx_full_integration_wait_for_ready_with_message(integration, 10000, "Terminal auto detection is taking unusually long, press space to abort."); char buff[1000]; char *self_reported_name_and_version = NULL; termpaint_terminal_auto_detect_result_text(terminal, buff, sizeof (buff)); if (termpaint_terminal_self_reported_name_and_version(terminal)) { self_reported_name_and_version = strdup_escaped(termpaint_terminal_self_reported_name_and_version(terminal)); } for (Cap *c = caps; c->name; c++) { c->state = termpaint_terminal_capable(terminal, c->id); } termpaint_terminal_free_with_restore(terminal); _Bool quiet = false; _Bool short_output = false; for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--quiet") == 0) { quiet = true; } if (strcmp(argv[i], "--short") == 0) { short_output = true; } } if (!quiet && !short_output) { puts(buff); if (self_reported_name_and_version) { printf("self reported: %s\n", self_reported_name_and_version); } for (Cap *c = caps; c->name; c++) { printf("%s: %s\n", c->name, c->state ? "1" : "0"); } } if (short_output) { printf("V1 %s", buff); if (self_reported_name_and_version) { printf(" >%s<", self_reported_name_and_version); } for (Cap *c = caps; c->name; c++) { if (c->state) { printf(" %s", c->short_name); } } puts("\n"); } for (int i=1; i < argc; i++) { if (strcmp(argv[i], "--write-file") == 0) { if (i + 1 < argc) { FILE *f = fopen(argv[i + 1], "w"); if (!f) { perror("fopen"); } else { fputs(buff, f); fprintf(f, "\n"); if (self_reported_name_and_version) { fprintf(f, "self reported: %s\n", self_reported_name_and_version); } for (Cap *c = caps; c->name; c++) { fprintf(f, "%s: %s\n", c->name, c->state ? "1" : "0"); } fclose(f); } } } if (strcmp(argv[i], "--debug") == 0) { if (debug) { printf("%s", debug); } else if (debug_used) { printf("debug log could not be allocated!\n"); } } if (strcmp(argv[i], "--key-wait") == 0) { puts("Press any key to continue"); getchar(); } } free(self_reported_name_and_version); return 0; } termpaint-0.3.1/demo/life.c000066400000000000000000000175771477303547200155620ustar00rootroot00000000000000// Feel free to copy from this example to your own code // SPDX-License-Identifier: 0BSD OR BSL-1.0 OR MIT-0 #include #include #include #include "termpaint.h" #include "termpaintx.h" termpaint_integration *integration; termpaint_terminal *terminal; termpaint_surface *surface; bool update; static int min(int a, int b) { return (a < b) ? a : b; } enum cell_t { DEAD, ALIVE }; typedef struct board_t_ { int width; int height; char *cells; } board_t; board_t board; bool pause; int current_background; int generation; int refresh = 250; int steps = 1; int speed = 4; // > 0 then steps per second, otherwise -speed + 2 seconds per step int cursor_x = 0; int cursor_y = 1; char *cell_at(board_t *board, int x, int y) { return &board->cells[((board->height + y) % board->height) * board->width + ((board->width + x) % board->width)]; } void update_timing(void) { steps = 1; if (speed > 0) { float r = 1. / speed; while (r < .1) { r *= 2; steps *= 2; } refresh = 1000 * r; } else { refresh = 1000 * (-speed + 2); } } void event_callback(void *userdata, termpaint_event *event) { bool *quit = userdata; if (event->type == TERMPAINT_EV_CHAR) { if (event->c.length == 1 && event->c.string[0] == 'q') { *quit = true; } if (event->c.length == 1 && event->c.string[0] == '+') { speed += 1; update_timing(); update = true; } if (event->c.length == 1 && event->c.string[0] == '-') { speed -= 1; update_timing(); update = true; } if (event->c.length == 1 && event->c.string[0] == '0') { *cell_at(&board, cursor_x, cursor_y) = DEAD; update = true; } if (event->c.length == 1 && event->c.string[0] == '1') { *cell_at(&board, cursor_x, cursor_y) = ALIVE; update = true; } } if (event->type == TERMPAINT_EV_KEY) { if (event->key.atom == termpaint_input_space()) { pause = !pause; update = true; } else if (event->key.atom == termpaint_input_arrow_up()) { cursor_y = (board.height + cursor_y - 1) % board.height; update = true; } else if (event->key.atom == termpaint_input_arrow_down()) { cursor_y = (cursor_y + 1) % board.height; update = true; } else if (event->key.atom == termpaint_input_arrow_left()) { cursor_x = (board.width + cursor_x - 1) % board.width; update = true; } else if (event->key.atom == termpaint_input_arrow_right()) { cursor_x = (cursor_x + 1) % board.width; update = true; } } if (event->type == TERMPAINT_EV_MOUSE) { if ((event->mouse.action == TERMPAINT_MOUSE_PRESS && event->mouse.button == 0) || event->mouse.action == TERMPAINT_MOUSE_MOVE) { cursor_x = event->mouse.x; cursor_y = event->mouse.y; char *cell = cell_at(&board, event->mouse.x, event->mouse.y); *cell = !*cell; update = true; } } } int rule(board_t *b, int x, int y) { int count = *cell_at(b, x - 1, y - 1) + *cell_at(b, x, y - 1) + *cell_at(b, x + 1, y - 1) + *cell_at(b, x - 1, y) + *cell_at(b, x + 1, y) + *cell_at(b, x - 1, y + 1) + *cell_at(b, x, y + 1) + *cell_at(b, x + 1, y + 1); int self = *cell_at(b, x, y); if (count == 3) { return ALIVE; } if (self && count == 2) { return ALIVE; } return DEAD; } void pulse(void) { if (pause) { return; } static int i = 0; update = true; int g; if (i < 60) { g = i; } else { g = 60 - (i - 60); } current_background = TERMPAINT_RGB_COLOR(0, 30 + g, 0); for (int i = 0; i < steps; i++) { board_t next; next.width = termpaint_surface_width(surface); next.height = termpaint_surface_height(surface); next.cells = calloc(next.width * next.height, 1); int count = 0; for (int x = 0; x < min(board.width, next.width); x++) { for (int y = 0; y < min(board.height, next.width); y++) { int new_state = rule(&board, x, y); *cell_at(&next, x, y) = new_state; count += new_state; } } if (count == 0) { if (next.height > 5 && next.width > 5) { *cell_at(&next, next.width / 2, next.height / 2 - 1) = ALIVE; *cell_at(&next, next.width / 2 + 1, next.height / 2) = ALIVE; *cell_at(&next, next.width / 2, next.height / 2 + 1) = ALIVE; *cell_at(&next, next.width / 2 - 1, next.height / 2 + 1) = ALIVE; *cell_at(&next, next.width / 2 + 1, next.height / 2 + 1) = ALIVE; } } board.width = next.width; board.height = next.height; free(board.cells); board.cells = next.cells; generation += 1; } i = (i + 10) % 120; } void redraw(void) { int cell_color = TERMPAINT_RGB_COLOR(255, 255, 255); termpaint_surface_clear(surface, TERMPAINT_DEFAULT_COLOR, current_background); for (int x = 0; x < board.width; x++) { for (int y = 0; y < board.height; y++) { int cell_background = current_background; if (pause && cursor_x == x && cursor_y == y) { cell_background = TERMPAINT_RGB_COLOR(0, 0, 0xdd); termpaint_surface_write_with_colors(surface, x, y, " ", cell_color, cell_background); } if (*cell_at(&board, x,y)) { termpaint_surface_write_with_colors(surface, x, y, "♦", cell_color, cell_background); } } } if (pause) { termpaint_surface_write_with_colors(surface, 0, 0, "q to quit, space to pause, cursor keys and 0/1 or mouse to edit", cell_color, current_background); } else { termpaint_surface_write_with_colors(surface, 0, 0, "q to quit, space to pause, -/+ change speed, mouse to edit", cell_color, current_background); } char buf[1000]; if (pause) { sprintf(buf, "generation: %i, speed %i (paused)", generation, speed); } else { sprintf(buf, "generation: %i, speed %i", generation, speed); } termpaint_surface_write_with_colors(surface, 0, board.height - 1, buf, cell_color, current_background); } int main(int argc, char **argv) { (void)argc; (void)argv; bool quit = false; integration = termpaintx_full_integration_setup_terminal_fullscreen( "+kbdsig +kbdsigint", event_callback, &quit, &terminal); surface = termpaint_terminal_get_surface(terminal); termpaint_terminal_set_mouse_mode(terminal, TERMPAINT_MOUSE_MODE_DRAG); board.width = termpaint_surface_width(surface); board.height = termpaint_surface_height(surface); board.cells = calloc(board.width * board.height, 1); pulse(); int timeout = refresh; update = true; while (!quit) { if (update) { redraw(); termpaint_terminal_flush(terminal, false); update = false; } if (!termpaintx_full_integration_do_iteration_with_timeout(integration, &timeout)) { // some kind of error break; } if (timeout == 0) { pulse(); timeout = refresh; } } termpaint_terminal_free_with_restore(terminal); return 0; } termpaint-0.3.1/demo/shuffle.c000066400000000000000000000260731477303547200162660ustar00rootroot00000000000000// Feel free to copy from this example to your own code // SPDX-License-Identifier: 0BSD OR BSL-1.0 OR MIT-0 #include #include #include #include #include "termpaint.h" #include "termpaintx.h" typedef struct event_ { int type; int modifier; char *string; int x; int y; struct event_* next; } event; event* event_current; // unprocessed event event* event_saved; // event to free on next call void event_callback(void *userdata, termpaint_event *tp_event) { (void)userdata; // remember tp_event is only valid while this callback runs, so copy everything we need. event *copied_event = NULL; if (tp_event->type == TERMPAINT_EV_CHAR) { copied_event = malloc(sizeof(event)); copied_event->type = tp_event->type; copied_event->modifier = tp_event->c.modifier; copied_event->string = strndup(tp_event->c.string, tp_event->c.length); copied_event->next = NULL; } else if (tp_event->type == TERMPAINT_EV_KEY) { copied_event = malloc(sizeof(event)); copied_event->type = tp_event->type; copied_event->modifier = tp_event->key.modifier; copied_event->string = strndup(tp_event->key.atom, tp_event->key.length); copied_event->next = NULL; } else if (tp_event->type == TERMPAINT_EV_MOUSE) { copied_event = malloc(sizeof(event)); copied_event->type = tp_event->type; copied_event->modifier = tp_event->mouse.modifier; copied_event->string = NULL; copied_event->x = tp_event->mouse.x; copied_event->y = tp_event->mouse.y; copied_event->next = NULL; } if (copied_event) { if (!event_current) { event_current = copied_event; } else { event* prev = event_current; while (prev->next) { prev = prev->next; } prev->next = copied_event; } } } event* key_wait(termpaint_integration *integration) { while (!event_current) { if (!termpaintx_full_integration_do_iteration(integration)) { // some kind of error return NULL; // or some other error handling } } if (event_saved) { free(event_saved->string); free(event_saved); } event *ret = event_current; event_current = ret->next; event_saved = ret; return ret; } const int screen_bg = TERMPAINT_COLOR_BRIGHT_YELLOW; const int ui_fg = TERMPAINT_COLOR_BLACK; const int win_message = TERMPAINT_COLOR_GREEN; const int tile_border = TERMPAINT_COLOR_BLACK; const int tile_background = TERMPAINT_COLOR_LIGHT_GREY; int field[5][5]; int x, y; int current_start_x; int current_start_y; void solved_message(termpaint_surface *surface) { const int screen_width = termpaint_surface_width(surface); const int screen_height = termpaint_surface_height(surface); termpaint_surface_write_with_colors(surface, screen_width / 2 - 12, screen_height / 2 - 2, "┌───────────────────────┐", ui_fg, win_message); termpaint_surface_write_with_colors(surface, screen_width / 2 - 12, screen_height / 2 - 1, "│ Solved! │", ui_fg, win_message); termpaint_surface_write_with_colors(surface, screen_width / 2 - 12, screen_height / 2, "│ │", ui_fg, win_message); termpaint_surface_write_with_colors(surface, screen_width / 2 - 12, screen_height / 2 + 1, "│ Press any key to exit │", ui_fg, win_message); termpaint_surface_write_with_colors(surface, screen_width / 2 - 12, screen_height / 2 + 2, "└───────────────────────┘", ui_fg, win_message); } void draw_screen(termpaint_surface *surface) { char buf[100]; termpaint_surface_clear(surface, TERMPAINT_COLOR_BLACK, screen_bg); const int screen_width = termpaint_surface_width(surface); const int screen_height = termpaint_surface_height(surface); current_start_x = screen_width / 2 - 10; current_start_y = screen_height / 2 - 7; for (int x = 0; x < 5; x++) { for (int y = 0; y < 5; y++) { const int visual_x = current_start_x + x * 4; const int visual_y = current_start_y + y * 3; if (field[x][y] != -1) { termpaint_surface_write_with_colors(surface, visual_x, visual_y, "┌──┐", tile_border, tile_background); termpaint_surface_write_with_colors(surface, visual_x, visual_y + 1, "│ │", tile_border, tile_background); sprintf(buf, "%.2i", field[x][y]); int fg = (field[x][y] != y * 5 + x + 1) ? TERMPAINT_COLOR_RED : TERMPAINT_COLOR_GREEN; termpaint_surface_write_with_colors(surface, visual_x + 1, visual_y + 1, buf, fg, tile_background); termpaint_surface_write_with_colors(surface, visual_x, visual_y + 2, "└──┘", tile_border, tile_background); } else { termpaint_surface_write_with_colors(surface, visual_x + 1, visual_y, "↓", ui_fg, screen_bg); termpaint_surface_write_with_colors(surface, visual_x, visual_y + 1, "→ ←", ui_fg, screen_bg); termpaint_surface_write_with_colors(surface, visual_x + 2, visual_y + 2, "↑", ui_fg, screen_bg); } } } termpaint_surface_write_with_colors(surface, screen_width / 2 - 15, 0, "Use arrow keys to move tiles.", ui_fg, screen_bg); termpaint_surface_write_with_colors(surface, screen_width / 2 - 15, 1, "Or click on the tile to move.", ui_fg, screen_bg); termpaint_surface_write_with_colors(surface, screen_width / 2 - 8, screen_height - 1, "Press Q to quit.", ui_fg, screen_bg); } enum direction { UP, RIGHT, DOWN, LEFT }; bool do_move(int direction) { bool did_step = false; if (direction == UP && y > 0) { field[x][y] = field[x][y - 1]; y -= 1; did_step = true; } if (direction == RIGHT && x < 4) { field[x][y] = field[x + 1][y]; x += 1; did_step = true; } if (direction == DOWN && y < 4) { field[x][y] = field[x][y + 1]; y += 1; did_step = true; } if (direction == LEFT && x > 0) { field[x][y] = field[x - 1][y]; x -= 1; did_step = true; } if (did_step) { field[x][y] = -1; } return did_step; } void randomize(void) { for (int i = 0; i < 1000; i++) { bool did_step = false; while (!did_step) { int direction = rand() % 4; did_step = do_move(direction); } } } bool solved(void) { for (int x = 0; x < 5; x++) { for (int y = 0; y < 5; y++) { if (x == 4 && y == 4) { return true; } if (field[x][y] != y * 5 + x + 1) { return false; } } } return false; } int main(int argc, char **argv) { (void)argc; (void)argv; termpaint_integration *integration; termpaint_terminal *terminal; termpaint_surface *surface; integration = termpaintx_full_integration_setup_terminal_fullscreen( "+kbdsig +kbdsigint", event_callback, NULL, &terminal); termpaint_terminal_set_mouse_mode(terminal, TERMPAINT_MOUSE_MODE_CLICKS); surface = termpaint_terminal_get_surface(terminal); for (int x = 0; x < 5; x++) { for (int y = 0; y < 5; y++) { field[x][y] = y * 5 + x + 1; } } field[4][4] = -1; x = 4; y = 4; randomize(); while (!solved()) { draw_screen(surface); termpaint_terminal_flush(terminal, false); event* ev = key_wait(integration); if (ev->type == TERMPAINT_EV_KEY) { if (strcmp(ev->string, "ArrowUp") == 0) { do_move(DOWN); } if (strcmp(ev->string, "ArrowRight") == 0) { do_move(LEFT); } if (strcmp(ev->string, "ArrowDown") == 0) { do_move(UP); } if (strcmp(ev->string, "ArrowLeft") == 0) { do_move(RIGHT); } } if (ev->type == TERMPAINT_EV_CHAR) { if (strcmp(ev->string, "Q") == 0 || strcmp(ev->string, "q") == 0) { break; } } if (ev->type == TERMPAINT_EV_MOUSE) { int mouse_x = (ev->x - current_start_x) / 4; int mouse_y = (ev->y - current_start_y) / 3; if (mouse_x >= 0 && mouse_x <= 4 && mouse_y >= 0 && mouse_y <= 4) { if (mouse_x == x) { if (mouse_y == y - 1) { do_move(UP); } if (mouse_y == y + 1) { do_move(DOWN); } } if (mouse_y == y) { if (mouse_x == x - 1) { do_move(LEFT); } if (mouse_x == x + 1) { do_move(RIGHT); } } } } } if (solved()) { draw_screen(surface); solved_message(surface); termpaint_terminal_flush(terminal, false); key_wait(integration); } termpaint_terminal_free_with_restore(terminal); return 0; } termpaint-0.3.1/demo/textwrap.c000066400000000000000000000102261477303547200165010ustar00rootroot00000000000000// Feel free to copy from this example to your own code // SPDX-License-Identifier: 0BSD OR BSL-1.0 OR MIT-0 #include #include #include #include #include "termpaint.h" #include "termpaintx.h" void event_callback(void *userdata, termpaint_event *event) { bool *quit = userdata; if (event->type == TERMPAINT_EV_CHAR) { *quit = true; } if (event->type == TERMPAINT_EV_KEY) { *quit = true; } } int main(int argc, char **argv) { if (argc != 2) { printf("Usage: %s filename\n", argv[0]); return 1; } FILE *fp = fopen(argv[1], "r"); if (!fp) { perror("Error opening file"); return 1; } char *buffer = calloc(40000, 1); if (!buffer) { puts("Out of memory\n"); fclose(fp); return 1; } (void)!fread(buffer, 1, 39999, fp); // already nul filled, error checking on next line if (ferror(fp)) { perror("Error reading file"); fclose(fp); free(buffer); return 1; } fclose(fp); termpaint_integration *integration; termpaint_terminal *terminal; termpaint_surface *surface; bool quit = false; integration = termpaintx_full_integration_setup_terminal_fullscreen( "+kbdsig +kbdsigint", event_callback, &quit, &terminal); surface = termpaint_terminal_get_surface(terminal); termpaint_surface_clear(surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); const int width = termpaint_surface_width(surface); const int height = termpaint_surface_height(surface); termpaint_text_measurement *m = termpaint_text_measurement_new(surface); termpaint_attr *attr = termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); int y = 0; const char* cur = buffer; do { const char* linebreak = strchr(cur, '\n'); int count; if (!linebreak) { count = strlen(cur); } else { count = linebreak - cur; } termpaint_text_measurement_reset(m); termpaint_text_measurement_set_limit_width(m, width); termpaint_text_measurement_feed_utf8(m, cur, count, true); const int bytes = termpaint_text_measurement_last_ref(m); if (bytes != count) { // needs break // try to find a word break int print_bytes = bytes; int skip_bytes = bytes; for (const char *p = cur + bytes; p > cur; p--) { if (*p == ' ') { skip_bytes = print_bytes = p - cur; // the space is always a start of a cluster. // when skipping the space itself after the wrap // care needs to be taken to only skip the space if it is the // sole character of its cluster termpaint_text_measurement_reset(m); termpaint_text_measurement_set_limit_clusters(m, 1); termpaint_text_measurement_feed_utf8(m, cur + skip_bytes, count - skip_bytes, true); if (termpaint_text_measurement_last_ref(m) == 1) { skip_bytes += 1; } break; } } termpaint_surface_write_with_len_attr_clipped(surface, 0, y, cur, print_bytes, attr, 0, width); cur += skip_bytes; } else { // fits completely termpaint_surface_write_with_len_attr_clipped(surface, 0, y, cur, count, attr, 0, width); cur += count + 1; } y += 1; if (!linebreak) break; } while (y < height); termpaint_text_measurement_free(m); termpaint_attr_free(attr); termpaint_terminal_flush(terminal, false); while (!quit) { if (!termpaintx_full_integration_do_iteration(integration)) { // some kind of error break; } } termpaint_terminal_free_with_restore(terminal); return 0; } termpaint-0.3.1/doc/000077500000000000000000000000001477303547200142775ustar00rootroot00000000000000termpaint-0.3.1/doc/_static/000077500000000000000000000000001477303547200157255ustar00rootroot00000000000000termpaint-0.3.1/doc/_static/custom.css000066400000000000000000000004421477303547200177510ustar00rootroot00000000000000dl.macro dt { text-indent: -120px; margin-left: 120px; } dl.function dt { text-indent: -120px; margin-left: 120px; } dl.type dt { text-indent: -120px; margin-left: 120px; } .hidden-references { visibility:hidden; height: 0px; } div .hidden-references + p { margin-top: 0px; } termpaint-0.3.1/doc/_templates/000077500000000000000000000000001477303547200164345ustar00rootroot00000000000000termpaint-0.3.1/doc/_templates/genindex.html000066400000000000000000000032511477303547200211240ustar00rootroot00000000000000{# basic/genindex.html ~~~~~~~~~~~~~~~~~~~ Template for an "all-in-one" index. :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. :license: BSD, see LICENSE for details. #} {%- extends "layout.html" %} {% set title = _('Index') %} {% macro indexentries(firstname, links) %} {%- if links -%} {%- if links[0][0] %}{% endif -%} {{ firstname|e }} {%- if links[0][0] %}{% endif -%} {%- for ismain, link in links[1:] -%} , {% if ismain %}{% endif -%} [{{ loop.index }}] {%- if ismain %}{% endif -%} {%- endfor %} {%- else %} {{ firstname|e }} {%- endif %} {% endmacro %} {% block body %}

{{ _('Index') }}

{%- for key, entries in genindexentries %} {%- for column in entries|slice_index(2) if column %} {%- for entryname, (links, subitems, _) in column %}
  • {{ indexentries(entryname, links) }} {%- if subitems %}
      {%- for subentryname, subentrylinks in subitems %}
    • {{ indexentries(subentryname, subentrylinks) }}
    • {%- endfor %}
    {%- endif -%}
  • {%- endfor %} {%- endfor %} {% endfor %} {% endblock %} {% block sidebarrel %} {% if split_index %}

    {{ _('Index') }}

    {% for key, dummy in genindexentries -%} {{ key }} {% if not loop.last %}| {% endif %} {%- endfor %}

    {{ _('Full index on one page') }}

    {% endif %} {{ super() }} {% endblock %} termpaint-0.3.1/doc/_templates/genindex.html.license000066400000000000000000000023301477303547200225420ustar00rootroot00000000000000Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. termpaint-0.3.1/doc/_templates/navigation.html000066400000000000000000000006711477303547200214650ustar00rootroot00000000000000

    {{ _('Navigation') }}

    {{ toctree(includehidden=theme_sidebar_includehidden, collapse=theme_sidebar_collapse) }} {% if theme_extra_nav_links %}
      {% for text, uri in theme_extra_nav_links.items() %}
    • {{ text }}
    • {% endfor %}
    {% endif %} termpaint-0.3.1/doc/attributes.rst000066400000000000000000000271321477303547200172240ustar00rootroot00000000000000.. _sec_attributes: Attributes ========== .. c:type:: termpaint_attr The attributes object contains paint settings used when painting on a :doc:`surface`. It consists of the foreground color, the background color and the decoration color. In addition it has a set of enabled styles. .. _colors: Colors ------ Terminal colors are a bit complicated for historical reasons. Even if colors seem the same they are all distinct and terminals can (and do) act differently depending on how the color was exactly selected. .. _default-colors: Default colors .............. The most basic color is :c:macro:`TERMPAINT_DEFAULT_COLOR`. It's the terminals default color. This color acts differently when used as foreground, background or decoration color. For foreground and background it's the respective default color. For decoration color the default color means that the terminal will use the foreground color of that particular cell. .. _named-colors: Named colors ............ Next there is the set of named colors. The first 8 named colors are also often called ANSI colors. These colors are have constants like ``TERMPAINT_COLOR_RED`` and can also be referred to as :c:macro:`TERMPAINT_NAMED_COLOR` + color number. The first 8 colors are supported by almost all color terminals. The following 8 colors are still very widely supported. The named colors have names like "red", but terminal implementations often allow reconfiguring these colors from easy accessible settings dialogs. Expect named colors to have different concrete color values for many users. .. container:: hidden-references .. c:macro:: TERMPAINT_COLOR_BLACK .. c:macro:: TERMPAINT_COLOR_RED .. c:macro:: TERMPAINT_COLOR_GREEN .. c:macro:: TERMPAINT_COLOR_YELLOW .. c:macro:: TERMPAINT_COLOR_BLUE .. c:macro:: TERMPAINT_COLOR_MAGENTA .. c:macro:: TERMPAINT_COLOR_CYAN .. c:macro:: TERMPAINT_COLOR_LIGHT_GREY .. c:macro:: TERMPAINT_COLOR_DARK_GREY .. c:macro:: TERMPAINT_COLOR_BRIGHT_RED .. c:macro:: TERMPAINT_COLOR_BRIGHT_GREEN .. c:macro:: TERMPAINT_COLOR_BRIGHT_YELLOW .. c:macro:: TERMPAINT_COLOR_BRIGHT_BLUE .. c:macro:: TERMPAINT_COLOR_BRIGHT_MAGENTA .. c:macro:: TERMPAINT_COLOR_BRIGHT_CYAN .. c:macro:: TERMPAINT_COLOR_WHITE .. table:: Named Colors :align: left ====== ============================== ==== Number Constant Name ====== ============================== ==== 0 TERMPAINT_COLOR_BLACK black 1 TERMPAINT_COLOR_RED red 2 TERMPAINT_COLOR_GREEN green 3 TERMPAINT_COLOR_YELLOW yellow 4 TERMPAINT_COLOR_BLUE blue 5 TERMPAINT_COLOR_MAGENTA magenta 6 TERMPAINT_COLOR_CYAN cyan 7 TERMPAINT_COLOR_LIGHT_GREY "white" (on terminals supporting 16 colors this is light gray) 8 TERMPAINT_COLOR_DARK_GREY dark gray 9 TERMPAINT_COLOR_BRIGHT_RED bright red 10 TERMPAINT_COLOR_BRIGHT_GREEN bright green 11 TERMPAINT_COLOR_BRIGHT_YELLOW bright yellow 12 TERMPAINT_COLOR_BRIGHT_BLUE bright blue 13 TERMPAINT_COLOR_BRIGHT_MAGENTA bright magenta 14 TERMPAINT_COLOR_BRIGHT_CYAN bright cyan 15 TERMPAINT_COLOR_WHITE bright white ====== ============================== ==== .. _indexed-colors: Indexed colors .............. The next color space is :c:macro:`TERMPAINT_INDEXED_COLOR`. This is a indexed color space. For most terminals it has 256 colors. Some terminals only implement 88 colors though. Per convention the first 16 colors (0-15) are the same as the named colors. Some terminals handle these differently in combination with the :c:macro:`TERMPAINT_STYLE_BOLD` style. For terminals supporting 256 colors in the default palette the rest of the indices are divided in a 6x6x6 color cube and a 23 step gray ramp (indices 232-255). The defaults are the color cube uses intensity levels of [0, 95, 135, 175, 215, 255] and calculates the components as red is (index-16) / 36, green is (index-16) / 6) % 6 and blue (index-16) % 6. The gray ramp uses the intensity levels of [8, 18, 28, 38, 48, 58, 68, 78, 88, 98, 108, 118, 128, 138, 148, 158, 168, 178, 188, 198, 208, 218, 228, 238]. Of course the index colors are redefinable so users might have a changed palette active. For some terminal implementations using index colors leads to garbled display because not all terminals support parsing the needed control sequences. .. image:: color256.png .. _rgb-colors: RGB colors .......... The last color space is :c:macro:`TERMPAINT_RGB_COLOR`. This is a direct color space which does not allow redefining colors. A color is specified by red, green and blue intensities in the range 0 to 255. For example ``TERMPAINT_RGB_COLOR(255, 128, 64)`` For some terminal implementations using direct rgb colors leads to garbled display because not all terminals support parsing the needed control sequences. Termpaint automatically translates RGB colors to indexed colors using the default palette when terminal capability detection yields that the terminal can not handle them. This detection is not always exact and can be switched to a more conservative mode by disabling the capability ``TERMPAINT_CAPABILITY_TRUECOLOR_MAYBE_SUPPORTED``. In the opposite direction promising the capability ``TERMPAINT_CAPABILITY_TRUECOLOR_SUPPORTED`` disables translation to index colors. .. c:macro:: TERMPAINT_RGB_COLOR_OFFSET(red, green, blue) A macro that returns a rgb color composed of the given values for red, green and blue. The value range is 0 to 255 for each color component Styles ------ Clusters may have one or more styles. The style changes how characters are displayed. The :c:macro:`TERMPAINT_STYLE_INVERSE` style also applies to blank cells. Styles can be enabled and disabled by using the :c:func:`termpaint_attr_set_style()` and :c:func:`termpaint_attr_unset_style()` functions. These functions take one or more of the style macros combined with bitwise or (``|``). Attribute support varies with terminal implementation. .. table:: Available styles :align: left =================== ========= Style Macro =================== ========= bold :c:macro:`TERMPAINT_STYLE_BOLD` inverse :c:macro:`TERMPAINT_STYLE_INVERSE` italic :c:macro:`TERMPAINT_STYLE_ITALIC` blink :c:macro:`TERMPAINT_STYLE_BLINK` underline :c:macro:`TERMPAINT_STYLE_UNDERLINE` double underline :c:macro:`TERMPAINT_STYLE_UNDERLINE_DBL` curly underline :c:macro:`TERMPAINT_STYLE_UNDERLINE_CURLY` strikethrough :c:macro:`TERMPAINT_STYLE_STRIKE` overline :c:macro:`TERMPAINT_STYLE_OVERLINE` =================== ========= Functions --------- See :ref:`safety` for general rules for calling functions in termpaint. .. c:macro:: TERMPAINT_DEFAULT_COLOR A Macro used to denote the terminals default color .. c:macro:: TERMPAINT_NAMED_COLOR A Macro used to denote the first of the named colors. For example use ``TERMPAINT_NAMED_COLOR + 1`` to denote red. .. c:macro:: TERMPAINT_INDEXED_COLOR A Macro used to denote the first indexed color. For example use ``TERMPAINT_NAMED_COLOR + 243`` to denote a mid gray. .. c:macro:: TERMPAINT_RGB_COLOR(r, g, b) A Macro used to denote the rgb direct colors. Parameters are in the range 0 to 255. .. c:function:: termpaint_attr* termpaint_attr_new(unsigned fg, unsigned bg) Creates a new attributes object with the foreground color ``fg`` and background color ``bg``. No styles will be selected. The application has to free this with :c:func:`termpaint_attr_free`. .. c:function:: termpaint_attr* termpaint_attr_clone(termpaint_attr* attr) Creates a new attributes object with the same settings as the attributes object passed in ``attr``. The application has to free this with :c:func:`termpaint_attr_free`. .. c:function:: void termpaint_attr_free(termpaint_attr* attr) Frees a attributes object allocated with :c:func:`termpaint_attr_new()` or :c:func:`termpaint_attr_clone()`. .. c:function:: void termpaint_attr_set_fg(termpaint_attr* attr, unsigned fg) Set the foreground to be used when painting to ``fg``. .. c:function:: void termpaint_attr_set_bg(termpaint_attr* attr, unsigned bg) Set the background to be used when painting to ``bg``. .. c:function:: void termpaint_attr_set_deco(termpaint_attr* attr, unsigned deco_color) Set the decoration color to be used when painting to ``deco_color``. .. c:macro:: TERMPAINT_STYLE_BOLD Style the text in bold. `(widely supported) `__ Some terminal implementations change named colors in the range 0-7 to their bright variants when rendering bold text. .. c:macro:: TERMPAINT_STYLE_ITALIC Style the text in italic. `(widely supported) `__ .. c:macro:: TERMPAINT_STYLE_BLINK Text should blink. `(support varies by terminal implementation) `__ .. c:macro:: TERMPAINT_STYLE_OVERLINE Style the text with a overline. `(limited support in terminal implementations) `__ .. c:macro:: TERMPAINT_STYLE_INVERSE Display the text with inverted foreground and background color. `(widely supported) `__ .. c:macro:: TERMPAINT_STYLE_STRIKE Style the text in strikethrough. `(support varies by terminal implementation) `__ .. c:macro:: TERMPAINT_STYLE_UNDERLINE Style the text with a single underline. `(widely supported) `__ If supported by the terminal emulator the underline uses the decoration color. .. c:macro:: TERMPAINT_STYLE_UNDERLINE_DBL Style the text with a double underline. `(limited support in terminal implementations) `__ If supported by the terminal emulator the underline uses the decoration color. .. c:macro:: TERMPAINT_STYLE_UNDERLINE_CURLY Style the text with a curly underline. `(limited support in terminal implementations) `__ If supported by the terminal emulator the underline uses the decoration color. .. c:function:: void termpaint_attr_set_style(termpaint_attr* attr, int bits) Adds the styles given in ``bits`` to the attributes. .. c:function:: void termpaint_attr_unset_style(termpaint_attr* attr, int bits) Removes the styles given in ``bits`` to the attributes. .. c:function:: void termpaint_attr_reset_style(termpaint_attr* attr) Removes all previously set styles. .. c:function:: void termpaint_attr_set_patch(termpaint_attr* attr, _Bool optimize, const char *setup, const char * cleanup) This function allows to use additional attributes for rendering that are not otherwise explicitly supported. .. warning:: This is a low level feature with potential to garble the whole terminal rendering. Use with care. Allows setting escape sequences to be output before (``setup``) and after (``cleanup``) rendering each cluster with this style. If ``optimize`` is set, do not use ``setup`` and ``cleanup`` between clusters that have the exact same patch. The caller is responsible to ensure that the patches don't break rendering. Setup is output after the "select graphics rendition" escape sequence right before the text of the cluster is output. If ``optimize`` is not set cleanup is output directly following the text of the cluster. If ``optimize`` is true, the setup sequence must not contain "select graphics rendition" sequences because the rendering resets SGR state between clusters if styles change in a display run. termpaint-0.3.1/doc/callback-event-handling.c000066400000000000000000000101631477303547200211010ustar00rootroot00000000000000// Feel free to copy from this example to your own code // SPDX-License-Identifier: 0BSD OR BSL-1.0 OR MIT-0 #include #include #include // snippet-header-start #include "termpaint.h" #include "termpaintx.h" // snippet-header-end // snippet-globals-start termpaint_terminal *terminal; termpaint_surface *surface; void (*current_callback)(void *user_data, termpaint_event* event); void *current_data; // snippet-globals-end // snippet-callback-start void event_callback(void *userdata, termpaint_event *event) { (void)userdata; if (current_callback) { current_callback(current_data, event); } } // snippet-callback-end // snippet-quit-data-start typedef struct quit_dialog_ { void (*saved_callback)(void *user_data, termpaint_event* event); void *saved_data; bool *result; } quit_dialog; // snippet-quit-data-end // snippet-quit-callback-start void quit_dialog_callback(void *userdata, termpaint_event *event) { quit_dialog* dlg = userdata; if (!event) { return; } if (event->type == TERMPAINT_EV_CHAR) { if (event->c.length == 1 && (event->c.string[0] == 'y' || event->c.string[0] == 'Y')) { current_data = dlg->saved_data; current_callback = dlg->saved_callback; *dlg->result = true; free(dlg); current_callback(current_data, NULL); return; } if (event->c.length == 1 && (event->c.string[0] == 'n' || event->c.string[0] == 'N')) { current_data = dlg->saved_data; current_callback = dlg->saved_callback; *dlg->result = false; free(dlg); current_callback(current_data, NULL); return; } } if (event->type == TERMPAINT_EV_CHAR || event->type == TERMPAINT_EV_KEY) { termpaint_surface_write_with_colors(surface, 20, 5, "Please reply with either 'y' for yes or 'n' for no.", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(terminal, false); } } // snippet-quit-callback-end // snippet-quit-ctor-start void quit_dialog_start(bool *result) { quit_dialog* dlg = calloc(1, sizeof(quit_dialog)); dlg->saved_data = current_data; dlg->saved_callback = current_callback; dlg->result = result; current_data = dlg; current_callback = quit_dialog_callback; int fg = TERMPAINT_DEFAULT_COLOR; int bg = TERMPAINT_DEFAULT_COLOR; termpaint_surface_write_with_colors(surface, 20, 4, "Really quit? (y/N)", fg, bg); termpaint_terminal_flush(terminal, false); } // snippet-quit-ctor-end void main_screen_callback(void *userdata, termpaint_event *event) { if (!event) { termpaint_surface_write_with_colors(surface, 0, 0, "There is really nothing else to do than quit!", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(terminal, false); quit_dialog_start(userdata); } } int main(int argc, char **argv) { (void)argc; (void)argv; termpaint_integration *integration; integration = termpaintx_full_integration_setup_terminal_fullscreen( "+kbdsig +kbdsigint", event_callback, NULL, &terminal); surface = termpaint_terminal_get_surface(terminal); termpaint_surface_clear(surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(surface, 0, 0, "Hello World", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(terminal, false); bool quit; current_callback = main_screen_callback; current_data = &quit; quit_dialog_start(&quit); // snippet-main-start while (!quit) { if (!termpaintx_full_integration_do_iteration(integration)) { // some kind of error break; } } termpaint_terminal_free_with_restore(terminal); // snippet-main-end return 0; } termpaint-0.3.1/doc/check.py000077500000000000000000000127051477303547200157360ustar00rootroot00000000000000#! /usr/bin/python3 import json import sys import os d = json.load(open('_build/json/genindex.fjson', 'r')) c_functions = [] c_macros = [] c_types = [] c_enums = [] for letter in d['genindexentries']: for entry in letter[1]: display_name = entry[0] if display_name.endswith(' (C function)'): c_functions.append(display_name[:-13]) elif display_name.endswith(' (C macro)'): c_macros.append(display_name[:-10]) elif display_name.endswith(' (C type)'): c_types.append(display_name[:-9]) elif display_name.endswith(' (C enum)'): c_enums.append(display_name[:-9]) elif display_name.endswith(' (C enumerator)'): pass # ignore else: print("can not guess type of index entry: ", display_name) # keys c_functions.extend(['termpaint_input_enter', 'termpaint_input_space', 'termpaint_input_tab', 'termpaint_input_backspace', 'termpaint_input_context_menu', 'termpaint_input_delete', 'termpaint_input_end', 'termpaint_input_home', 'termpaint_input_insert', 'termpaint_input_page_down', 'termpaint_input_page_up', 'termpaint_input_arrow_down', 'termpaint_input_arrow_left', 'termpaint_input_arrow_right', 'termpaint_input_arrow_up', 'termpaint_input_numpad_divide', 'termpaint_input_numpad_multiply', 'termpaint_input_numpad_subtract', 'termpaint_input_numpad_add', 'termpaint_input_numpad_enter', 'termpaint_input_numpad_decimal', 'termpaint_input_numpad0', 'termpaint_input_numpad1', 'termpaint_input_numpad2', 'termpaint_input_numpad3', 'termpaint_input_numpad4', 'termpaint_input_numpad5', 'termpaint_input_numpad6', 'termpaint_input_numpad7', 'termpaint_input_numpad8', 'termpaint_input_numpad9', 'termpaint_input_escape', 'termpaint_input_f1', 'termpaint_input_f2', 'termpaint_input_f3', 'termpaint_input_f4', 'termpaint_input_f5', 'termpaint_input_f6', 'termpaint_input_f7', 'termpaint_input_f8', 'termpaint_input_f9', 'termpaint_input_f10', 'termpaint_input_f11', 'termpaint_input_f12']) c_functions.extend([]) # intentionally undocumented c_types.extend(['termpaint_integration_private']) # intentionally undocumented c_types.extend(['termpaint_logging_func']) # helper to workaround problems in inline type specification sys.path.insert(0, "/opt/qtcreator-build/llvm-10/clang/bindings/python/") os.environ['CLANG_LIBRARY_PATH'] = '/opt/qtcreator-build/llvm-10-build/lib' from clang.cindex import CursorKind from clang.cindex import Index, TranslationUnit from clang.cindex import SourceLocation, SourceRange from clang.cindex import TokenKind, TokenGroup import clang.cindex clang.cindex.Config.set_library_path('/opt/qtcreator-build/llvm-10-build/lib') function_names_from_source = [] typedef_names_from_source = [] enum_names_from_source = [] macro_names_from_source = [] def get_from_header(header_name): expected_basename = header_name.split('/')[-1] index = Index.create() tu = index.parse(header_name, options=TranslationUnit.PARSE_DETAILED_PROCESSING_RECORD) # | TranslationUnit.PARSE_SKIP_FUNCTION_BODIES for cursor in tu.cursor.get_children(): #print(cursor.kind) basename = str(cursor.location.file).split('/')[-1] if basename != expected_basename: continue #print(cursor.kind, cursor.displayname) if cursor.kind == CursorKind.FUNCTION_DECL: function_names_from_source.append(cursor.spelling) sig = cursor.result_type.spelling + ' ' + cursor.spelling + '(' args = [] for arg in cursor.get_children(): if arg.kind != CursorKind.PARM_DECL: continue args.append(arg.type.spelling + ' ' + arg.spelling) #if cursor.type.is_function_variadic(): # args.append('...') sig += ', '.join(args) + ')' #print(sig) if cursor.kind == CursorKind.TYPEDEF_DECL: typedef_names_from_source.append(cursor.spelling) if cursor.kind == CursorKind.ENUM_DECL: enum_names_from_source.append(cursor.spelling) if cursor.kind == CursorKind.MACRO_DEFINITION and not cursor.spelling.endswith('_INCLUDED'): macro_names_from_source.append(cursor.spelling) get_from_header('../termpaint.h') get_from_header('../termpaint_event.h') get_from_header('../termpaint_image.h') get_from_header('../termpaint_input.h') get_from_header('../termpaintx.h') get_from_header('../termpaintx_ttyrescue.h') def filter(fn): return ( (fn.endswith('_mustcheck') and fn[:-10] in function_names_from_source) or (fn.endswith('_or_nullptr') and fn[:-11] in function_names_from_source)) macro_names_from_source = [name for name in macro_names_from_source if name not in ('_tERMPAINT_PUBLIC', 'TERMPAINTP_CAST')] function_names_from_source = [fn for fn in function_names_from_source if not filter(fn)] #print("undocumented functions: ", set(function_names_from_source) - set(c_functions)) #print("undocumented macros: ", set(macro_names_from_source) - set(c_macros)) #print("undocumented types: ", set(typedef_names_from_source) - set(c_types)) #print("enums: ", enum_names_from_source) #print("number of items: ", len(function_names_from_source) + len(macro_names_from_source) + len(typedef_names_from_source) + len(enum_names_from_source)) for i in (list(set(function_names_from_source) - set(c_functions)) + list(set(macro_names_from_source) - set(c_macros)) + list(set(typedef_names_from_source) - set(c_types)) + list(set(enum_names_from_source) - set(c_enums))): print(i) termpaint-0.3.1/doc/color256.png000066400000000000000000000044531477303547200163660ustar00rootroot00000000000000PNG  IHDR?:PPLTE\\_________ׇ__________________________ׇ__________ׇ_________ׇ_ׇׯ____ׇ_ׯ___ׇׯׇ_________ׇ_ׯ__ׯ______ׯ________________ׯׇׇ_ׇׇׇׇ__ׯׯׯ_ׯׯׯׯ__ׇׯׯ_ׇׯ_ׇׯ_ׯ__&&&000:::DDDNNNXXXbbblllvvvяbKGDH pHYs  tIME",IDATxccW{nۭm۶m۶ݭm۶m۶m[dߛپO?;7''rD$I$I a`X`D#A_(3~$i"W_H Kmlּ&^$c Z[s ~~t~~ O~J}~>c2% gRzr.cOJ$4q6ډ3Cr5&4vwt[͆O%.vKjSگk^ׅ CP{ hKs!{ gk;T\k M /4i]%"lxW$j0*0&ēǂaƃ!<L0 L g?~g?ٯ`r`j'Ot0=3BlBmʟ!6eNBߐ:W:4M99Me^cݽ衅We\,"⣇J9PoS@5;KHu(2WO?~=jumv.)e^eON:*/Ij'7~I_}_mʻF+\!69)1B_6 ѣ/^y+k>SϠeۗU${H /bHZ49^M;p  |0ap8>p$G1g?~g?~_1qp<'IO>NS48΀xp p~g?~gb· B.K |)\p%\g?~g?~7p A`?~g?~$I$I!sk{~g~wog?~g?~$I$N=jkIENDB`termpaint-0.3.1/doc/conf.py000066400000000000000000000043741477303547200156060ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('.')) # -- Project information ----------------------------------------------------- project = 'Termpaint' copyright = '2020, Martin Hostettler' author = 'Martin Hostettler' # The full version, including alpha/beta/rc tags release = '0.3.0' # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] highlight_language = 'c' nitpick_ignore = [ ('c:identifier', 'FILE'), ('c:identifier', 'termios'), ('c:identifier', 'uint16_t'), ('c:identifier', 'uint32_t'), ] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'alabaster' html_theme_options = { 'page_width': '1040px', 'sidebar_width': '190px', 'github_user': 'termpaint', 'github_repo': 'termpaint', 'github_banner': True, } html_show_copyright = False # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] termpaint-0.3.1/doc/details.rst000066400000000000000000000120741477303547200164620ustar00rootroot00000000000000Technical details ================= .. _resync: Handling of ambiguous input --------------------------- Terminal to application communicating doesn't use a prefix free encoding. For example the escape key sends just ASCII 0x1b, but many function key sequences also begin with ASCII 0x1b. As the terminal communication is just a stream of bytes the library needs to use some strategy to decide when no further input is considered part of the input sequence. Many terminal interface libraries use timeouts to disambiguate input. Which on one hand makes behavior timing dependent and too large timeouts interfere with using ESC followed by any other key. Termpaint doesn't use timeouts in the traditional way. Instead when termpaint encounters a sequence that needs disambiguation, it sends a request to the terminal which produces a fixed reply that allows to disambiguate the sequence. In practical terms the sequence used is ``ESC[5n`` to which the terminal replies ``ESC[0n``. The concrete implementation uses a short delay before sending ``ESC[5n`` to reduce overhead. It also only ever keeps one such request in flight at one time. .. _safety: Safety ------ There are various rules an application has to follow to safely interact with functions in termpaint. Parameters ---------- * unless otherwise specified all pointers need to point to valid objects of their type * pointers to opaque structures always need to point to a initialized and valid object. (Exceptions are of course functions that initialize the objects) Life times .......... Objects derived from the terminal object need to have a bounded lifetime that does not exceed the lifetime of the terminal object. The application is responsible for freeing all subobjects before freeing the terminal object. Integrations have to take care not to call into the terminal object after it was freed. Threading and signals ..................... No function in termpaint is async signal safe. Applications must cache all needed information for signal handlers themselves. All objects that are created (transitively) from a terminal and the terminals integration need to be handled as one item for threading. This whole collection of objects may only be called from a one thread at a time. Switching the thread that calls into this collection needs to be mediated by a C happens-before relation. Independent terminal instances can be used without interfering with each other. .. _incremental-update: Terminal update optimization ---------------------------- Each call to :c:func:`termpaint_terminal_flush` takes a copy of the just flushed primary surface. If the next call to ``flush`` does not specify a full redraw, only cells are redrawn that differ from the copy made in the previous call. .. _malloc-failure: Environments that need to handle malloc failure ----------------------------------------------- A restricted subset of termpaint can be used in environments that need to handle malloc failure gracefully. The default mode of operation of termpaint is to abort() on allocation failure for ease of usage and to avoid cluttering code using termpaint with checks. For many applications that are not carefully written to handle allocation failure this is a reasonable tradeoff. For applications that are written carefully enough to handle allocation failures there is an alternative mode, that avoids aborting. For these applications three major differences in usage are needed. The first difference is that for all functions where a variant ending with ``_or_nullptr`` or ``_mustcheck`` is offered the application needs to call these variants. These signal via a return value of NULL or false that memory allocation failed. The application can try to work without their effects or try calling them again later. Consult the header files for which functions need these variants. The next difference is that applications need to call :c:func:`termpaint_terminal_glitch_on_out_of_memory` to switch termpaint to a mode where it does not abort, but potentially misrenders output when memory allocation fails in code paths that can't sensibly report an error. The major parts where this can happen is when writing strings that need more than 8 byte of utf8 encoded characters per cell (i.e. stacks of composing characters or complex emoji sequences) or when using :c:func:`termpaint_attr_set_patch`. The last difference is that the application may only use a limited subset of termpaint. Currently there are the following known restrictions: * :c:func:`termpaint_surface_copy_rect` must not be used with the same surface for source and destination. As workaround preallocate a surface to use as an intermediate surface for such copy operations. * termpaintx is not yet allocation failure safe. Some parts of termpaint like terminal type auto-detection and management of the restore sequence use preallocated memory to avoid memory allocation failures in many code paths. The preallocations might not be large enough if a terminal sends excessive amounts of identifying data or the application uses an extremely high amount of setup (i.e. more colors slots than predefined). termpaint-0.3.1/doc/events.rst000066400000000000000000000330671477303547200163460ustar00rootroot00000000000000Events ====== Terminal input is translated to events. The application handles the events by implementing a event callback and setting it on the terminal object by using the :c:func:`termpaint_terminal_set_event_cb()` function. The primary input events are character events, key events and mouse events. Each event has a ``type`` which describes what kind of event is emitted. An application should ignore events of types that the application does not know how to handle, as from time to time new events will be added. .. _character event: Character events ---------------- Character events are emitted when the user presses a key that has a printable character associated. The ``type`` field will be set to :c:macro:`TERMPAINT_EV_CHAR`. The input character is described by an utf8 string (not null terminated) that is available as ``c.string`` with length ``c.length``. Additionally the field ``c.modifier`` contains a bit field describing the modifier keys held while the character was input. The space key and enter key are handled as key events as described in the following section. .. _key event: Key events ---------- Key events are emitted for key presses on non character keys like space, enter, the edit keys and function keys. The ``type`` field will be set to :c:macro:`TERMPAINT_EV_KEY`. The key is described by the ``key.atom`` field which can be used in two ways. Together with ``key.length`` it contains a (not null terminated) string naming the key. Additionally ``key.atom`` can be compared as pointer to the result of the termpaint_input_* functions that return a unique pointer value that can be used instead of string comparison. See :doc:`keys` for a complete list of keys. Additionally the field ``c.modifier`` contains a bit field describing the modifier keys held while the character was input. .. _modifiers: Modifiers --------- Modifiers is a bit flag of combinations of the following: .. c:macro:: TERMPAINT_MOD_SHIFT The key was pressed while the shift key was held. .. c:macro:: TERMPAINT_MOD_CTRL The key was pressed while the ctrl key was held. .. c:macro:: TERMPAINT_MOD_ALT The key was pressed while the alt key was held. .. c:macro:: TERMPAINT_MOD_ALTGR The key was pressed while the shift alt-gr key (alternate graphics) was held. .. _mouse event: Not all terminals support reporting all modifiers with each key. Generally the function keys and arrow keys have better modifier reporting that the printable characters. Mouse events ------------ Many terminals have optional mouse reporting modes. If these are supported and activated by calling :c:func:`termpaint_terminal_set_mouse_mode` input events for mouse clicks and possibly more events will be produced. The ``type`` field will be set to :c:macro:`TERMPAINT_EV_KEY`. Mouse events consist of ``x`` and ``y`` coordinates of the event. ``modifier`` held while the event was generated and an ``action`` that describes if the event was a click, release or a movement event. For click events the ``button`` number is available. .. _misc-events: Misc events ----------- Various non keyboard related terminal events that don't need additional data. .. c:function:: const char *termpaint_input_focus_in(void) The terminal has received focus. Terminal support varies and is opt-in. Focus change events can be requested using :c:func:`termpaint_terminal_request_focus_change_reports`. String value: ``FocusIn`` .. c:function:: const char *termpaint_input_focus_out(void) The terminal has lost focus. Terminal support varies and is opt-in. Focus change events can be requested using :c:func:`termpaint_terminal_request_focus_change_reports`. String value: ``FocusOut`` .. c:function:: const char *termpaint_input_paste_begin(void) If enabled in the terminal pasted text is bracketed by paste begin and paste end markers. If translation of the whole sequence to paste events is disabled using :c:func:`termpaint_input_handle_paste` then this misc event is emitted on paste start. Terminal support varies and is opt-in. String value: ``PasteBegin`` .. c:function:: const char *termpaint_input_paste_end(void) If enabled in the terminal pasted text is bracketed by paste begin and paste end markers. If translation of the whole sequence to paste events is disabled using :c:func:`termpaint_input_handle_paste` then this misc event is emitted on paste end. Terminal support varies and is opt-in. String value: ``PasteEnd`` .. c:function:: const char *termpaint_input_i_resync(void) This misc event is emitted when the input parser was resynchronized by requesting a device status report due to a incomplete terminal sequence. See :ref:`resync` for details. String value: ``i_resync`` Main event types ---------------- .. c:macro:: TERMPAINT_EV_CHAR A :ref:`character event ` was sent by the terminal. .. c:macro:: TERMPAINT_EV_KEY A :ref:`key event ` was sent by the terminal. .. c:macro:: TERMPAINT_EV_PASTE The terminal sent a clipboard paste event. .. c:macro:: TERMPAINT_EV_AUTO_DETECT_FINISHED The auto detection phase was finished. The application can now create it's user interface. See :ref:`terminal-setup`. The event does not contain additional data. .. c:macro:: TERMPAINT_EV_MOUSE A :ref:`mouse event` was sent by the terminal. .. c:macro:: TERMPAINT_EV_MISC Other simple terminal events. .. c:macro:: TERMPAINT_EV_REPAINT_REQUESTED Termpaint acquired additional data and a repaint could improve the rendering of the user interface. Currently used when after :c:func:`termpaint_terminal_set_color` has made sure the color can be restored to it's original value on restore. The event does not contain additional data. Other event types ------------------- .. c:macro:: TERMPAINT_EV_UNKNOWN An unknown event was sent by the terminal. The event does not contain additional data. .. c:macro:: TERMPAINT_EV_OVERFLOW The internal parsing buffer was discarded because a sequence was to long to fit. The event does not contain additional data. .. c:macro:: TERMPAINT_EV_INVALID_UTF8 The terminal sent invalid utf8 encoded data. .. c:macro:: TERMPAINT_EV_CURSOR_POSITION The terminal sent a cursor position report. .. c:macro:: TERMPAINT_EV_MODE_REPORT The terminal sent a mode report. .. c:macro:: TERMPAINT_EV_COLOR_SLOT_REPORT The terminal sent a color report. .. c:macro:: TERMPAINT_EV_RAW_PRI_DEV_ATTRIB The terminal sent a primary device attributes report. .. c:macro:: TERMPAINT_EV_RAW_SEC_DEV_ATTRIB The terminal sent a secondary device attributes report. .. c:macro:: TERMPAINT_EV_RAW_3RD_DEV_ATTRIB The terminal sent a tertiary device attributes report. .. c:macro:: TERMPAINT_EV_RAW_DECREQTPARM The terminal sent a dec terminal parameters report. .. c:macro:: TERMPAINT_EV_PALETTE_COLOR_REPORT The terminal sent a palette color report. .. c:macro:: TERMPAINT_EV_RAW_TERMINFO_QUERY_REPLY The terminal sent a report for a ``ESCP+????ESC\\`` query. .. c:macro:: TERMPAINT_EV_RAW_TERM_NAME The terminal sent a report for a ``ESC[>q`` query. Termpaint only uses event types >= 0. If the design of an application needs application internal codes that coexist with termpaint event types in the same ``int``-typed variable, it may use negative numbers for these. The event structure ------------------- .. c:type:: termpaint_event :: int type; The type of the events. Depending on the value of ``type`` different parts of the event contain valid data. If ``type`` is :c:macro:`TERMPAINT_EV_CHAR` or :c:macro:`TERMPAINT_EV_INVALID_UTF8`: :: struct { unsigned length; const char *string; int modifier; } c; ``string`` and ``length`` together describe a (non null terminated) string with the raw data from the terminal. ``modifiers`` describes the :ref:`modifiers` held. If ``type`` is :c:macro:`TERMPAINT_EV_CHAR` this describes a key press. If ``type`` is :c:macro:`TERMPAINT_EV_INVALID_UTF8` the terminal sent a invalidly encoded utf8 sequence. If ``type`` is :c:macro:`TERMPAINT_EV_KEY`: :: struct { unsigned length; const char *atom; int modifier; } key; ``atom`` and ``length`` together describe which (non null terminated) key from the table :doc:`keys` was pressed. Alternatively ``atom`` can directly compared to the one of the pointers returned by one of the termpaint_input_* functions. ``modifiers`` describes the :ref:`modifiers` held. If ``type`` :c:macro:`TERMPAINT_EV_MOUSE`: :: struct { int x; int y; int raw_btn_and_flags; int action; int button; // button == 3 means release with unknown button int modifier; } mouse; Each mouse event has a position described by ``x``, ``y`` and the state of the keyboard :ref:`modifiers` held is made available in ``modifier``. ``action`` describes if the event was a button press, button release or mouse pointer move event. Which of these events are sent depends on the mouse mode of the terminals. See :c:func:`termpaint_terminal_set_mouse_mode` for details. For mouse press events the number of the pressed button is available as ``button``. Mouse release and move events have limited support for this information depending on the terminal. Left, middle and right buttons have the numbers 0, 1 and 2 respectively. Button number 3 is used by terminals when no button is held or the button information is not available. .. c:macro:: TERMPAINT_MOUSE_PRESS The button ``button`` was pressed. .. c:macro:: TERMPAINT_MOUSE_RELEASE A mouse button was released. Depending on the terminal ``button`` is either 3 or the number of the just release mouse button. .. c:macro:: TERMPAINT_MOUSE_MOVE The mouse cursor was moved. ``raw_btn_and_flags`` contains a raw and undecoded value from the terminal that contains information from which ``modifiers`` and ``button`` was interpreted. It's available for not yet fully supported extended event information from the terminal. If ``type`` is :c:macro:`TERMPAINT_EV_PASTE`: :: struct { unsigned length; const char *string; _Bool initial; _Bool final; } paste; A paste event. As termpaint does not buffer the pasted characters a paste from the terminal generates many fragmented events. ``initial`` is true if this event belongs to the start of a paste operation and ``final`` is true if this event marks the end of the paste operation. ``string`` and ``length`` together describe a (non null terminated) string with a fragment of the pasted characters. The application should be prepared to get empty fragments and fragments with one or multiple characters. Details how the events are fragmented are subject to change in future versions of the library. If ``type`` is :c:macro:`TERMPAINT_EV_MISC`: :: struct { unsigned length; const char *atom; } misc; A misc event. Available value for atom are described in `Misc Events`_. ``atom`` can directly compared to the one of the pointers returned by one of the functions described there. Alternatively ``atom`` and ``length`` together form a (non null terminated) string that can compared to one of the strings also described in that section. If ``type`` is :c:macro:`TERMPAINT_EV_CURSOR_POSITION`: :: struct { int x; int y; _Bool safe; } cursor_position; A cursor position report. ``x`` and ``y`` contain the cell coordinates of the reported cursor position. If ``safe`` is true the cursor position report was in a format that is not ambiguous with a keyboard event. See :c:func:`termpaint_input_expect_cursor_position_report` for handling of ambiguous cursor position events. If ``type`` is :c:macro:`TERMPAINT_EV_MODE_REPORT` :: struct { int number; int kind; int status; } mode; The terminal send a report for a terminal mode query. If ``kind`` is 1 the report is for a "private" mode, otherwise it's for a non private mode. ``number`` specifies the mode number. ``status`` is the status of the mode and contains a value from the following list: 0 unknown mode 3 mode is set 4 mode is reset If ``event`` is :c:macro:`TERMPAINT_EV_RAW_PRI_DEV_ATTRIB`, :c:macro:`TERMPAINT_EV_RAW_SEC_DEV_ATTRIB`, :c:macro:`TERMPAINT_EV_RAW_3RD_DEV_ATTRIB`, :c:macro:`TERMPAINT_EV_RAW_DECREQTPARM`, :c:macro:`TERMPAINT_EV_RAW_TERMINFO_QUERY_REPLY` or :c:macro:`TERMPAINT_EV_RAW_TERM_NAME`: :: struct { unsigned length; const char *string; } raw; The mostly raw report from the terminal is contained in ``string`` and ``length`` which together describe a (non null terminated) string. If ``type`` is :c:macro:`TERMPAINT_EV_COLOR_SLOT_REPORT`: :: struct { int slot; const char *color; unsigned length; } color_slot_report; A report for a query of a global terminal color was received. ``slot`` contains the number of the slot and ``color`` and ``length`` together describe a (non null terminated) string which contains the color data reported by the terminal. If ``type`` is :c:macro:`TERMPAINT_EV_PALETTE_COLOR_REPORT`: :: struct { int color_index; const char *color_desc; unsigned length; } palette_color_report; A report for a query of a palette terminal color was received. ``color_index`` contains index of the color in the palette and ``color`` and ``length`` together describe a (non null terminated) string which contains the color data reported by the terminal. termpaint-0.3.1/doc/getting-started.c000066400000000000000000000032341477303547200175520ustar00rootroot00000000000000// Feel free to copy from this example to your own code // SPDX-License-Identifier: 0BSD OR BSL-1.0 OR MIT-0 #include // snippet-header-start #include "termpaint.h" #include "termpaintx.h" // snippet-header-end // snippet-callback-start void event_callback(void *userdata, termpaint_event *event) { bool *quit = userdata; if (event->type == TERMPAINT_EV_CHAR) { if (event->c.length == 1 && event->c.string[0] == 'q') { *quit = true; } } if (event->type == TERMPAINT_EV_KEY) { if (event->key.atom == termpaint_input_escape()) { *quit = true; } } } // snippet-callback-end int main(int argc, char **argv) { (void)argc; (void)argv; // snippet-main-start termpaint_integration *integration; termpaint_terminal *terminal; termpaint_surface *surface; bool quit = false; integration = termpaintx_full_integration_setup_terminal_fullscreen( "+kbdsig +kbdsigint", event_callback, &quit, &terminal); surface = termpaint_terminal_get_surface(terminal); termpaint_surface_clear(surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(surface, 0, 0, "Hello World", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(terminal, false); while (!quit) { if (!termpaintx_full_integration_do_iteration(integration)) { // some kind of error break; } } termpaint_terminal_free_with_restore(terminal); // snippet-main-end return 0; } termpaint-0.3.1/doc/getting-started.rst000066400000000000000000000315141477303547200201420ustar00rootroot00000000000000Getting started =============== Termpaint is a low level terminal interface library. The output model is a grid of character cells with :ref:`attributes` specifying colors and style (like bold or underline). Input from the terminal is passed into the library which then calls a user-provided event callback with e.g. character or key press events. The core of termpaint does not do any operating system or environment specific I/O. In the general case the application provides an integration pointer and callbacks for all terminal I/O. This integration can connect to an existing event loop or just wrap blocking terminal I/O. For applications that deal with any kind of asynchronous events, apart from the terminal, it's recommended to use a custom integration or an external integration into an general event loop. But for simple applications that only deal with synchronous data sources termpaint ships with a very simplistic sample integration in a add-on called :doc:`termpaintx`. This integration helps to get started quickly for simple applications. Let's start with a "Hello World" style application. Hello world ----------- First you need to include the pkg-config dependency named ``termpaint`` into your application's build. Next include the needed headers into your source: .. literalinclude:: getting-started.c :caption: includes :start-after: // snippet-header-start :end-before: // snippet-header-end The most code is for the main function: .. literalinclude:: getting-started.c :caption: main code :linenos: :dedent: 4 :start-after: // snippet-main-start :end-before: // snippet-main-end This code starts with a few variables needed later. ``integration`` stores a pointer to the :c:type:`integration object` needed after initialization to run keyboard input handling. ``terminal`` is used for interacting with the terminal as a whole and ``surface`` for interaction with the character cell grid. Finally ``quit`` is used for communication between the event handler (see below) and the loop waiting in the main function. The first call uses :c:func:`termpaintx_full_integration_setup_terminal_fullscreen()` to setup termpaint for full screen usage with the simple termpaintx integration. Its first arguement is a string with :ref:`options for termpaint` and :ref:`options for termpaintx`, followed by the event callback and it's data pointer. The last parameter is an out-parameter and together with the return value they produce the pointers to the integration object and the terminal object. The following line extracts the pointer to the primary surface of the terminal object. Surfaces in termpaint are objects that represent a character cell grid that have functions to allow writing text. The next line clears the whole primary surface (which later is transferred to the terminal) with the given colors. It takes two colors because some terminals behave oddly if cleared space has the same foreground and background color set [#defcol]_. When two colors are specified in termpaint the order is always foreground color and then background color. In this example the :ref:`default colors` are used. These are special colors defined by the terminal. Additionally :ref:`named`, :ref:`indexed` and :ref:`rgb` colors are available. :c:func:`termpaint_surface_write_with_colors` places text on the surface. It takes the position for the text to start, the text itself (in utf-8) and the colors. In termpaint coordinates are always 0-based and start in the upper-left corner. The next line actually displays the content of the primary surface by calling :c:func:`termpaint_terminal_flush()`. The code that follows waits for the user to press ``q`` or the escape key. :c:func:`termpaintx_full_integration_do_iteration` waits for input from the terminal and lets termpaint process the data into events. For deciding if the input matches this example uses the event callback: .. literalinclude:: getting-started.c :caption: event callback :linenos: :start-after: // snippet-callback-start :end-before: // snippet-callback-end This is a very simple callback only suitable for the example. Termpaint uses :doc:`events of various types`. But the most important events are key and character events. :ref:`Character events ` (``TERMPAINT_EV_CHAR``) are emitted for printable characters like letters. :ref:`Key events ` (``TERMPAINT_EV_KEY``) are for other keys like function keys, enter or space. Both additionally contain a bitflag that describes modifiers like alt and ctrl that have been held down while pressing the key. The strings are not nul-terminated. For key events the ``atom`` field can either be used as a string or directly compared to the return of functions like ``termpaint_input_escape()``. See :doc:`keys` for a complete list of keys. Finally before termination the application :c:func:`termpaint_terminal_free_with_restore()` should be called to restore the terminal to it's normal state. See :download:`getting-started.c` for the whole source of this example. Input handling strategies ------------------------- Synchronous usage ................. Termpaint uses an event callback based design for handling input events. This fits well into designs which use a central event processing loop but out of the box it does not fit well for code that wants to synchronously wait for input in many places while processing, e.g. a usage styles where a menu or such is implemented by a function that only returns after the user has completed interacting with it. For such synchronous usage it's recommended to use an callback that just adds events to a application specific queue of events. And then implement a function that passes input from the terminal to termpaint until a new event appears. In this way the application can also filter out events, it does not need, in these functions. As termpaint tries to avoid allocations in steady state operation, the event callback has to copy all needed data out of the original event to a safe place. First we need some data structures and variables to save the events: .. literalinclude:: sync-event-handling.c :linenos: :start-after: // snippet-type-start :end-before: // snippet-type-end Here the key or character is represented by a freshly allocated nul-terminated string. The events are copied into this structure in the event handler: .. literalinclude:: sync-event-handling.c :linenos: :start-after: // snippet-callback-start :end-before: // snippet-callback-end In this case only key or character events are translated, all other events are filtered out. For the events the type, string and modifiers are copied and the new event is added to a linked list. Next we need a function for the application to call to wait for the next event: .. literalinclude:: sync-event-handling.c :linenos: :start-after: // snippet-wait-start :end-before: // snippet-wait-end The ``key_wait`` function either returns an already queued event or if no event is queued it calls :c:func:`termpaintx_full_integration_do_iteration` to wait for terminal data and process it. As soon as enough data is read and processed to give one or more suitable events it stops waiting for data and returns the first event. To easy usage this implementation internally handles freeing the last returned event in the next ``key_wait`` call. Now the application can synchronously wait for events like this: .. literalinclude:: sync-event-handling.c :linenos: :dedent: 4 :start-after: // snippet-main-start :end-before: // snippet-main-end Remember to flush the primary surface to make sure that the user can see the most recent content while the application waits for input. Or maybe even move the flush into the wait function, if so desired. Given such an synchronous input function, parts of the user interface can be written as stand-alone functions like this: .. literalinclude:: sync-event-handling.c :linenos: :start-after: // snippet-menu-start :end-before: // snippet-menu-end See :download:`sync-event-handling.c` for the whole source of this example. Callback usage .............. Using callbacks directly needs some kind of event dispatching in the application. There are many ways this could be done, ranging in granularity from having one event processing function for the whole application over a function per "page" or having a fine grained "widget" structure with elaborate event routing rules. A simple way to do event routing is to have global variables for an event handler and a void pointer for it's data and switch those around while the user moves through the application. A very rough version of something like this could look like this: Some global variables for drawing and event handling: .. literalinclude:: callback-event-handling.c :linenos: :start-after: // snippet-globals-start :end-before: // snippet-globals-end Instead of passing the terminal and surface object around the whole application as parameters, having them as globals simplifies the code a lot. The global ``current_callback`` and it's ``current_data`` allows switching which function will get events to process while the user moves through the application. .. literalinclude:: callback-event-handling.c :linenos: :start-after: // snippet-callback-start :end-before: // snippet-callback-end The callback passed to the terminal object just passes the event on to the currently active event processing function. The following is a simple confirm dialog. It can be started with a pointer to a bool where it stores it's result. When started it replaces the currently active event handler with it's own. When it is finished it saves the result, restores the previous handler and calls it without an event to indicate that the result is ready. First the confirm dialog needs a place to store it's data: .. literalinclude:: callback-event-handling.c :linenos: :start-after: // snippet-quit-data-start :end-before: // snippet-quit-data-end Than the start function can save the old event handler and the result pointer and draw the initial user interface: .. literalinclude:: callback-event-handling.c :linenos: :start-after: // snippet-quit-ctor-start :end-before: // snippet-quit-ctor-end Finally the event handling function reacts to user input and when finished saves the result, restores the event handler and calls it: .. literalinclude:: callback-event-handling.c :linenos: :start-after: // snippet-quit-callback-start :end-before: // snippet-quit-callback-end When using callbacks as primary means of composing the application logic, the main function just needs to wait for events in one central place. Thus the central loop can simply be: .. literalinclude:: callback-event-handling.c :linenos: :dedent: 4 :start-after: // snippet-main-start :end-before: // snippet-main-end See :download:`callback-event-handling.c` for the whole source of this example. Event loop usage ................ Existing event loops differ in their API in various details, but the general approach is fairly similar between common event loops. Currently the only known library to integrate termpaint into a general purpose event loop is `Tui Widgets `_ which provides a widget framework using the event loop from QtCore. The general steps are: * Setup the operating system terminal I/O interface for unbuffered input and output processing On \*nix like operation systems this generally means saving the current settings via ``tcgetattr`` and configuring the needed new settings. Perhaps using :c:func:`termpaintx_fd_set_termios`. * Bridge input from the terminal file descriptor to termpaint After registering an input available notifier a typical implemantation would be:: gsize amount; g_io_channel_read_chars(channel, buf, sizeof(buf) - 1, &amount, NULL); termpaint_terminal_add_input_data(terminal, buf, amount); * Pass output by termpaint to the operation system terminal interface For best performance the output from termpaint should be buffered and send in reasonable blocks to the terminal. The integration will receive output date via it's ``write`` callback and needs to flush it's output buffer when the ``flush`` callback is called. For best performance the integration should additionally use :c:func:`termpaint_integration_set_request_callback` to enable a mechanism for a delayed callback used in cases where terminal input can not be readily parsed without knowing if additional data is on it's way from the terminal to the application. .. rubric:: Footnotes .. [#defcol] TERMPAINT_DEFAULT_COLOR is special as it is replaced by the terminals with globally set colors. So using TERMPAINT_DEFAULT_COLOR as foreground and background doesn't actually count as using the same color. termpaint-0.3.1/doc/image.rst000066400000000000000000000040471477303547200161200ustar00rootroot00000000000000Addon image =========== The image addon contains functions to store the contents of a :c:type:`termpaint_surface` into a file or memory buffer and to load the contents from a file or memory buffer. This addon is only available if your compilation environment supports c++. Functions --------- See :ref:`safety` for general rules for calling functions in termpaint. .. c:function:: _Bool termpaint_image_save(termpaint_surface *surface, const char* name) Save the contents if the surface ``surface`` into a file with the name ``name``. .. c:function:: bool termpaint_image_save_to_file(termpaint_surface *surface, FILE *file) Save the contents if the surface ``surface`` into a file referred to by file pointer ``file``. .. c:function:: char *termpaint_image_save_alloc_buffer(termpaint_surface *surface) Save the contents if the surface ``surface`` into a freshly allocated nul-terminated buffer. After usage the returned buffer must be deallocated using termpaint_image_save_dealloc_buffer. .. c:function:: void termpaint_image_save_dealloc_buffer(char *buffer) Deallocate a buffer returned by termpaint_image_save_alloc_buffer. .. c:function:: termpaint_surface *termpaint_image_load(termpaint_terminal *term, const char *name) Load the contents of a surface from the file named ``name`` and return a newly allocated surface with the data from the file prepared to be used with the terminal object ``term``. .. c:function:: termpaint_surface *termpaint_image_load_from_file(termpaint_terminal *term, FILE *file) Load the contents of a surface from the file referred to by file pointer ``file`` and return a newly allocated surface with the data from the file prepared to be used with the terminal object ``term``. .. c:function:: termpaint_surface *termpaint_image_load_from_buffer(termpaint_terminal *term, char *buffer, int length) Load the contents of a surface from a memory buffer ``buffer`` with the length ``length`` and return a newly allocated surface with the data from the file prepared to be used with the terminal object ``term``. termpaint-0.3.1/doc/index.rst000066400000000000000000000055741477303547200161530ustar00rootroot00000000000000.. Termpaint documentation master file Termpaint ========= Termpaint is a low level terminal interface library for character-cell terminals in the tradition of VT1xx (like xterm, etc). It's designed to be portable and flexible to integrate. It covers event handling and rendering. Features -------- * works with and without an central event loop * robust input handling, unknown key events are gracefully filtered out * truecolor, soft line breaks, explicit control of trailing whitespace * double and curly underlines, custom underline colors * flexible support for program supplied additional formatting like hyperlinks * block, underline and bar cursor shape * simple grid of character cell design, support for wide characters * does not depend on correctly set $TERM or terminfo database * mouse event handling * tagged paste * mostly utf-8 based, string width routines also handle utf-16 and utf-32 * offscreen surfaces/layers * interface with opaque structures designed for ABI stability (but breaking changes are still happening) * possible to use where allocation failure needs to be handled gracefully * does not use global variables where possible, can handle multiple terminals in one process * input parsing subset usable standalone * permissively licensed: Boost Software License 1.0 Does not contain: * ready made user interface elements (form, menu or similar) * window or panel abstractions * support for non utf-8 capable terminals Termpaint is meant as a basic building block to build more specific libraries upon. There are a lot of different higher layer styles, so it's cleaner to have separate libraries for this. .. TODO link more libraries when something is released in this space Known libraries using Termpaint: * `Tui Widgets `_ implements various common UI widgets based on QtCore as platform abstraction. Minimal example --------------- A "hello world", using the internal default operating system integration and opinionated default setup. See :doc:`getting-started` for full source. .. literalinclude:: getting-started.c :caption: main code :start-after: // snippet-main-start :end-before: // snippet-main-end .. literalinclude:: getting-started.c :caption: event callback :start-after: // snippet-callback-start :end-before: // snippet-callback-end Support ------- It's known to work on * xterm * vte * rxvt-unicode * mintty * iTerm2 * microsoft terminal * putty * konsole * linux * freebsd * and more. The core library (but not the OS integration and the meson build system) only depend on C11 (plus a few common string functions like strdup). .. toctree:: :maxdepth: 2 :caption: Contents: getting-started terminal integration surface attributes measuring events details termpaint_input termpaintx image Indices and tables ================== * :ref:`genindex` * :ref:`search` termpaint-0.3.1/doc/integration.rst000066400000000000000000000241001477303547200173510ustar00rootroot00000000000000Integration =========== .. c:type:: termpaint_integration The core termpaint library does not come with integration into the operating system's input/output channels. It's designed to be integrated into synchronous or asynchronous program designs by a pluggable integration abstraction. There are auxiliary functions in the termpaintx_* namespace that have some common code that can be used for integrations. But this code is fairly limited, so if more capabilities are needed feel free to copy this code into your project. To use the simple premade integration see the :doc:`termpaintx addon`. A user supplied integration consists of 3 major parts: terminal to termpaint communication, termpaint to terminal communication and terminal interface setup. Termpaint uses a semi-opaque struct which contains callbacks for calling the application provided integration code when needed. The :c:type:`termpaint_integration` structure can be used as part of a struct in the application provided integration code. It must be initialized by calling :c:func:`termpaint_integration_init()` with the mandatory callbacks. Additional optional callbacks can then be set with additional functions. When the integration is no longer needed the allocated resources have to be freed by calling :c:func:`termpaint_integration_deinit`, possibly from the integrations ``free`` callback. The integration is connected to a terminal object by passing a pointer to its :c:type:`termpaint_integration` struct to :c:func:`termpaint_terminal_new` when creating the terminal object. Input bytes from the terminal to termpaint need to be passed to :c:func:`termpaint_terminal_add_input_data`. If enough bytes have accumulated to identify a input sequence termpaint will call the event callback set by the application using :c:func:`termpaint_terminal_set_event_cb` with the interpreted :doc:`event `. The integration needs to take care, that :c:func:`termpaint_terminal_add_input_data` is not called recursively from the callbacks set on the terminal. Some platforms have kernel level terminal processing that needs to be configured for termpaint to work. On \*nix-like platforms the kernel tty interface can be setup with :c:func:`termpaintx_fd_set_termios`. For details see the implementation of that function. In general the terminal interface should be set to disable all kernel interpretation and transformation features. If keyboard signal handling (ctrl-c, etc) is needed it can be left enabled. But in that case the terminal object needs to be configured with ``+kbdsig`` to avoid switching keyboard input into advanced modes that would be incompatible with kernel signal generation. In addition to the kernel interface the terminal needs to be setup using configuration sequences. For this :c:func:`termpaint_terminal_setup_fullscreen` needs to be called with the size of the terminal. All callbacks of the integration receive a pointer to the integration struct as the first parameter. If the integration just uses global variables the pointer can be ignored. If the integration itself uses a struct with data members the recommended setup is to begin the custom struct with :c:type:`termpaint_integration`:: struct custom_integration { termpaint_integration base; // additional members go here. } Then the callbacks can just cast their first argument to a pointer to the custom struct. On \*nix-like operating systems the integration should arrange for proper cleanup if the application is suddenly terminated (e.g. a crash). The traditional way is to install signal handlers for various fatal signals and do the cleanup before terminating the application. All functions in termpaint are unsafe for use in signal handlers, so it's the job of the integration to save all needed information before the signal happens. There are two major parts of state to restore. The first is the kernel terminal layer configuration, which can simply be saved before changing it to the needed values for termpaint. The second is the state of the terminal itself that needs to be restored by outputting a sequence of characters to the terminal. This sequence can change as different features are used, thus the integration should set a callback via :c:func:`termpaint_integration_set_restore_sequence_updated` and save a copy of that data in a place where the signal handler can safely access it. An alternative without installing signal handlers is to use a auxiliary watchdog process to restore the terminal state. The :doc:`termpaintx addon` contains functions for such an watchdog process. See :c:func:`termpaintx_ttyrescue_start_or_nullptr` for details. Another signal handler is needed to detect terminal size changes. \*nix-like systems raise an ``SIGWINCH`` signal if the terminal size changes. This signal is best handled asynchronously (e.g. by using an event loop's signal support or using a self pipe). Outside of signal context the integration can retrieve the new terminal size using the ``TIOCGWINSZ`` ioctl and resize the terminals primary surface to match using :c:func:`termpaint_surface_resize`. Functions --------- See :ref:`safety` for general rules for calling functions in termpaint. .. c:function:: void termpaint_integration_init(termpaint_integration *integration, void (*free)(termpaint_integration *integration), void (*write)(termpaint_integration *integration, const char *data, int length), void (*flush)(termpaint_integration *integration)) This function initializes a ``termpaint_integration`` structure and sets the 3 mandatory callback functions. All of the callbacks must be set to a non-NULL value. The callbacks are ``void (*write)(termpaint_integration *integration, char *data, int length)`` This callback is called by termpaint to write bytes to the terminal. The application needs to implement this function so that ``length`` bytes of data starting at ``data`` are passed to the terminal. The data should be buffered for best performance. Termpaint will call the ``flush`` callback when the buffered data needs to be transmitted to the terminal. ``void (*flush)(termpaint_integration *integration)`` This callback will be called when the data written using the ``write`` callback needs to be transmitted to the terminal. ``void (*free)(termpaint_integration *integration)`` This callback is invoked when the terminal using this integration is deallocated. This function has to be provided, but may be just a empty function if the memory of the integration is managed externally. .. c:function:: void termpaint_integration_deinit(termpaint_integration *integration) This function frees resources internally held by a initialized ``termpaint_integration`` structure. It must be called exactly once for each ``termpaint_integration`` structure initialized by :c:func:`termpaint_integration_init`. .. c:function:: void termpaint_integration_set_request_callback(termpaint_integration *integration, void (*request_callback)(termpaint_integration *integration)) Sets the optional callback ``request_callback``: ``void (*request_callback)(termpaint_integration *integration)`` With terminal input there are often cases where sequences might be finished or just the start of a longer sequence. In this case termpaint forces to terminal to output additional data so it can make the decision what interpretation is correct. If this callback is set it allows termpaint to delay these commands for a short while to wait for additional bytes from the terminal. If this callback is implemented the application needs to remember that this callback was called and after a short delay (while processing terminal input in the usual way) call :c:func:`termpaint_terminal_callback` on the terminal. If this callback is invoked multiple times before the application calls :c:func:`termpaint_terminal_callback` one call is sufficient. See also :ref:`resync`. .. c:function:: void termpaint_integration_set_restore_sequence_updated(termpaint_integration *integration, void (*restore_sequence_updated)(termpaint_integration *integration, const char *data, int length)) Sets the optional callback ``restore_sequence_updated``: ``void (*restore_sequence_updated)(termpaint_integration *integration, const char *data, int length)`` This callback is invoked every time the sequence to reset the terminal changes. This allows to cache a current value to be used in crash recovery or suspend signal handlers where :c:func:`termpaint_terminal_restore_sequence` can not be used. The restore sequence can change over time as additional terminal configuration is requested (e.g. mouse modes, set title or global color changes). .. c:function:: void termpaint_integration_set_is_bad(termpaint_integration *integration, _Bool (*is_bad)(termpaint_integration *integration)) Sets the optional callback ``is_bad``: ``_Bool (*is_bad)(termpaint_integration *integration)`` This callback should return false, as long as the connection to the terminal is functional. .. c:function:: void termpaint_integration_set_awaiting_response(termpaint_integration *integration, void (*awaiting_response)(termpaint_integration *integration)) Sets the optional callback ``awaiting_response``: ``void (*awaiting_response)(termpaint_integration *integration)`` This callback is invoked when termpaint sends queries to the terminal. This can be used to decide if the integration should wait for a little while when restoring the terminal while reading and discarding input to avoid leaving responses to these queries in flight that might confuse the next application accessing the terminal. .. c:function:: void termpaint_integration_set_logging_func(termpaint_integration *integration, void (*logging_func)(termpaint_integration *integration, const char *data, int length)) Sets the optional callback ``logging_func``: ``void (*logging_func)(termpaint_integration *integration, const char *data, int length)`` This callback receives logging messages. Some error messages are always logged if this callback is specified. Additional messages can be enabled by :c:func:`termpaint_terminal_set_log_mask`. termpaint-0.3.1/doc/keys.rst000066400000000000000000000044171477303547200160120ustar00rootroot00000000000000:orphan: Keys ==== .. table:: :align: left ================================== ======= Function Name ================================== ======= termpaint_input_enter Enter termpaint_input_space Space termpaint_input_tab Tab termpaint_input_backspace Backspace termpaint_input_context_menu ContextMenu termpaint_input_delete Delete termpaint_input_end End termpaint_input_home Home termpaint_input_insert Insert termpaint_input_page_down PageDown termpaint_input_page_up PageUp termpaint_input_arrow_down ArrowDown termpaint_input_arrow_left ArrowLeft termpaint_input_arrow_right ArrowRight termpaint_input_arrow_up ArrowUp termpaint_input_numpad_divide NumpadDivide termpaint_input_numpad_multiply NumpadMultiply termpaint_input_numpad_subtract NumpadSubtract termpaint_input_numpad_add NumpadAdd termpaint_input_numpad_enter NumpadEnter termpaint_input_numpad_decimal NumpadDecimal termpaint_input_numpad0 Numpad0 termpaint_input_numpad1 Numpad1 termpaint_input_numpad2 Numpad2 termpaint_input_numpad3 Numpad3 termpaint_input_numpad4 Numpad4 termpaint_input_numpad5 Numpad5 termpaint_input_numpad6 Numpad6 termpaint_input_numpad7 Numpad7 termpaint_input_numpad8 Numpad8 termpaint_input_numpad9 Numpad9 termpaint_input_escape Escape termpaint_input_f1 F1 termpaint_input_f2 F2 termpaint_input_f3 F3 termpaint_input_f4 F4 termpaint_input_f5 F5 termpaint_input_f6 F6 termpaint_input_f7 F7 termpaint_input_f8 F8 termpaint_input_f9 F9 termpaint_input_f10 F10 termpaint_input_f11 F11 termpaint_input_f12 F12 ================================== ======= termpaint-0.3.1/doc/measuring.rst000066400000000000000000000256101477303547200170270ustar00rootroot00000000000000Measuring Text =============== .. c:type:: termpaint_text_measurement The text measurement functions are offered in variants for utf8, utf16 and utf32 to allow for flexibility in the storage format applications use for bulk data storage. All functions should behave identical regardless of utf-variant when the results are used according to the relevant code unit size. Text is printed on a grid of cells. Letters like ``a`` use one cell each. But not every unicode codepoint uses one cell. There are wide characters and characters that modify the preceding character. This text measuring api is meant to be able to cover these special cases. Currently it supports single cell characters, double cell characters (like Japanese characters and some emoji) and zero width combining characters. Logically text strings are separated into clusters. Each cluster is a unit that is displayed as one unbreakable symbol. A cluster has a width in cells, a width in codepoints and a width in code units (bytes for utf-8, 16bit words for utf-16). A simple example to measure the first cluster in a utf-16 string looks like this :: termpaint_text_measurement *tm = termpaint_text_measurement_new(surface); termpaint_text_measurement_set_limit_clusters(tm, 1); termpaint_text_measurement_feed_utf16(tm, data, size, true); int codePoints = termpaint_text_measurement_last_codepoints(tm); int codeUnits = termpaint_text_measurement_last_ref(tm); int columns = termpaint_text_measurement_last_width(tm); termpaint_text_measurement_free(tm); The termpaint_text_measurement_set_limit\_\* function allow to set how much of the string is measured in one call to :c:func:`termpaint_text_measurement_feed_utf16()`. The feed function returns true if one of the set limits was reached. When no limit is set the feed function will always return false. If more than one limit is set, the limit that is reached first is significant. After :c:func:`termpaint_text_measurement_feed_utf16()` returns true, the termpaint_text_measurement_last\_\* family of functions can be used to check the measurements up to and including the last cluster that did not exceed any of the limits. The :c:func:`termpaint_text_measurement_feed_utf16()` function is designed to allow measuring text that is stored in multiple separate segments in memory (like in a rope or a linked list of substrings). If it returns false the application can call it again with data from the next segment. All calls except for the call for the last segment have to be made with the ``final`` parameter set to false. To ensure the last cluster in a string is properly measured, the last (or only – when not using segments) call to :c:func:`termpaint_text_measurement_feed_utf16()` has to be made with the ``final`` parameter set to true. This is because the last cluster could still be extended by additional non spacing marks in the next segment of a string if it would not have been the last segment. After a limit is reached the measurement can be continued by setting a new limit and resuming with calling :c:func:`termpaint_text_measurement_feed_utf16()` starting from the next code unit after cluster where the limit was reached. In practice this mean starting from the ``termpaint_text_measurement_last_ref(tm)``-th code unit in the original string. (When using segments this count includes code units from the previous segments. It is possible that resuming a measurement needs restarting in a previous segment) The :c:func:`termpaint_text_measurement_feed_utf8` and :c:func:`termpaint_text_measurement_feed_utf32` functions work using the same principle, just with a different encoding for input and different code units sizes for :c:func:`termpaint_text_measurement_last_ref`. Mixing different variants in one measurement is possible as long as switching between utf-variants is done only at codepoint boundaries, but should still be avoided because it makes interpreting the results very hard. :c:func:`termpaint_text_measurement_feed_codepoint` allows feeding in codepoints from other storage formats one by one. It works similar, but with freely definable meaning of what exactly :c:func:`termpaint_text_measurement_last_ref` means, as the increments for each codepoint is supplied by the user. Functions --------- See :ref:`safety` for general rules for calling functions in termpaint. .. c:function:: termpaint_text_measurement* termpaint_text_measurement_new(termpaint_surface *surface) Create a new text measurement object. The application has to free this with :c:func:`termpaint_text_measurement_free()`. The lifetime of this object must not exceed the lifetime of the terminal object originating the passed surface. .. c:function:: void termpaint_text_measurement_free(termpaint_text_measurement *m) Frees the text measurement object. .. c:function:: void termpaint_text_measurement_reset(termpaint_text_measurement *m) Resets the text measurement object to enable use for a fresh measurement. It removes all limits and resets the state back to zero clusters, columns, codepoints and code units. .. c:function:: int termpaint_text_measurement_last_codepoints(termpaint_text_measurement *m) Returns the number of code points up to and including the last measured cluster not exceeding any set limits. .. c:function:: int termpaint_text_measurement_last_clusters(termpaint_text_measurement *m) Returns the number of clusters up to and including the last measured cluster not exceeding any set limits. .. c:function:: int termpaint_text_measurement_last_width(termpaint_text_measurement *m) Returns the width in cells of the text up to and including the last measured cluster not exceeding any set limits. .. c:function:: int termpaint_text_measurement_last_ref(termpaint_text_measurement *m) If using :c:func:`termpaint_text_measurement_feed_utf8()`: Returns the number of bytes up to and including the last measured cluster not exceeding any set limits. If using :c:func:`termpaint_text_measurement_feed_utf16()`: Returns the number of utf16 code units up to and including the last measured cluster not exceeding any set limits. If using :c:func:`termpaint_text_measurement_feed_utf32()`: Returns the same as :c:func:`termpaint_text_measurement_last_codepoints()` If using :c:func:`termpaint_text_measurement_feed_codepoint()`: Returns the sum of all ``ref_adjust`` values up to and including the last measured cluster not exceeding any set limits. .. c:function:: int termpaint_text_measurement_pending_ref(termpaint_text_measurement *m) Like :c:func:`termpaint_text_measurement_last_ref` but also include code units that belong the cluster currently in processing (if any). .. c:function:: void termpaint_text_measurement_set_limit_codepoints(termpaint_text_measurement *m, int new_value) Sets the limit for codepoints. -1 means no limit. The limit must be greater than the current position. .. c:function:: int termpaint_text_measurement_limit_codepoints(termpaint_text_measurement *m) Returns the value set using :c:func:`termpaint_text_measurement_set_limit_codepoints()`. .. c:function:: void termpaint_text_measurement_set_limit_clusters(termpaint_text_measurement *m, int new_value) Sets the limit for clusters. -1 means no limit. The limit must be greater than the current position. .. c:function:: int termpaint_text_measurement_limit_clusters(termpaint_text_measurement *m) Returns the value set using :c:func:`termpaint_text_measurement_set_limit_clusters()`. .. c:function:: void termpaint_text_measurement_set_limit_width(termpaint_text_measurement *m, int new_value) Sets the limit for the width. -1 means no limit. The limit must be greater than the current position. .. c:function:: int termpaint_text_measurement_limit_width(termpaint_text_measurement *m) Returns the value set using :c:func:`termpaint_text_measurement_set_limit_width()`. .. c:function:: void termpaint_text_measurement_set_limit_ref(termpaint_text_measurement *m, int new_value) Sets the limit for reference. Depending on the feed function used the reference is in utf32, utf16 or utf8 code units. -1 means no limit. The limit must be greater than the current position. .. c:function:: int termpaint_text_measurement_limit_ref(termpaint_text_measurement *m) Returns the value set using :c:func:`termpaint_text_measurement_set_limit_ref()`. .. c:function:: _Bool termpaint_text_measurement_feed_utf8(termpaint_text_measurement *m, const char *code_units, int length, _Bool final) Add the utf8 encoded string starting at ``code_units`` with length ``length`` to the measurement. Set ``final`` to true if this is the last segment of the to be measured string. See termpaint_text_measurement_last\_\* for functions to retrieve the measurement results. If no limits are set, it always returns false. Otherwise returns ``false`` if no limit was reached. Returns true if the limit was reached while measuring. .. c:function:: _Bool termpaint_text_measurement_feed_utf16(termpaint_text_measurement *m, const uint16_t *code_units, int length, _Bool final) Add the utf16 encoded string (in host endianness) starting at ``code_units`` with length ``length`` to the measurement. Set ``final`` to true if this is the last segment of the to be measured string. See termpaint_text_measurement_last\_\* for functions to retrieve the measurement results. If no limits are set, it always returns false. Otherwise returns ``false`` if no limit was reached. Returns true if the limit was reached while measuring. .. c:function:: _Bool termpaint_text_measurement_feed_utf32(termpaint_text_measurement *m, const uint32_t *chars, int length, _Bool final) Add the utf32 encoded string (in host endianness) starting at ``chars`` with length ``length`` to the measurement. Set ``final`` to true if this is the last segment of the to be measured string. See termpaint_text_measurement_last\_\* for functions to retrieve the measurement results. If no limits are set, it always returns false. Otherwise returns ``false`` if no limit was reached. Returns true if the limit was reached while measuring. .. container:: hidden-references .. c:macro:: TERMPAINT_MEASURE_NEW_CLUSTER .. c:macro:: TERMPAINT_MEASURE_LIMIT_REACHED .. c:function:: int termpaint_text_measurement_feed_codepoint(termpaint_text_measurement *m, int ch, int ref_adjust) This is a low level function used to implement the termpaint_text_measurement_feed_utf* family of functions. It adds a single codepoint to the measurement and returns a bit flag with the result. if ``TERMPAINT_MEASURE_NEW_CLUSTER`` is set: The added code point started a new cluster. The information about the previous cluster is now available using the termpaint_text_measurement_last_* functions. if ``TERMPAINT_MEASURE_LIMIT_REACHED`` is set: The limit was reached while measuring. See termpaint_text_measurement_last_* for function to retrieve the measurement results. To continue measuring the measurement needs to be restarted at the point where the limit was reached. termpaint-0.3.1/doc/requirements.txt000066400000000000000000000000771477303547200175670ustar00rootroot00000000000000MarkupSafe <2.0 Jinja2 <3.0 sphinx >=3.3.1,<4 sphinx-autobuild termpaint-0.3.1/doc/surface.rst000066400000000000000000000441071477303547200164670ustar00rootroot00000000000000Surface ======= .. c:type:: termpaint_surface Surfaces are the abstraction for output into the terminal. Each :doc:`terminal object` has a primary surface that is used for output. A pointer to the primary surface can be obtained by the :c:func:`termpaint_terminal_get_surface()` function and output can be triggered by the :c:func:`termpaint_terminal_flush()` function. Additionally there can be any number of offscreen surfaces for a terminal. These can be created by :c:func:`termpaint_terminal_new_surface()`. The offscreen surfaces can by copied to other surfaces using the :c:func:`termpaint_surface_copy_rect()` function to ultimately either copy to the primary surface or to save the contents to external storage. Output to a surface is done using the :c:func:`termpaint_surface_write_*` family of functions for output of text or symbols and the :c:func:`termpaint_surface_clear_rect*` family of functions for clearing areas of the surface. The surface is a rectangle of cells. The cells each have the same size. The smallest unit of text that can be rendered is a cluster that represents a "character" and covers one cell or two cells next to each other on a line. Each cluster additionally has a foreground color, a background color, a decoration color and a combination of styles. .. note:: In many terminal the foreground color and some attributes are even important for blank cells. For example text selection might not display properly if foreground and background color are the same. For best compatibility always use a combination of foreground and background color that has decent contrast. Blank space at the end of a line is handled specially in many terminals. For example, for the terminal native text selection, a range of cells at the end of the line can be ignored for text selection's contents if they are detected as insignificant. Termpaint allows marking cells as erased to mirror this behavior. All cells at the very end of a line that are marked as erased are send to the terminals as insignificant, if supported by the terminal. .. container:: hidden-references .. c:macro:: TERMPAINT_ERASED When using one of the clear functions or writing ``TERMPAINT_ERASED`` characters in a surface these cells will be counted as erased and not be considered significant spaces at the end of a line in supported terminals. Terminals commonly group lines into paragraphs to determine if text that is copied with the terminal native clipboard support should contain line breaks between lines of the terminal display. For compatible terminals termpaint can connect subsequent lines into a paragraph using soft wrap markers. If a line ends in a cell with a soft wrap marker and the following line begins with a cell with a soft wrap marker termpaint will connect both lines into a paragraph. Applications can set those using :c:func:`termpaint_surface_set_softwrap_marker` after writing text to the cell. Many surface function take an pointer to an :doc:`attribute object` to specify the colors and styles to be used for the modified cells. Furthermore there are functions to read back the current contents of a surface (see the :c:func:`termpaint_surface_peek_*` family of functions) and to compare the contents of two surfaces (:c:func:`termpaint_surface_same_contents()`) as well are changing the color of the cells on a surface (:c:func:`termpaint_surface_tint()`). Functions --------- See :ref:`safety` for general rules for calling functions in termpaint. .. c:function:: termpaint_surface *termpaint_terminal_new_surface(termpaint_terminal *term, int width, int height) Creates an new off-screen surface for usage with terminal object ``term``. The new surface has the size ``width`` columns by ``height`` lines and is initialized with spaces with default attributes (TERMPAINT_DEFAULT_COLOR for all colors). The application has to free this with :c:func:`termpaint_surface_free`. The lifetime of this object must not exceed the lifetime of the passed terminal object. .. c:function:: termpaint_surface *termpaint_surface_new_surface(termpaint_surface *surface, int width, int height) Like :c:func:`termpaint_terminal_new_surface` but instead of directly requiring a terminal object uses a existing surface for that terminal object. The lifetime of this object must not exceed the lifetime of the terminal object originating the passed surface. .. c:function:: termpaint_surface *termpaint_surface_duplicate(termpaint_surface *surface) Creates an new off-screen surface for usage with terminal object for which the source surface ``surface`` was created. The new surface has the same size as the source surface ``surface`` and is initialized with a copy of the source surface ``surface`` content. The application has to free this with :c:func:`termpaint_surface_free`. .. c:function:: void termpaint_surface_free(termpaint_surface *surface) Frees a surface allocated with :c:func:`termpaint_terminal_new_surface`. This must not be called on the primary surface of a terminal object, because that is owned by the terminal object. .. c:function:: void termpaint_surface_resize(termpaint_surface *surface, int width, int height) Change the size of a surface to ``width`` columns by ``height`` lines. The current contents is erased as if the surface had been freshly created by :c:func:`termpaint_terminal_new_surface`. .. c:function:: int termpaint_surface_width(const termpaint_surface *surface) Returns the current width of the surface. .. c:function:: int termpaint_surface_height(const termpaint_surface *surface) Returns the current height of the surface. .. c:function:: void termpaint_surface_write_with_attr(termpaint_surface *surface, int x, int y, const char *string, const termpaint_attr *attr) Write a text given in the null terminated utf8 string ``string`` to the surface starting in cell ``x``, ``y``. Uses ``attr`` as attributes for all newly placed characters. The length of the run of cells where the characters will be placed can be calculated using the :doc:`string measurement functions`. If any modified cells have previously been part of a multicell character cluster the cluster as a whole is erased. Cells not overwritten will keep their previous attributes (colors, etc). If ``string`` contains the special character :c:macro:`TERMPAINT_ERASED` the corresponding cells are marked as erased. .. c:function:: void termpaint_surface_write_with_len_attr(termpaint_surface *surface, int x, int y, const char *string, int len, const termpaint_attr *attr) Like :c:func:`termpaint_surface_write_with_attr()` but does take a explicit length parameter instead of writing the string until it encounters a NUL character in the string. .. c:function:: void termpaint_surface_write_with_attr_clipped(termpaint_surface *surface, int x, int y, const char *string, const termpaint_attr *attr, int clip_x0, int clip_x1) Like :c:func:`termpaint_surface_write_with_attr()` but additionally applies clipping so that only cells in column ``clip_x0`` (inclusive) to column ``clip_x1`` are used for placing characters. ``x`` may be less than ``clip_x0``, in that case characters at the start of the string are not placed as needed to maintain the clipping interval. The clip range does *not* prevent modifications of characters outside of the interval to be changed if a existing cluster in surface cross the clipping boundary. In that case if that cluster is partially overwritten the part outside the interval is erased (preserving its previous attributes). .. c:function:: void termpaint_surface_write_with_len_attr_clipped(termpaint_surface *surface, int x, int y, const char *string, int len, const termpaint_attr *attr, int clip_x0, int clip_x1) Like :c:func:`termpaint_surface_write_with_attr_clipped()` but does take a explicit length parameter instead of writing the string until it encounters a NUL character in the string. .. c:function:: void termpaint_surface_write_with_colors(termpaint_surface *surface, int x, int y, const char *string, int fg, int bg) Like :c:func:`termpaint_surface_write_with_attr()` but with explicit parameters for foreground and background color. Decoration color will be set to TERMPAINT_DEFAULT_COLOR and no style attributes will be applied. See :ref:`colors` for how to specify colors. .. c:function:: void termpaint_surface_write_with_len_colors(termpaint_surface *surface, int x, int y, const char *string, int len, int fg, int bg) Like :c:func:`termpaint_surface_write_with_colors()` but does take a explicit length parameter instead of writing the string until it encounters a NUL character in the string. .. c:function:: void termpaint_surface_write_with_colors_clipped(termpaint_surface *surface, int x, int y, const char *string, int fg, int bg, int clip_x0, int clip_x1) Like :c:func:`termpaint_surface_write_with_attr_clipped()` but with explicit parameters for foreground and background color. Decoration color will be set to TERMPAINT_DEFAULT_COLOR and no style attributes will be applied. See :ref:`colors` for how to specify colors. .. c:function:: void termpaint_surface_write_with_len_colors_clipped(termpaint_surface *surface, int x, int y, const char *string, int fg, int bg, int clip_x0, int clip_x1) Like :c:func:`termpaint_surface_write_with_colors_clipped()` but does take a explicit length parameter instead of writing the string until it encounters a NUL character in the string. .. c:function:: void termpaint_surface_clear(termpaint_surface *surface, int fg, int bg) Clear the contents of the whole surface. All cells are set to spaces with ``fg`` as foreground color and ``bg`` as background color. Decoration color will be set to TERMPAINT_DEFAULT_COLOR and no style attributes will be applied. The cleared cell will be marked as erased. See :ref:`colors` for how to specify colors. .. c:function:: void termpaint_surface_clear_with_attr(termpaint_surface *surface, const termpaint_attr *attr) Clear the contents of the whole surface. All cells are set to spaces with attributes set to the contents of ``attr``. The cleared cell will be marked as erased. .. c:function:: void termpaint_surface_clear_rect(termpaint_surface *surface, int x, int y, int width, int height, int fg, int bg) Like :c:func:`termpaint_surface_clear()` but only clears the rectangle starting from cell at ``x``, ``y`` in it's upper left corner with with width ``width`` and height ``height``. If clusters cross the boundary of the rectangle, these clusters are completely erased. Portions of these clusters outside of the rectangle preserve their colors and attributes. The cleared cell will be marked as erased. See :ref:`colors` for how to specify colors. .. c:function:: void termpaint_surface_clear_rect_with_attr(termpaint_surface *surface, int x, int y, int width, int height, const termpaint_attr *attr) Like :c:func:`termpaint_surface_clear_with_attr()` but only clears the rectangle starting from cell at ``x``, ``y`` in it's upper left corner with with width ``width`` and height ``height``. If clusters cross the boundary of the rectangle, these clusters are completely erased. Portions of these clusters outside of the rectangle preserve their colors and attributes. The cleared cell will be marked as erased. .. c:function:: void termpaint_surface_clear_with_char(termpaint_surface *surface, int fg, int bg, int codepoint) Like :c:func:`termpaint_surface_clear` but the cells will be filled with the character ``codepoint`` and marked as not erased. .. c:function:: void termpaint_surface_clear_with_attr_char(termpaint_surface *surface, const termpaint_attr *attr, int codepoint) Like :c:func:`termpaint_surface_clear_with_attr` but the cells will be filled with the character ``codepoint`` and marked as not erased. .. c:function:: void termpaint_surface_clear_rect_with_char(termpaint_surface *surface, int x, int y, int width, int height, int fg, int bg, int codepoint) Like :c:func:`termpaint_surface_clear_rect` but the cells will be filled with the character ``codepoint`` and marked as not erased. .. c:function:: void termpaint_surface_clear_rect_with_attr_char(termpaint_surface *surface, int x, int y, int width, int height, const termpaint_attr *attr, int codepoint) Like :c:func:`termpaint_surface_clear_rect_with_attr` but the cells will be filled with the character ``codepoint`` and marked as not erased. .. c:function:: void termpaint_surface_set_fg_color(const termpaint_surface *surface, int x, int y, unsigned fg) Replaces the foreground color of the cluster at position ``x``, ``y`` with the color given in ``fg``. .. c:function:: void termpaint_surface_set_bg_color(const termpaint_surface *surface, int x, int y, unsigned bg) Replaces the background color of the cluster at position ``x``, ``y`` with the color given in ``bg``. .. c:function:: void termpaint_surface_set_deco_color(const termpaint_surface *surface, int x, int y, unsigned deco_color) Replaces the decoration color of the cluster at position ``x``, ``y`` with the color given in ``deco_color``. .. c:function:: void termpaint_surface_set_softwrap_marker(termpaint_surface *surface, int x, int y, _Bool state) This function sets or remove a soft wrap marker in a given cell. If ``state`` is true, the marker is set, otherwise it is removed. The marker will also be removed by writing new text to the cell or using a clear function. See above for how soft wrap markers are used to connect lines into paragraphs for native terminal clipboard functionality. .. c:function:: void termpaint_surface_copy_rect(termpaint_surface *src_surface, int x, int y, int width, int height, termpaint_surface *dst_surface, int dst_x, int dst_y, int tile_left, int tile_right) Copies the contents of the rectangle with the upper-left corner ``x``, ``y`` and width ``width`` and height ``height`` in surface ``src_surface`` into the surface ``dst_surface`` at position ``dst_x``, ``dst_y``. Overlapping source and destination rectangles are supported. If clusters in the source or destination surface cross the boundary of the rectangle the behavior depends on the values in ``tile_left`` for the left boundary and ``tile_right`` for the right boundary. The following tiling modes are available: .. c:namespace:: 0 .. c:macro:: TERMPAINT_COPY_NO_TILE Partial clusters in the source are copied to the destination as spaces for the parts of the cluster that is inside the rectangle. If clusters in the destination cross the boundary they are erased before the copy is made. (The part of the cluster outside the rectangle preserves it's attributes but the text is replaced by spaces) .. c:macro:: TERMPAINT_COPY_TILE_PUT Clusters in the source will be copied into the destination even if that means modifying cells outside of the destination rectangle. This allows copying a larger region in multiple steps. .. c:macro:: TERMPAINT_COPY_TILE_PRESERVE If clusters in the destination line up with clusters in source, the cluster in the destination is preserved. This allows seamlessly extending a copy made with ``TERMPAINT_COPY_TILE_PUT`` without overwriting previously copied cells. .. c:function:: void termpaint_surface_tint(termpaint_surface *surface, void (*recolor)(void *user_data, unsigned *fg, unsigned *bg, unsigned *deco), void *user_data) Changes the colors of all cells of the surface according to the recoloration function ``recolor``. This function is called for each cluster with the ``user_data`` and a pointer to locations that hold the foreground, background and decoration colors of that cluster. The function can then recolor that cluster by changing the values pointed to. .. c:function:: unsigned termpaint_surface_peek_fg_color(const termpaint_surface *surface, int x, int y) Return the foreground color of the cluster at ``x``, ``y``. .. c:function:: unsigned termpaint_surface_peek_bg_color(const termpaint_surface *surface, int x, int y) Return the background color of the cluster at ``x``, ``y``. .. c:function:: unsigned termpaint_surface_peek_deco_color(const termpaint_surface *surface, int x, int y) Return the decoration color of the cluster at ``x``, ``y``. .. c:function:: int termpaint_surface_peek_style(const termpaint_surface *surface, int x, int y) Return the style of the cluster at ``x``, ``y``. .. c:function:: void termpaint_surface_peek_patch(const termpaint_surface *surface, int x, int y, const char **setup, const char **cleanup, _Bool *optimize) Place the low level patching information of the cluster at ``x``, ``y`` into to locations pointed to by ``setup``, ``cleanup`` and ``optimize``. The strings are owned by the surface and must not be freed. .. c:function:: const char *termpaint_surface_peek_text(const termpaint_surface *surface, int x, int y, int *len, int *left, int *right) Return the text of the cluster at ``x``, ``y``. The returned string is not null terminated. It's length is stored into the location pointed to by ``len``. If non-zero the locations pointed to by ``left`` and ``right`` receive the columns of the left most and right most cell that is part of the cluster. If a cell is cleared this function returns a pointer to the special character :c:macro:`TERMPAINT_ERASED`. .. c:function:: _Bool termpaint_surface_peek_softwrap_marker(const termpaint_surface *surface, int x, int y) This function returns true if a soft wrap marker is set in a given cell. .. c:function:: _Bool termpaint_surface_same_contents(const termpaint_surface *surface1, const termpaint_surface *surface2) Compares two surfaces. If both have the same contents and attributes for every cell/cluster then it returns true. .. c:function:: int termpaint_surface_char_width(const termpaint_surface *surface, int codepoint) Returns the "width" of a character with Unicode codepoint ``codepoint``. Prefer the :doc:`string measurement functions` to using this function directly. Return values are 0 This character combines with previous characters into a cluster 1 This character takes one cell of space. 2 This character takes two cells of space. termpaint-0.3.1/doc/sync-event-handling.c000066400000000000000000000105551477303547200203260ustar00rootroot00000000000000// Feel free to copy from this example to your own code // SPDX-License-Identifier: 0BSD OR BSL-1.0 OR MIT-0 #include #include #include #include "termpaint.h" #include "termpaintx.h" // snippet-type-start typedef struct event_ { int type; int modifier; char *string; struct event_* next; } event; event* event_current; // unprocessed event event* event_saved; // event to free on next call // snippet-type-end // snippet-callback-start void event_callback(void *userdata, termpaint_event *tp_event) { (void)userdata; // remember tp_event is only valid while this callback runs, so copy everything we need. event *copied_event = NULL; if (tp_event->type == TERMPAINT_EV_CHAR) { copied_event = malloc(sizeof(event)); copied_event->type = tp_event->type; copied_event->modifier = tp_event->c.modifier; copied_event->string = strndup(tp_event->c.string, tp_event->c.length); copied_event->next = NULL; } else if (tp_event->type == TERMPAINT_EV_KEY) { copied_event = malloc(sizeof(event)); copied_event->type = tp_event->type; copied_event->modifier = tp_event->key.modifier; copied_event->string = strndup(tp_event->key.atom, tp_event->key.length); copied_event->next = NULL; } if (copied_event) { if (!event_current) { event_current = copied_event; } else { event* prev = event_current; while (prev->next) { prev = prev->next; } prev->next = copied_event; } } } // snippet-callback-end // snippet-wait-start event* key_wait(termpaint_integration *integration) { while (!event_current) { if (!termpaintx_full_integration_do_iteration(integration)) { // some kind of error return NULL; // or some other error handling } } if (event_saved) { free(event_saved->string); free(event_saved); } event *ret = event_current; event_current = ret->next; event_saved = ret; return ret; } // snippet-wait-end // snippet-menu-start bool quit_menu(termpaint_terminal *terminal, termpaint_integration *integration) { termpaint_surface *surface; surface = termpaint_terminal_get_surface(terminal); int fg = TERMPAINT_DEFAULT_COLOR; int bg = TERMPAINT_DEFAULT_COLOR; termpaint_surface_write_with_colors(surface, 20, 4, "Really quit? (y/N)", fg, bg); termpaint_terminal_flush(terminal, false); while (true) { event *event = key_wait(integration); if (event->type == TERMPAINT_EV_CHAR) { if (!strcmp(event->string, "y") || !strcmp(event->string, "Y")) { return true; } if (!strcmp(event->string, "n") || !strcmp(event->string, "N")) { return false; } } termpaint_surface_write_with_colors(surface, 20, 5, "Please reply with either 'y' for yes or 'n' for no.", fg, bg); termpaint_terminal_flush(terminal, false); } } // snippet-menu-end int main(int argc, char **argv) { (void)argc; (void)argv; termpaint_integration *integration; termpaint_terminal *terminal; termpaint_surface *surface; integration = termpaintx_full_integration_setup_terminal_fullscreen( "+kbdsig +kbdsigint", event_callback, NULL, &terminal); surface = termpaint_terminal_get_surface(terminal); termpaint_surface_clear(surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(surface, 0, 0, "Hello World", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); // snippet-main-start termpaint_terminal_flush(terminal, false); while (true) { event *event = key_wait(integration); if (event->type == TERMPAINT_EV_CHAR) { if (strcmp(event->string, "q") == 0) { break; } } if (event->type == TERMPAINT_EV_KEY) { if (strcmp(event->string, "Escape") == 0) { break; } } } // snippet-main-end quit_menu(terminal, integration); termpaint_terminal_free_with_restore(terminal); return 0; } termpaint-0.3.1/doc/terminal.rst000066400000000000000000000742351477303547200166570ustar00rootroot00000000000000Terminal ======== .. c:type:: termpaint_terminal The terminal object represents a connected terminal. This can be seen as the root object in termpaint. The actual connection with the terminal is left to the application. It can be connected to both a operating system terminal as well as to network protocols with suitable glue. The terminal always needs an bi-directional connection for the terminal object to work. See :doc:`integration` for how write a custom integrate of termpaint into an application. See :doc:`termpaintx` for a simple premade integration suitable for simple applications using a synchronous usage style or which work with a very simple event loop design. Terminals have different capabilities and thus a auto detection step is recommended before using the terminal in the application. Input and other events from the terminal are passed to the application using callback functions. The primary callback is the event callback which is called for keyboard input, mouse events and clipboard paste events. Use :c:func:`termpaint_terminal_set_event_cb()` to setup your event callback. This callback is required. Output is done by placing text on the primary surface of the terminal. This surface can be obtained by :c:func:`termpaint_terminal_get_surface()`. After all text is in place, call :c:func:`termpaint_terminal_flush()` to output the contents of the surface to the terminal. If no full repaint is requested this will only send :ref:`differences` since the last flush to the terminal. .. _terminal-setup: Setup ----- The terminal object works better with terminal specific setup which can enabled by doing a terminal auto-detection before calling :c:func:`termpaint_terminal_setup_fullscreen`. The terminal auto-detection can be started using :c:func:`termpaint_terminal_auto_detect`. This will initiate bidirectional communication to the terminal. The application can proceed with the setup when the detection is finished. For applications preferring synchronous integration the application should call :c:func:`termpaint_terminal_auto_detect_state` after each additional input from the terminal. If this function returns ``termpaint_auto_detect_done`` the detection is finished. For applications preferring asynchronous integration the application needs to wait for an event of type :c:macro:`TERMPAINT_EV_AUTO_DETECT_FINISHED` before proceeding with terminal setup. In either case the application needs to set an event callback before starting auto-detection. Applications should call either :c:func:`termpaint_terminal_setup_fullscreen` or :c:func:`termpaint_terminal_setup_inline` after terminal detection to setup the terminal for the chosen mode. In fullscreen mode the application claims and repaints the full terminal screen. If the terminal supports alternative screen mode, the terminal contents is not erased by the application and reappears after the application quits. In inline mode the application instead claims a stripe with limited height for display. This stripe starts at the line where the cursor is located. If the requested number of lines in not available the terminal will be scrolled to make the necessary space available. (Requesting a stripe higher than the terminal height is invalid and will lead to display corruption) When the application terminates in inline mode the cursor is reset to the top line of the stripe and the output stripe is erased. For clean shutdown the application needs to restore both terminal configuration as well as the kernel level terminal setup back to it's previous values. The first part should be done by calling :c:func:`termpaint_terminal_free_with_restore`. The second part should be done by using operating system specific calls to save the kernel settings before changing those and then restoring them after restoring the terminal setup. Common functions ---------------- These functions are commonly used when a initialized terminal object has been acquired. See :ref:`safety` for general rules for calling functions in termpaint. .. c:function:: void termpaint_terminal_free_with_restore(termpaint_terminal *term) Frees the terminal object ``term`` and restores the attached terminal to its base state. This function calls the integrations free callback. .. c:function:: void termpaint_terminal_free_with_restore_and_persistent(termpaint_terminal *term, termpaint_surface *surface) Frees the terminal object ``term`` and restores the attached terminal to its base state. The contents of the surface ``surface`` is left visible on the terminal. The cursor is placed after the contents of ``surface``. If ``surface`` is NULL, this function is the same as :c:func:`termpaint_terminal_free_with_restore()`. This function calls the integrations free callback. .. c:function:: void termpaint_terminal_free(termpaint_terminal *term) Frees the terminal object ``term`` without restoring the attached terminal to it's base state. This will leave the terminal in a state that other applications or the shell will not be prepared to handle in most cases. Prefer to use :c:func:`termpaint_terminal_free_with_restore()`. This function calls the integrations free callback. .. c:function:: void termpaint_terminal_set_event_cb(termpaint_terminal *term, void (*cb)(void *user_data, termpaint_event* event), void *user_data) The application must use this function to set an event callback. See :doc:`events` for details about events produced by terminal input. This is mostly a wrapper for using :c:func:`termpaint_input_set_event_cb` with a terminal object. Termpaint interprets certain events before passing them on to the application. Also, while running terminal auto detection, events are not passed to the given callback. Some events like :c:macro:`TERMPAINT_EV_AUTO_DETECT_FINISHED` are actually produced by termpaint and not by termpaint_input. .. c:function:: termpaint_surface *termpaint_terminal_get_surface(termpaint_terminal *term) Returns the primary surface of the terminal object ``term``. This surface is linked to the terminal and can be output using :c:func:`termpaint_terminal_flush`. This object is owned by the terminal object, don't free the returned value. .. c:function:: void termpaint_terminal_flush(termpaint_terminal *term, bool full_repaint) Output the current state of the primary surface of the terminal object to the attached terminal. If ``full_repaint`` is false it uses :ref:`incremental drawing` to reduce bandwidth use. Else it does a full redraw that can repair the contents of the terminal in case another application interfered with uncoordinated output to the same underlying terminal. .. c:function:: void termpaint_terminal_set_cursor_position(termpaint_terminal *term, int x, int y) Sets the text cursor position for the terminal object ``term``. The cursor is moved to this position the next time :c:func:`termpaint_terminal_flush` is called. .. c:function:: void termpaint_terminal_set_cursor_visible(termpaint_terminal *term, bool visible) Sets the visibility of the text cursor for the terminal object ``term``. The cursor is shown/hidden the next time :c:func:`termpaint_terminal_flush` is called. .. c:function:: void termpaint_terminal_set_cursor_style(termpaint_terminal *term, int style, bool blink) Sets the cursor shape / style of the terminal object ``term`` to the style specified in ``style``. If ``blink`` is true, the cursor will blink. The following styles are available: .. c:namespace:: 0 .. c:macro:: TERMPAINT_CURSOR_STYLE_TERM_DEFAULT This is the terminal default style. This is terminal implementation and configuration defined. .. c:macro:: TERMPAINT_CURSOR_STYLE_BLOCK Display the cursor as a block that covers an entire character. .. c:macro:: TERMPAINT_CURSOR_STYLE_UNDERLINE Display the cursor as a underline under the character. .. c:macro:: TERMPAINT_CURSOR_STYLE_BAR Display the cursor as a vertical bar between characters. .. c:function:: void termpaint_terminal_set_title(termpaint_terminal *term, const char* title, int mode) Set the title of the terminal to the string ``title``. ``mode`` specifies how to handle terminals where it is not certain that the original title can be restored when exiting the application. .. c:namespace:: 0 .. c:macro:: TERMPAINT_TITLE_MODE_ENSURE_RESTORE Only set the title if it is certain that the original title can be restored when the application restores the terminal. This is the recommended mode. .. c:macro:: TERMPAINT_TITLE_MODE_PREFER_RESTORE Set the title on all terminals that support setting a title without restricting to terminals that are known to be able to restore the title when the application restores the terminal. .. c:function:: void termpaint_terminal_set_icon_title(termpaint_terminal *term, const char* title, int mode) This function is like :c:func:`termpaint_terminal_set_title` but does not set the primary title but an alternative title called the icon title. Interpretation of this title differs by terminal. .. c:function:: void termpaint_terminal_set_color(termpaint_terminal *term, int color_slot, int r, int b, int g) Set special global (not per cell) terminal colors. ``color_slot`` can be one of: .. c:namespace:: 0 .. c:macro:: TERMPAINT_COLOR_SLOT_BACKGROUND This is the color used for cells without an explicitly set background. This color is used e.g. for cells using the :c:macro:`TERMPAINT_DEFAULT_COLOR` as background. .. c:macro:: TERMPAINT_COLOR_SLOT_FOREGRUND This is the color used for cells without an explicitly set foreground. This color is used e.g. for cells using the :c:macro:`TERMPAINT_DEFAULT_COLOR` as foreground. .. c:macro:: TERMPAINT_COLOR_SLOT_CURSOR Set the color of the cursor in the terminal. .. c:function:: void termpaint_terminal_reset_color(termpaint_terminal *term, int color_slot) Reset color choices made using :c:func:`termpaint_terminal_set_color`. When ``color_slot`` is ``TERMPAINT_COLOR_SLOT_CURSOR`` the cursor color is reset to the default. .. c:function:: void termpaint_terminal_set_inline(termpaint_terminal *terminal, _Bool enabled) Switch between inline and fullscreen mode. If ``enabled`` is true switch to inline mode. Otherwise switch to fullscreen mode. A call to this function must be followed by reestablishing the primary surface size and contents and by a call to :c:func:`termpaint_terminal_flush()` to finalize the switch to the new mode. Note: As primary surface resizing requirements differ between inline and fullscreen mode, check if your integration has an API for switching that needs to be called in addition or instead of this function. For example when using termpaintx :c:func:`termpaintx_full_integration_set_inline` needs to be called instead of this function. .. c:function:: void termpaint_terminal_request_tagged_paste(termpaint_terminal *term, _Bool enabled) Request the terminal to send the needed information so :c:macro:`TERMPAINT_EV_PASTE` events can be generated. .. c:function:: void termpaint_terminal_set_mouse_mode(termpaint_terminal *term, int mouse_mode) Request the terminal to enable mouse handling by the application. Depending on the setting :c:macro:`TERMPAINT_EV_MOUSE` events will be generated for: .. c:namespace:: 0 .. c:macro:: TERMPAINT_MOUSE_MODE_OFF No events. Terminal native select and copy features will be available to the user. .. c:macro:: TERMPAINT_MOUSE_MODE_CLICKS Only report mouse down and up events (clicks). Terminal native select and copy features will not be available to the user. Some terminals allow overriding this mouse mode using shift temporarily. .. c:macro:: TERMPAINT_MOUSE_MODE_DRAG Report mouse down and up events as well as movement when at least one mouse button is held down. Terminal native select and copy features will not be available to the user. Some terminals allow overriding this mouse mode using shift temporarily. .. c:macro:: TERMPAINT_MOUSE_MODE_MOVEMENT Report mouse movement and down and up events independent of mouse button state. Terminal native select and copy features will not be available to the user. Some terminals allow overriding this mouse mode using shift temporarily. .. c:function:: void termpaint_terminal_request_focus_change_reports(termpaint_terminal *term, _Bool enabled) Request focus change events from the terminal. If supported by the terminal these events will be reported as :ref:`misc-events` of type :c:func:`termpaint_input_focus_in` and :c:func:`termpaint_input_focus_out`. .. c:function:: _Bool termpaint_terminal_should_use_truecolor(termpaint_terminal *terminal) After auto detection, returns true if termpaint does not translate rgb color colors to indexed colors. To force passing rgb colors to the terminal, one of the the capabilities :c:macro:`TERMPAINT_CAPABILITY_TRUECOLOR_MAYBE_SUPPORTED` or :c:macro:`TERMPAINT_CAPABILITY_TRUECOLOR_SUPPORTED` must be set. .. c:function:: void termpaint_terminal_bell(termpaint_terminal *term) Send the BEL character to the terminal. Most terminals trigger a visual or audio reaction to the BEL character. Functions for setup and auto-detection -------------------------------------- These functions are used to get a initialized terminal object and are somewhat dependent on the integration used. For the integration from the :doc:`termpaintx addon` a convenience function encapsulating the setup is available as :c:func:`termpaintx_full_integration_setup_terminal_fullscreen` that can be used instead. See :ref:`safety` for general rules for calling functions in termpaint. .. c:function:: termpaint_terminal *termpaint_terminal_new(termpaint_integration *integration) Create a new terminal object. See :doc:`integration` for details on the callbacks needed in ``integration``. The application has to free this with :c:func:`termpaint_terminal_free_with_restore()` or :c:func:`termpaint_terminal_free()` If the integration's free callback frees the integration this takes ownership of the integration. .. c:function:: bool termpaint_terminal_auto_detect(termpaint_terminal *terminal) Starts terminal type auto-detection. The event callback has to be set before calling this function. Return false, if the auto-detection could not be started. .. c:enum:: termpaint_auto_detect_state_enum .. c:namespace:: 0 .. c:enumerator:: termpaint_auto_detect_none Terminal type auto-detection was not run yet. .. c:enumerator:: termpaint_auto_detect_running Terminal type auto-detection is currently running. .. c:enumerator:: termpaint_auto_detect_done Terminal type auto-detection was run and has finished. .. c:function:: enum termpaint_auto_detect_state_enum termpaint_terminal_auto_detect_state(const termpaint_terminal *terminal) Get the state of a possibly running terminal type auto-detection. .. c:function:: _Bool termpaint_terminal_might_be_supported(const termpaint_terminal *terminal) After auto detection, returns true if the terminal might be supported. If it returns false the terminal is likely missing essential features for proper support. .. _termpaint-fullscreen-options: .. c:function:: void termpaint_terminal_setup_fullscreen(termpaint_terminal *terminal, int width, int height, const char *options) Setup the terminal connected to the terminal object ``term`` to fullscreen mode. Assume terminal size is ``width`` columns by ``height`` lines. ``options`` specifies an space delimited list of additional settings: ``-altscreen`` Do not activate the alternative screen of the terminal. Previous contents of the screen is not restored after terminating the application. ``+kbdsig`` Do not activate any modes of the terminal that might conflict with processing of keyboard signals in the kernel tty layer. Use this when passing ``+kdbsigint``, ``+kdbsigquit`` or ``+kdbsigtstp`` to :c:func:`termpaintx_full_integration` or when using an custom integration that enabled the equivalent kernel terminal layer processing. Affected key combinations are usually ctrl-c, ctrl-z and, ctrl-\\ .. _termpaint-inline-options: .. c:function:: void termpaint_terminal_setup_inline(termpaint_terminal *terminal, int width, int height, const char *options) Setup the terminal connected to the terminal object ``term`` to inline mode. Assume terminal size is ``width`` columns. And the initially used height is ``height`` lines. ``options`` specifies an space delimited list of additional settings: ``+kbdsig`` Do not activate any modes of the terminal that might conflict with processing of keyboard signals in the kernel tty layer. Use this when passing ``+kdbsigint``, ``+kdbsigquit`` or ``+kdbsigtstp`` to :c:func:`termpaintx_full_integration` or when using an custom integration that enabled the equivalent kernel terminal layer processing. Affected key combinations are usually ctrl-c, ctrl-z and, ctrl-\\ In inline mode termpaint does not paint the full screen, but it only paints a vertical stripe starting at the previous cursor position of a given height. This allows integration of the output into the "transcript" of commands in a command line environment. For example implementing entering of interactive commands. Inline mode never uses the alternative screen of the terminal itself. But when switching from inline mode to fullscreen mode, alternative screen mode is always used when available. .. c:function:: void termpaint_terminal_auto_detect_apply_input_quirks(termpaint_terminal *terminal, _Bool backspace_is_x08) Setup input handling based on the auto detection result and ``backspace_is_x08``. Needs to be called after auto detection is finished. Pass ``backspace_is_x08`` as true if the terminal uses 0x08 (ASCII BS) for the backspace key. On \*nix platforms this information can be obtained from the ``termios`` structure by ``original_termios.c_cc[VERASE] == 0x08``. For ssh connections the VERASE value is transmitted as part of the pseudo terminal request in the encoded terminal modes. Special purpose functions ------------------------- These functions have specialized use. They are not needed in many applications. See :ref:`safety` for general rules for calling functions in termpaint. .. c:function:: void termpaint_terminal_pause(termpaint_terminal *term) Temporarily restore the terminal state. This should be called before running external applications. To return to rendering by termpaint call :c:func:`termpaint_terminal_unpause`. After calling this function the application still needs to restore the kernel tty layer settings to the state needed to run external applications. .. c:function:: void termpaint_terminal_pause_and_persistent(termpaint_terminal *term, termpaint_surface *surface) Temporarily restore the terminal state. This should be called before running external applications. The contents of the surface ``surface`` is left visible on the terminal. The cursor is placed after the contents of ``surface``. If ``surface`` is NULL, this function is the same as :c:func:`termpaint_terminal_pause_and_persistent()`. To return to rendering by termpaint call :c:func:`termpaint_terminal_unpause`. After calling this function the application still needs to restore the kernel tty layer settings to the state needed to run external applications. .. c:function:: void termpaint_terminal_unpause(termpaint_terminal *term) This function activates termpaint mode again after it was previously temporarily restored to the normal state. Before calling this function the application needs to restore the kernel tty layer settings to the state needed by termpaint (or to the state before calling pause). .. c:function:: void termpaint_terminal_set_raw_input_filter_cb(termpaint_terminal *term, bool (*cb)(void *user_data, const char *data, unsigned length, bool overflow), void *user_data) This function allows settings a callback that is called with raw sequences before interpretation. The application can inspect the sequences in this callback. If the callback returns true the sequence is not interpreted further. This is mostly a wrapper for using :c:func:`termpaint_input_set_raw_filter_cb` with a terminal object. But events while running terminal auto detection are not passed to the given callback. .. c:function:: void termpaint_terminal_handle_paste(termpaint_terminal *term, _Bool enabled) This is a wrapper for using :c:func:`termpaint_input_handle_paste` with a terminal object. Explicit paste handling is an switchable terminal feature, see :c:func:`termpaint_terminal_request_tagged_paste` for enabling it. .. c:function:: const char *termpaint_terminal_self_reported_name_and_version(const termpaint_terminal *terminal) Returns a pointer to a string with the result of the terminal's self reported name and version. Only some terminals support this. For other terminals NULL will be returned. This value is only available after successful terminal auto-detection. The returned pointer is valid until the terminal object is freed or terminal auto detection is triggered again. .. c:function:: void termpaint_terminal_auto_detect_result_text(const termpaint_terminal *terminal, char *buffer, int buffer_length) Fills ``buffer`` with null terminated string with debugging details about the detected terminal type. The buffer pointed to by ``buffer`` needs to be at least ``buffer_length`` bytes long. .. c:function:: void termpaint_terminal_activate_input_quirk(termpaint_terminal *term, int quirk) This is a wrapper for using :c:func:`termpaint_input_activate_quirk` with a terminal object. Quirks matching the auto detected terminal are already activated by :c:func:`termpaint_terminal_auto_detect_apply_input_quirks`. Calling this function explicitly should be rarely needed. .. c:function:: const char* termpaint_terminal_peek_input_buffer(const termpaint_terminal *term) This function in conjunction with :c:func:`termpaint_terminal_peek_input_buffer_length` allows an application to observe input data that is buffered by not yet processed. If called after :c:func:`termpaint_terminal_add_input_data` returned, this will contain data in partial or ambiguous sequences not yet processed. This is a wrapper for using :c:func:`termpaint_input_peek_buffer` with a terminal object. .. c:function:: int termpaint_terminal_peek_input_buffer_length(const termpaint_terminal *term) Returns the length of the valid data for :c:func:`termpaint_terminal_peek_input_buffer`. This is a wrapper for using :c:func:`termpaint_input_peek_buffer_length` with a terminal object. .. c:function:: void termpaint_terminal_set_log_mask(termpaint_terminal *term, unsigned mask) Set the mask of what besides errors is reported to the integration's logging callback. All logging messages are for debugging only and might change between releases. ``mask`` is a bit combination of .. c:namespace:: 0 .. c:macro:: TERMPAINT_LOG_AUTO_DETECT_TRACE Log details of the auto detection state machine .. c:macro:: TERMPAINT_LOG_TRACE_RAW_INPUT Log raw input bytes from the terminal. .. c:function:: _Bool termpaint_terminal_capable(const termpaint_terminal *terminal, int capability) Features supported differ among terminal implementations. Termpaint uses as set of capabilities to decide how to interface with terminals. This function allows to query currently set capabilities. Capabilities start with some defaults and get setup during terminal auto-detection. The following capabilities are available: .. c:namespace:: 0 .. c:macro:: TERMPAINT_CAPABILITY_7BIT_ST The terminals fully supports using ``ESC\\`` as string terminator. This is the string terminator specified by ECMA-48. .. c:macro:: TERMPAINT_CAPABILITY_88_COLOR The terminal uses 88 colors for indexed colors instead of the more widely supported 256 colors. .. c:macro:: TERMPAINT_CAPABILITY_CLEARED_COLORING The terminal supports using "clear to end of line" for trailing sequences of insignificant spaces. This includes support for setting up multiple colored ranges per line using this sequence. .. c:macro:: TERMPAINT_CAPABILITY_CLEARED_COLORING_DEFCOLOR If TERMPAINT_CAPABILITY_CLEARED_COLORING is supported this indicated if this sequence also works for the special "default" terminal color. .. c:macro:: TERMPAINT_CAPABILITY_CSI_EQUALS The terminal's escape sequence parser properly handles sequences starting with ``ESC[=`` and ignores unknown sequences of this type. .. c:macro:: TERMPAINT_CAPABILITY_CSI_GREATER The terminal's escape sequence parser properly handles sequences starting with ``ESC[>`` and ignores unknown sequences of this type. .. c:macro:: TERMPAINT_CAPABILITY_CSI_POSTFIX_MOD The terminal's escape sequence parser properly handles sequences that use a intermediate character before the final character of a CSI sequence. .. c:macro:: TERMPAINT_CAPABILITY_CURSOR_SHAPE_OSC50 Cursor shape needs to be setup with a konsole specific escape sequence. .. c:macro:: TERMPAINT_CAPABILITY_EXTENDED_CHARSET The terminal is capable of displaying a font with more than 512 different characters. .. c:macro:: TERMPAINT_CAPABILITY_MAY_TRY_CURSOR_SHAPE The terminal's parser is expected to cope with the cursor setup CSI sequence without glitches. .. c:macro:: TERMPAINT_CAPABILITY_MAY_TRY_CURSOR_SHAPE_BAR The terminal either does not support cursor shapes or it does support bar cursor shape. .. c:macro:: TERMPAINT_CAPABILITY_MAY_TRY_TAGGED_PASTE The terminal supports bracketed/tagged paste. .. c:macro:: TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT The terminal uses a format for cursor position reports that is distinct from key press reports. .. c:macro:: TERMPAINT_CAPABILITY_TITLE_RESTORE The terminal has a title stack that can be used to restore the title. .. c:macro:: TERMPAINT_CAPABILITY_TRUECOLOR_MAYBE_SUPPORTED The terminal is not known to have problems with rgb(truecolor) color types. .. c:macro:: TERMPAINT_CAPABILITY_TRUECOLOR_SUPPORTED The terminal is known to support rgb(truecolor) color types. .. c:function:: void termpaint_terminal_promise_capability(termpaint_terminal *terminal, int capability) This function allows overriding terminal type auto-detection of terminal capabilities. Use this with care, if the terminal is not able to handle the enabled capabilities the rendering might break. .. c:function:: void termpaint_terminal_disable_capability(termpaint_terminal *terminal, int capability) This function allows overriding terminal type auto-detection of terminal capabilities. On specific use is to disable :c:macro:`TERMPAINT_CAPABILITY_TRUECOLOR_MAYBE_SUPPORTED` to switch to a more conservative estamination of a terminals capability to support rgb color modes. .. c:function:: void termpaint_terminal_expect_apc_input_sequences(termpaint_terminal *term, _Bool enabled) This is a wrapper for using :c:func:`termpaint_input_expect_apc_sequences` with a terminal object. APC sequences are only known to be used by kitty in an extended keyboard reporting mode that is currently not supported by termpaint. .. c:function:: void termpaint_terminal_expect_cursor_position_report(termpaint_terminal *term) This is a wrapper for using :c:func:`termpaint_input_expect_cursor_position_report` with a terminal object. Needs to be called for each ``ESC[6n`` sequence send manually to the terminal to ensure the result is interpreted as cursor position report instead of a key press. If the terminal :c:macro:`properly supports ESC[?6n` that sequence should be used and this function does not need to be called. .. c:function:: void termpaint_terminal_expect_legacy_mouse_reports(termpaint_terminal *term, int s) This is a wrapper for using :c:func:`termpaint_input_expect_legacy_mouse_reports` with a terminal object. When mouse reporting is enabled this function is internally called with :c:macro:`TERMPAINT_INPUT_EXPECT_LEGACY_MOUSE`, so it should be rarely needed to call this explicitly. .. c:function:: void termpaint_terminal_glitch_on_out_of_memory(termpaint_terminal *term) Normally termpaint aborts the process on memory allocation failure to avoid hard to debug glitches. When this function is called instead termpaint tries to continue, but potentially discarding output characters and attributes where allocation would be needed. Call this function if your application needs to be resilient against memory allocation failures. To use termpaint in such environments it's additionally required to call variants of functions ending in _or_nullptr or _mustcheck instead of the base variant whenever those exist in the header file. See :ref:`malloc-failure` for details. Functions for integrations -------------------------- See :ref:`safety` for general rules for calling functions in termpaint. .. c:function:: const char *termpaint_terminal_restore_sequence(const termpaint_terminal *term) Returns a null terminated string that can be used to restore the terminal to it's base state. The restore string is the same string that is used when calling :c:func:`termpaint_terminal_free_with_restore` or :c:func:`termpaint_terminal_pause`. .. c:function:: void termpaint_terminal_callback(termpaint_terminal *term) If the application has set ``request_callback`` in the integration structure, this function needs to be called after a delay when the terminal object requests it by invoking the ``request_callback`` callback. .. c:function:: void termpaint_terminal_add_input_data(termpaint_terminal *term, const char *data, unsigned length) The integration part of the application has to call this function to pass terminal input data to the terminal object. See :doc:`integration` for details. The application has to ensure that this function is never called recursively from a callback with any :c:type:`termpaint_input` object that is already in a call to ``termpaint_terminal_add_input_data``. This is a wrapper for using :c:func:`termpaint_input_add_data` with a terminal object. termpaint-0.3.1/doc/termpaint_input.rst000066400000000000000000000163021477303547200202550ustar00rootroot00000000000000termpaint_input =============== .. c:type:: termpaint_input ``termpaint_input`` is a sublibrary for terminal input handling. It can be used standalone, but when using termpaint for terminal output it's always used as an internal dependency. Thus while standalone usage is fully supported, most applications will not use it explicitly. Where interaction with the input handling is needed there are wrappers that take a :c:type:`termpaint_terminal` object and forward settings to the internal instance. .. TODO general API design Input bytes from the terminal to termpaint_input need to be passed to :c:func:`termpaint_input_add_data`. If enough bytes have accumulated to identify a input sequence termpaint_input will call the event callback set by the application using :c:func:`termpaint_input_set_event_cb` with the interpreted :doc:`event `. Functions --------- These functions are contained in the header ``termpaintx_input.h`` See :ref:`safety` for general rules for calling functions in termpaint. .. c:function:: termpaint_input *termpaint_input_new(void) Create a new termpaint_input object. The application has to free this with :c:func:`termpaint_input_free` .. c:function:: void termpaint_input_free(termpaint_input *ctx) Frees the termpaint_input object ``ctx``. .. c:function:: void termpaint_input_set_event_cb(termpaint_input *ctx, void (*cb)(void *user_data, termpaint_event* event), void *user_data) The application must use this function to set an event callback. See :doc:`events` for details about events produced by terminal input. The input is buffered and as soon as a complete sequence is detected the raw filter callback is invoked and if that callback didn't suppress further processing an an event is generated and passed to the event callback. The wrapper for using this with a terminal object is :c:func:`termpaint_terminal_set_event_cb` .. c:function:: void termpaint_input_add_data(termpaint_input *ctx, const char *data, unsigned length) This is the function to feed incoming data from the terminal to the input processing. The application has to ensure that this function is never called recursively from a callback with any :c:type:`termpaint_input` object that is already in a call to ``termpaint_input_add_data``. The wrapper for using this with a terminal object is :c:func:`termpaint_terminal_add_input_data` .. c:function:: void termpaint_input_set_raw_filter_cb(termpaint_input *ctx, _Bool (*cb)(void *user_data, const char *data, unsigned length, _Bool overflow), void *user_data) This function allows settings a callback that is called with raw sequences before interpretation. The application can inspect the sequence in this callback. If the callback returns true the sequence is not interpreted further. The wrapper for using this with a terminal object is :c:func:`termpaint_terminal_set_raw_input_filter_cb` .. c:function:: void termpaint_input_expect_cursor_position_report(termpaint_input *ctx) Needs to be called for each ``ESC[6n`` sequence send to the terminal to ensure the result is interpreted as cursor position report instead of a key press. This should not be called if ``ESC[?6n`` is used and properly supported by the terminal. The wrapper for using this with a terminal object is :c:func:`termpaint_terminal_expect_cursor_position_report` .. c:function:: void termpaint_input_expect_legacy_mouse_reports(termpaint_input *ctx, int s) Legacy mouse modes use sequences that do not fit into the ECMA-48 sequence schema. They are composed of ``ESC[M`` followed by additional data. The original mouse reporting modes use this kind of encoding. An additional mode was defined later that can not reliably differentiated thus apart from "off" there are two choices. .. c:namespace:: 0 .. c:macro:: TERMPAINT_INPUT_EXPECT_NO_LEGACY_MOUSE Disable parsing of legacy mouse sequences. .. c:macro:: TERMPAINT_INPUT_EXPECT_LEGACY_MOUSE Expect legacy mouse sequences in the original format composed of 3 bytes following the ``ESC[M`` sequence. .. c:macro:: TERMPAINT_INPUT_EXPECT_LEGACY_MOUSE_MODE_1005 Expect legacy mouse sequences in the multibyte format similar to 2 byte utf-8 encoding. The ``ESC[M`` sequence is followed by 3 of these variable length representations. This encoding is commonly selected using `ESC[?1005h`. The wrapper for using this with a terminal object is :c:func:`termpaint_terminal_expect_legacy_mouse_reports` .. c:function:: void termpaint_input_handle_paste(termpaint_input *ctx, _Bool enable) Set whether bracketed paste terminal sequences are parsed as a :c:macro:`TERMPAINT_EV_PASTE` event (if ``enable`` is true) or if the contents of the paste is parsed like normal input, sandwiched in :c:macro:`TERMPAINT_EV_MISC` events with atoms :c:func:`termpaint_input_paste_begin` and :c:func:`termpaint_input_paste_end`. Explicit paste handling is an switchable terminal feature, this setting only is meaningful if bracketed paste has been enabled in the terminal. The wrapper for using this with a terminal object is :c:func:`termpaint_terminal_handle_paste` .. c:function:: void termpaint_input_expect_apc_sequences(termpaint_input *ctx, _Bool enable) Consider input starting with ``ESC_`` as a sequence to be terminated by a string terminator (ST) instead of as alt ``_``. The resync trick will still detect ``ESC_`` if it's followed by ``ESC[0n``. The wrapper for using this with a terminal object is :c:func:`termpaint_terminal_expect_apc_input_sequences` .. c:function:: const char* termpaint_input_peek_buffer(const termpaint_input *ctx) This function in conjunction with :c:func:`termpaint_input_peek_buffer_length` allows an application to observe input data that is buffered by not yet processed. If called after :c:func:`termpaint_input_add_data` returned, this will contain data in partial or ambiguous sequences not yet processed. The wrapper for using this with a terminal object is :c:func:`termpaint_terminal_peek_input_buffer` .. c:function:: int termpaint_input_peek_buffer_length(const termpaint_input *ctx) Returns the length of the valid data for :c:func:`termpaint_input_peek_buffer`. The wrapper for using this with a terminal object is :c:func:`termpaint_terminal_peek_input_buffer_length` .. c:function:: void termpaint_input_activate_quirk(termpaint_input *ctx, int quirk) Most input parsing is independent of the connected terminal. But some sequences are used by different terminals for different functions. In that case quirks allow fine tuning the input parsing to pick the correct interpretation. .. c:namespace:: 0 .. c:macro:: TERMPAINT_INPUT_QUIRK_BACKSPACE_X08_AND_X7F_SWAPPED Without this quirk char 0x7f (DEL) is interpreted as backspace key and char 0x08 (ASCII BS) is interpreted as ctrl backspace. With this quirk activated the interpretation is swapped. .. c:macro:: TERMPAINT_INPUT_QUIRK_C1_FOR_CTRL_SHIFT By default C1 control characters are not interpreted as special key input. When this quirk is activated char 0x80 is interpreted as ctrl shift space and chars 0x81 to 0x9a are interpreted as ctrl shift A-Z. The wrapper for using this with a terminal object is :c:func:`termpaint_terminal_activate_input_quirk` termpaint-0.3.1/doc/termpaintx.rst000066400000000000000000000416451477303547200172360ustar00rootroot00000000000000Addon termpaintx ================ termpaintx is an optional add-on to termpaint that offers a very simple premade integration and a few functions that might be useful for custom integrations. For applications that deal with any kind of asynchronous events, apart from the terminal, it’s recommended to use a custom integration or an external integration into a general event loop. But for simple applications that only deal with synchronous data sources and time based events this simple integration can be used. termpaintx has operating system specific dependencies and might not be available for all compilation environments. The free callback of this integration frees the memory used, thus termpaint_terminal_new takes ownership of the integration. .. For simple synchronous applications termpaintx contains a functional integration. This integration does not support additional communication devices or connections. .. An example using this integration looks like this:: .. termpaint_integration *integration = termpaintx_full_integration("+kbdsigint +kbdsigtstp"); termpaint_terminal *terminal = termpaint_terminal_new(integration); termpaint_terminal_set_event_cb(terminal, event_callback, NULL); termpaintx_full_integration_set_terminal(integration, terminal); termpaint_terminal_auto_detect(terminal); termpaintx_full_integration_wait_for_ready(integration); termpaintx_full_integration_apply_input_quirks(integration); int width, height; termpaintx_full_integration_terminal_size(integration, &width, &height); termpaint_terminal_setup_fullscreen(terminal, width, height, "+kbdsig"); .. // use terminal here .. while (!quit) { if (!termpaint_full_integration_do_iteration(integration)) { // some kind of error break; } // either do work here or from the event_callback. } .. termpaint_terminal_free_with_restore(terminal); These functions are contained in the header ``termpaintx.h`` .. c:function:: _Bool termpaintx_full_integration_available() Checks if the program is connected to a terminal. (using `isatty(3) `__) This function checks if :c:func:`termpaintx_full_integration` will likely succeed. .. _termpaintx-options: .. c:function:: termpaint_integration *termpaintx_full_integration(const char *options) Creates an integration object with the given options. It tries finding a connected terminal by looking at stdin, stdout, stderr and the process' controlling terminal. ``options`` is a space separated list of options. Supported options: ``+kbdsigint`` Do not disable kernel keyboard interrupt handling (usually Ctrl-C) ``+kbdsigquit`` Do not disable kernel keyboard quit handling (usually Ctrl-\\) ``+kbdsigtstp`` Do not disable kernel keyboard suspend handling (usually Ctrl-Z) This integration does not offer any support for handling the needed SIGTSTP signal for proper suspend support. It's your applications responsibility to supply the needed signal handling. If an application wants to support keyboard suspend it has to arrange for a signal handler to restore the terminal and call :c:func:`termpaint_terminal_unpause` after the process is resumed and the kernel terminal interface is again configured for termpaint usage. Returns NULL on failure. Handling of the WINCH (window size changed) signal is automatically setup if possible. .. c:function:: termpaint_integration *termpaintx_full_integration_from_controlling_terminal(const char *options) Creates an integration object with the given options. It tries finding a connected terminal by looking at the process' controlling terminal. Returns NULL on failure. Handling of the WINCH (window size changed) signal is automatically setup. See :ref:`here` for allowed values of the ``options`` parameter. .. c:function:: termpaint_integration *termpaintx_full_integration_from_fd(int fd, _Bool auto_close, const char *options) Creates an integration object with the given options. It uses file descriptor ``fd``. If ``auto_close`` is true, the file descriptor will be closed when the integration is deallocated. The application is responsible to detect terminal size changes and call :c:func:`termpaint_surface_resize` on the primary surface with the new size. See :ref:`here` for allowed values of the ``options`` parameter. .. c:function:: termpaint_integration *termpaintx_full_integration_from_fds(int fd_read, int fd_write, const char *options) Creates an integration object with the given options. It uses file descriptor ``fd_read`` for reading from the terminal and ``fd_write`` for writing to the terminal. The application is responsible to detect terminal size changes and call :c:func:`termpaint_surface_resize` on the primary surface with the new size. See :ref:`here` for allowed values of the ``options`` parameter. .. c:function:: termpaint_integration *termpaintx_full_integration_setup_terminal_fullscreen(const char *options, void (*event_handler)(void *, termpaint_event *), void *event_handler_user_data, termpaint_terminal **terminal_out) Creates an integration and a terminal object with the given options and connects them to work together. The integration is returned and the terminal object is made available via the ``terminal`` out-parameter. It also runs terminal auto-detection and applies detected input processing quirks, initializes full screen mode using :c:func:`termpaint_terminal_setup_fullscreen()` and sets up a watchdog process to restore the terminal to it's normal state if the main application suddenly terminates (e.g. a crash). The ``event_handler`` and ``event_handler_user_data`` are passed to :c:func:`termpaint_terminal_set_event_cb`. Valid options are :ref:`options for termpaint` and :ref:`options for termpaintx`. If the integration can not be initialized then the function prints an error message and returns NULL. This function is currently equivalent to a manual setup like this:: termpaint_integration *integration = termpaintx_full_integration(options); if (!integration) { const char* error = "Error: Terminal not available!"; write(1, error, strlen(error)); return nullptr; } termpaint_terminal *terminal = termpaint_terminal_new(integration); termpaintx_full_integration_set_terminal(integration, terminal); termpaint_terminal_set_event_cb(terminal, event_handler, event_handler_user_data); termpaint_terminal_auto_detect(terminal); termpaintx_full_integration_wait_for_ready_with_message(integration, 10000, "Terminal auto detection is taking unusually long, press space to abort."); termpaintx_full_integration_apply_input_quirks(integration); int width, height; termpaintx_full_integration_terminal_size(integration, &width, &height); termpaint_terminal_setup_fullscreen(terminal, width, height, options); termpaintx_full_integration_ttyrescue_start(integration); .. c:function:: termpaint_integration *termpaintx_full_integration_setup_terminal_inline(const char *options, int lines, void (*event_handler)(void *, termpaint_event *), void *event_handler_user_data, termpaint_terminal **terminal_out) Creates an integration and a terminal object with the given options and connects them to work together. The integration is returned and the terminal object is made available via the ``terminal`` out-parameter. It also runs terminal auto-detection and applies detected input processing quirks, initializes inline mode using :c:func:`termpaint_terminal_setup_inline()` and sets up a watchdog process to restore the terminal to it's normal state if the main application suddenly terminates (e.g. a crash). The height of the inline rendering area is set to ``lines`` high. The ``event_handler`` and ``event_handler_user_data`` are passed to :c:func:`termpaint_terminal_set_event_cb`. Valid options are :ref:`options for termpaint` and :ref:`options for termpaintx`. If the integration can not be initialized then the function prints an error message and returns NULL. The steps are similar to :c:func:`termpaintx_full_integration_setup_terminal_fullscreen` except that termpaint_terminal_setup_inline is used and the height passed to it is limited to `lines`. .. c:function:: _Bool termpaintx_full_integration_do_iteration(termpaint_integration *integration) Waits for input from the terminal and passes it to the connected terminal object. Return false, if an error occurred while reading from the input file descriptor. .. c:function:: _Bool termpaintx_full_integration_do_iteration_with_timeout(termpaint_integration *integration, int *milliseconds) Waits for input from the terminal for ``*milliseconds`` milliseconds and passes it to the connected terminal object. After the call ``*milliseconds`` will contain the remaining milliseconds from the original timeout. If the call returned because the timeout expired ``*milliseconds`` will be zero, otherwise it will be the original value minus the time spend waiting for and processing input. Return false, if an error occurred while reading from the input file descriptor. .. c:function:: void termpaintx_full_integration_wait_for_ready(termpaint_integration *integration) Waits for the auto-detection to be finished. It internally calls :c:func:`termpaintx_full_integration_do_iteration` while waiting. .. c:function:: void termpaintx_full_integration_wait_for_ready_with_message(termpaint_integration *integration, int milliseconds, const char* message) Like :c:func:`termpaintx_full_integration_wait_for_ready` but if detection did not finish after `milliseconds` milliseconds, will print ``message``. Please note, printing a message while fingerprinting is in it's start phase might interfere with fingerprinting. So don't use too small values for ``milliseconds``. Nevertheless a timeout can help for terminals that are not compatible with running terminal autodetection, by at least altering the user that something likly has gone wrong. .. c:function:: void termpaintx_full_integration_apply_input_quirks(termpaint_integration *integration) Setup input handling based on the auto detection result and tty parameters. Needs to be called after auto detection is finished. It internally calls :c:func:`termpaint_terminal_auto_detect_apply_input_quirks` .. c:function:: void termpaintx_full_integration_set_terminal(termpaint_integration *integration, termpaint_terminal *terminal) Sets the terminal object to be managed by this integration object. This needs to be called before using :c:func:`termpaintx_full_integration_do_iteration` when not using :c:func:`termpaintx_full_integration_setup_terminal_fullscreen` (which already does that). .. c:function:: const struct termios *termpaintx_full_integration_original_terminal_attributes(termpaint_integration *integration) Returns a pointer to the saved terminal attributes in ``termios`` format. The pointer is valid until the integration is freed. Note: As all functions in termpaint this function is not async-signal safe. If the application needs this information in a signal handler it needs to call this function while initializing and store the value for the signal handler to use. .. c:function:: void termpaintx_full_integration_set_inline(termpaint_integration *integration, _Bool enabled, int height) Sets the inline status and inline height for the associated terminal. If ``enabled`` is true, the terminal is set to inline mode and resized to the height given in ``height`` (or the size of the connected terminal if ``height`` is larger). In contrast to :c:func:`termpaint_terminal_set_inline` this function updates essential state in termpaintx needed for handling terminal resize. A call to this function must be followed by reestablishing the primary surface contents and by a call to :c:func:`termpaint_terminal_flush()` to finalize the switch to the new mode. On manual terminal initializations (i.e. using :c:func:`termpaint_terminal_setup_inline` instead of :c:func:`termpaintx_full_integration_setup_terminal_inline`) of inline mode, this function may also be used to synchronize termpaintx internal state to inline state of termpaint, if used with ``enabled`` is true and ``height`` is greater than zero. .. c:function:: _Bool termpaintx_full_integration_ttyrescue_start(termpaint_integration *integration) Sets up a watchdog process to restore the terminal to it’s normal state if the main application suddenly terminates (e.g. a crash). Returns false on failure. .. c:function:: _Bool termpaintx_full_integration_terminal_size(termpaint_integration *integration, int *width, int *height) Stores the current terminal size into ``*width`` and ``*height``. This function relies on the terminal size cached in the kernel. Returns false on failure. .. c:function:: termpaint_logging_func termpaintx_enable_tk_logging(void) This function starts a helper process that uses python3 to create a window (using `tkinter `_ with logging messages. The window will appear when the first log message is output. It returns a function suitable as logging callback for a integration. If an error occurred setting up the helper process returns a no-op logging function. This function is meant to easy development and debugging of an application using termpaint. It's not meant for usage in the final application. This function is only available if enabled at compile time. Functions for custom integrations --------------------------------- .. c:function:: _Bool termpaintx_fd_set_termios(int fd, const char *options) This function can be used to get the kernel terminal setup without using the full integration. Instead of a pointer to an integration object this accesses the terminal directly by the file descriptor ``fd``. It accepts the same options as :c:func:`termpaintx_full_integration` Returns false on failure. .. c:function:: _Bool termpaintx_fd_terminal_size(int fd, int *width, int *height) This function can be used to get the terminal size from the kernel without using the full integration. Instead of a pointer to an integration object this accesses the terminal directly by the file descriptor ``fd``. Otherwise it works like :c:func:`termpaintx_full_integration_terminal_size`. Returns false on failure. Terminal restore watchdog ------------------------- .. c:type:: termpaintx_ttyrescue termpaintx has a functions to create a watchdog subprocess to restore the terminal to a usable state on sudden program termination (e.g. a crash). This watchdog process uses a socket pair (similar to a pipe) to monitor that the main process is still running. If the main process terminates without first signaling a clean shutdown by calling :c:func:`termpaintx_ttyrescue_stop` the watchdog restores the terminal and kernel interface settings. When using the integration from termpaintx the watchdog is started by calling :c:func:`termpaintx_full_integration_ttyrescue_start`. The integration takes care of updating the restore sequence as it changes over time and communicating the original kernel terminal interface layer settings to the watchdog. The watchdog is automatically shut down, when the integration is freed. If the watchdog is used with a custom terminal integration it is started using :c:func:`termpaintx_ttyrescue_start_or_nullptr`, passing it the initial restore sequence and the file descriptor of the terminal. The integration has to call :c:func:`termpaintx_ttyrescue_set_restore_termios` to set the original ``struct termios`` contents and if the restore sequence changes it has to call :c:func:`termpaintx_ttyrescue_update` with the new restore sequence. Functions ......... These functions are contained in the header ``termpaintx_ttyrescue.h`` See :ref:`safety` for general rules for calling functions in termpaint. .. c:function:: termpaintx_ttyrescue *termpaintx_ttyrescue_start_or_nullptr(int fd, const char *restore_seq) Setup the watchdog process. The watchdog uses terminal file descriptor ``fd`` when a restore is triggered, sending the string ``restore_seq`` to the terminal. Returns ``NULL`` on error. .. c:function:: void termpaintx_ttyrescue_stop(termpaintx_ttyrescue *tpr) Cleanly stop the watchdog process. .. c:function:: _Bool termpaintx_ttyrescue_update(termpaintx_ttyrescue *tpr, const char* data, int len) Update the restore sequence used by the watchdog process. Returns false on failure. .. c:function:: _Bool termpaintx_ttyrescue_set_restore_termios(termpaintx_ttyrescue *tpr, const struct termios *original_terminal_attributes) Set or update the ``struct termios`` to reset the terminal kernel interface to when the watchdog triggers. Returns false on failure. termpaint-0.3.1/filetoinc.py000077500000000000000000000014331477303547200160640ustar00rootroot00000000000000#! /usr/bin/env python3 # SPDX-License-Identifier: BSL-1.0 import sys name = sys.argv[1] infile = sys.argv[2] outfile = sys.argv[3] with open(infile, 'rb') as f: data = f.read() data_encoded = [""] # intentionally excluding some whitespace and some special characters basic_c_source_chars = set(b' abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_{}[]#()<>%:;.?*+-/^&|~!=,\'') for ch in data: if ch in basic_c_source_chars: data_encoded[-1] += chr(ch) else: data_encoded[-1] += "\\{:o}".format(ch) if len(data_encoded[-1]) > 70: data_encoded.append("") with open(outfile, 'w') as f: f.write('static const unsigned char {}[] = {{\n'.format(name)) for line in data_encoded: f.write(' "{}"\n'.format(line)) f.write('};\n') termpaint-0.3.1/inputevents.cpp000066400000000000000000000364001477303547200166250ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include #include #include #include #include #include #include #include typedef bool _Bool; #include "termpaint.h" #include "termpaintx.h" #include "termpaint_input.h" #include "termpaintx_ttyrescue.h" struct DisplayEvent { std::string raw; std::string eventString; }; std::vector ring; std::string peek_buffer; termpaint_terminal *terminal; termpaint_surface *surface; time_t last_q; bool m_mode; bool quit; bool focus_tracking = false; bool tagged_paste = false; bool raw_paste = false; bool legacy_mouse_support = false; bool apc_parsing = false; bool raw_command_mode = false; std::string raw_commmand_str; std::string terminal_info; template unsigned char u8(X); // intentionally undefined template <> unsigned char u8(char ch) { return ch; } _Bool raw_filter(void *user_data, const char *data, unsigned length, _Bool overflow) { (void)user_data; (void)overflow; std::string event { data, length }; ring.emplace_back(); ring.back().raw = event; if (event == "q") { time_t now = time(0); if (last_q && (now - last_q) == 3) { quit = true; } last_q = now; } else { last_q = 0; } if (m_mode) { m_mode = false; if (event == "0") { termpaint_terminal_set_mouse_mode(terminal, TERMPAINT_MOUSE_MODE_OFF); } else if (event == "1") { termpaint_terminal_set_mouse_mode(terminal, TERMPAINT_MOUSE_MODE_CLICKS); } else if (event == "2") { termpaint_terminal_set_mouse_mode(terminal, TERMPAINT_MOUSE_MODE_DRAG); } else if (event == "3") { termpaint_terminal_set_mouse_mode(terminal, TERMPAINT_MOUSE_MODE_MOVEMENT); } else if (event == "4") { focus_tracking = !focus_tracking; termpaint_terminal_request_focus_change_reports(terminal, focus_tracking); } else if (event == "6") { legacy_mouse_support = !legacy_mouse_support; termpaint_terminal_expect_legacy_mouse_reports(terminal, legacy_mouse_support); } else if (event == "p") { tagged_paste = !tagged_paste; termpaint_terminal_request_tagged_paste(terminal, tagged_paste); } else if (event == "r") { raw_paste = !raw_paste; termpaint_terminal_handle_paste(terminal, !raw_paste); } else if (event == "a") { apc_parsing = !apc_parsing; termpaint_terminal_expect_apc_input_sequences(terminal, apc_parsing); } else if (event == "x") { raw_commmand_str = ""; raw_command_mode = true; } else if (event == "q") { quit = true; } } else if (raw_command_mode) { if (event[1] == 0 && event[0] >= ' ' && event[0] <= 126) { raw_commmand_str += event; } if (event == "\x0d") { raw_command_mode = false; if (raw_commmand_str.size()) { printf("%s", "\033[0;0H\033"); printf("%s", raw_commmand_str.data()); fflush(stdout); sleep(1); } } if (event == "\x08" || event == "\x7f") { if (raw_commmand_str.size()) { raw_commmand_str.pop_back(); } } } else { if (event == "m") { m_mode = true; } } return 0; } void event_handler(void *user_data, termpaint_event *event) { (void)user_data; std::string pretty; if (event->type == 0) { pretty = "unknown"; } else if (event->type == TERMPAINT_EV_KEY) { pretty = "K: "; if ((event->key.modifier & ~(TERMPAINT_MOD_SHIFT|TERMPAINT_MOD_ALT|TERMPAINT_MOD_CTRL)) == 0) { pretty += (event->key.modifier & TERMPAINT_MOD_SHIFT) ? "S" : " "; pretty += (event->key.modifier & TERMPAINT_MOD_ALT) ? "A" : " "; pretty += (event->key.modifier & TERMPAINT_MOD_CTRL) ? "C" : " "; } else { char buf[100]; snprintf(buf, 100, "%03d", event->key.modifier); pretty += buf; } pretty += " "; pretty += std::string { event->key.atom, event->key.length }; } else if (event->type == TERMPAINT_EV_CHAR) { pretty = "C: "; pretty += (event->c.modifier & TERMPAINT_MOD_SHIFT) ? "S" : " "; pretty += (event->c.modifier & TERMPAINT_MOD_ALT) ? "A" : " "; pretty += (event->c.modifier & TERMPAINT_MOD_CTRL) ? "C" : " "; pretty += " "; pretty += std::string { event->c.string, event->c.length }; } else if (event->type == TERMPAINT_EV_MOUSE) { if ((event->mouse.modifier & ~(TERMPAINT_MOD_SHIFT|TERMPAINT_MOD_ALT|TERMPAINT_MOD_CTRL)) == 0) { pretty += (event->mouse.modifier & TERMPAINT_MOD_SHIFT) ? "S" : " "; pretty += (event->mouse.modifier & TERMPAINT_MOD_ALT) ? "A" : " "; pretty += (event->mouse.modifier & TERMPAINT_MOD_CTRL) ? "C" : " "; } else { char buf[100]; snprintf(buf, 100, "%03d", event->mouse.modifier); pretty += buf; } pretty += " Mouse "; if (event->mouse.action == TERMPAINT_MOUSE_PRESS) { pretty += std::to_string(event->mouse.button) + " press"; } else if (event->mouse.action == TERMPAINT_MOUSE_MOVE) { pretty += "move"; } else if (event->mouse.button != 3) { pretty += std::to_string(event->mouse.button) + " release"; } else { pretty += "some release"; } pretty += ": x=" + std::to_string(event->mouse.x) + " y=" + std::to_string(event->mouse.y) + " rawbtn=" + std::to_string(event->mouse.raw_btn_and_flags); } else if (event->type == TERMPAINT_EV_MISC) { pretty += "Misc: "; pretty += std::string { event->misc.atom, event->misc.length }; } else if (event->type == TERMPAINT_EV_CURSOR_POSITION) { pretty = "Cursor position report: x=" + std::to_string(event->cursor_position.x) + " y=" + std::to_string(event->cursor_position.y); } else if (event->type == TERMPAINT_EV_MODE_REPORT) { if (event->mode.kind & 1) { pretty = "Mode status report: mode=?" + std::to_string(event->mode.number) + " status=" + std::to_string(event->mode.status); } else { pretty = "Mode status report: mode=" + std::to_string(event->mode.number) + " status=" + std::to_string(event->mode.status); } } else if (event->type == TERMPAINT_EV_PASTE) { pretty = "Paste: "; pretty += (event->paste.initial) ? "I" : " "; pretty += (event->paste.final) ? "F" : " "; pretty += " "; std::string pasted = std::string { event->paste.string, event->paste.length }; pretty += "\""; for (auto ch : pasted) { if (ch < ' ') { pretty += "\\x" + std::to_string(ch); } else if (ch == '\\') { pretty += "\\x5c"; } else { pretty.append(1, ch); } } pretty += "\""; } else { pretty = "Other event no. " + std::to_string(event->type); } if (ring.empty() || ring.back().eventString.size()) { ring.emplace_back(); } ring.back().eventString = pretty; } const auto rgb_white = TERMPAINT_RGB_COLOR(0xff, 0xff, 0xff); const auto rgb_greyCC = TERMPAINT_RGB_COLOR(0xcc, 0xcc, 0xcc); const auto rgb_grey7F = TERMPAINT_RGB_COLOR(0x7f, 0x7f, 0x7f); const auto rgb_black = TERMPAINT_RGB_COLOR(0, 0, 0); const auto rgb_redFF = TERMPAINT_RGB_COLOR(0xff, 0, 0); const auto rgb_red7F = TERMPAINT_RGB_COLOR(0xff, 0, 0); void display_esc(int x, int y, const std::string &data) { for (unsigned i = 0; i < data.length(); i++) { if (u8(data[i]) == '\033') { termpaint_surface_write_with_colors(surface, x, y, "^[", rgb_white, rgb_red7F); x+=2; } else if (0xfc == (0xfe & u8(data[i])) && i+5 < data.length()) { char buf[7] = {data[i], data[i+1], data[i+2], data[i+3], data[i+4], data[i+5], 0}; termpaint_surface_write_with_colors(surface, x, y, buf, rgb_white, rgb_grey7F); x += 1; i += 5; } else if (0xf8 == (0xfc & u8(data[i])) && i+4 < data.length()) { char buf[7] = {data[i], data[i+1], data[i+2], data[i+3], data[i+4], 0}; termpaint_surface_write_with_colors(surface, x, y, buf, rgb_white, rgb_grey7F); x += 1; i += 4; } else if (0xf0 == (0xf8 & u8(data[i])) && i+3 < data.length()) { char buf[7] = {data[i], data[i+1], data[i+2], data[i+3], 0}; termpaint_surface_write_with_colors(surface, x, y, buf, rgb_white, rgb_grey7F); x += 1; i += 3; } else if (0xe0 == (0xf0 & u8(data[i])) && i+2 < data.length()) { char buf[7] = {data[i], data[i+1], data[i+2], 0}; termpaint_surface_write_with_colors(surface, x, y, buf, rgb_white, rgb_grey7F); x += 1; i += 2; } else if (0xc0 == (0xe0 & u8(data[i])) && i+1 < data.length()) { if (((unsigned char)data[i]) == 0xc2 && ((unsigned char)data[i+1]) < 0xa0) { // C1 and non breaking space char v = ((unsigned char)data[i+1]) >> 4; char a = char(v < 10 ? '0' + v : 'a' + v - 10); v = data[i+1] & 0xf; char b = char(v < 10 ? '0' + v : 'a' + v - 10); char buf[7] = {'\\', 'u', '0', '0', a, b, 0}; termpaint_surface_write_with_colors(surface, x, y, buf, rgb_white, rgb_red7F); x += 6; } else { char buf[7] = {data[i], data[i+1], 0}; termpaint_surface_write_with_colors(surface, x, y, buf, rgb_white, rgb_grey7F); x += 1; } i += 1; } else if (data[i] < 32 || data[i] >= 127) { termpaint_surface_write_with_colors(surface, x, y, "\\x", rgb_white, rgb_red7F); x += 2; char buf[3]; sprintf(buf, "%02x", (unsigned char)data[i]); termpaint_surface_write_with_colors(surface, x, y, buf, rgb_white, rgb_red7F); x += 2; } else { char buf[2] = {data[i], 0}; termpaint_surface_write_with_colors(surface, x, y, buf, rgb_white, rgb_grey7F); x += 1; } } } void render() { termpaint_surface_clear(surface, rgb_white, rgb_black); termpaint_surface_write_with_colors(surface, 0, 0, "Input Decoding", rgb_white, rgb_black); termpaint_surface_write_with_colors(surface, 5, 23, "m for menu", rgb_white, rgb_black); termpaint_surface_write_with_colors(surface, 20, 0, terminal_info.data(), rgb_greyCC, rgb_black); if (peek_buffer.length()) { termpaint_surface_write_with_colors(surface, 0, 23, "unmatched:", rgb_redFF, rgb_black); display_esc(11, 23, peek_buffer); } int y = 2; for (DisplayEvent &event : ring) { display_esc(5, y, event.raw); termpaint_surface_write_with_colors(surface, 30, y, event.eventString.data(), rgb_redFF, rgb_black); ++y; } if (m_mode) { y = 10; termpaint_surface_write_with_colors(surface, 10, y++, "+ Choose: +", rgb_black, rgb_greyCC); termpaint_surface_write_with_colors(surface, 10, y++, "| q: quit |", rgb_black, rgb_greyCC); termpaint_surface_write_with_colors(surface, 10, y++, "| 0: mouse off |", rgb_black, rgb_greyCC); termpaint_surface_write_with_colors(surface, 10, y++, "| 1: mouse clicks on |", rgb_black, rgb_greyCC); termpaint_surface_write_with_colors(surface, 10, y++, "| 2: mouse drag on |", rgb_black, rgb_greyCC); termpaint_surface_write_with_colors(surface, 10, y++, "| 3: mouse movements on |", rgb_black, rgb_greyCC); termpaint_surface_write_with_colors(surface, 10, y++, "| 4: toggle focus tracking |", rgb_black, rgb_greyCC); termpaint_surface_write_with_colors(surface, 10, y++, "| 6: toggle legacy mouse sup |", rgb_black, rgb_greyCC); termpaint_surface_write_with_colors(surface, 10, y++, "| p: toggle tagged paste |", rgb_black, rgb_greyCC); termpaint_surface_write_with_colors(surface, 10, y++, "| r: toggle tagged paste raw |", rgb_black, rgb_greyCC); termpaint_surface_write_with_colors(surface, 10, y++, "| a: toggle APC parsing |", rgb_black, rgb_greyCC); termpaint_surface_write_with_colors(surface, 10, y++, "| x: raw mode switch |", rgb_black, rgb_greyCC); termpaint_surface_write_with_colors(surface, 10, y++, "+----------------------------+", rgb_black, rgb_greyCC); } if (raw_command_mode) { termpaint_surface_write_with_colors(surface, 10, 10, "+ Sequence to send: +", rgb_black, rgb_greyCC); termpaint_surface_write_with_colors(surface, 10, 11, "| ESC |", rgb_black, rgb_greyCC); termpaint_surface_write_with_colors(surface, 15, 11, raw_commmand_str.data(), rgb_black, rgb_greyCC); termpaint_surface_write_with_colors(surface, 10, 12, "+--------------------------------------------+", rgb_black, rgb_greyCC); } termpaint_terminal_flush(terminal, false); } int main(int argc, char **argv) { std::string init_str; bool init_str_set = false; if (argc >= 3 && argv[1] == std::string("--init")) { init_str = argv[2]; init_str_set = true; } termpaint_integration *integration = termpaintx_full_integration(init_str_set ? init_str.data() : "+kbdsigint +kbdsigtstp"); if (!integration) { puts("Could not init!"); return 1; } terminal = termpaint_terminal_new(integration); termpaintx_full_integration_set_terminal(integration, terminal); surface = termpaint_terminal_get_surface(terminal); termpaint_terminal_set_raw_input_filter_cb(terminal, raw_filter, 0); termpaint_terminal_set_event_cb(terminal, event_handler, 0); termpaint_terminal_auto_detect(terminal); termpaintx_full_integration_wait_for_ready_with_message(integration, 10000, "Terminal auto detection is taking unusually long, press space to abort."); termpaintx_full_integration_apply_input_quirks(integration); int width, height; termpaintx_full_integration_terminal_size(integration, &width, &height); termpaint_terminal_setup_fullscreen(terminal, width, height, init_str_set ? init_str.data() : "+kbdsig"); termpaintx_full_integration_ttyrescue_start(integration); if (termpaint_terminal_auto_detect_state(terminal) == termpaint_auto_detect_done) { char buff[100]; termpaint_terminal_auto_detect_result_text(terminal, buff, sizeof (buff)); terminal_info = std::string(buff); } render(); while (!quit) { if (!termpaintx_full_integration_do_iteration(integration)) { // some kind of error break; } peek_buffer = std::string(termpaint_terminal_peek_input_buffer(terminal), termpaint_terminal_peek_input_buffer_length(terminal)); while (ring.size() > 18) { ring.erase(ring.begin()); } render(); } termpaint_terminal_free_with_restore(terminal); return 0; } termpaint-0.3.1/meson.build000066400000000000000000000260401477303547200156760ustar00rootroot00000000000000# SPDX-License-Identifier: BSL-1.0 project('termpaint', ['c', 'cpp'], default_options : ['buildtype=debugoptimized', 'cpp_std=c++14', 'c_std=gnu11'], version: '0.3.1') if meson.get_compiler('cpp').get_id() == 'gcc' or meson.get_compiler('cpp').get_id() == 'clang' add_project_arguments('-fvisibility=hidden', '-DTERMPAINT_EXPORT_SYMBOLS', language: 'c') add_project_arguments('-fvisibility=hidden', '-fvisibility-inlines-hidden', '-DTERMPAINT_EXPORT_SYMBOLS', language: 'cpp') endif cc = meson.get_compiler('c') add_project_arguments(cc.get_supported_arguments('-Wall', '-Wextra', '-Werror=strict-prototypes', '-Werror=incompatible-pointer-types', '-Werror=implicit-int', '-Werror=discarded-qualifiers', '-Wno-string-plus-int'), language: 'c') add_project_arguments('-Werror=return-type', '-Werror=implicit-function-declaration', '-Werror=int-conversion', '-Werror=old-style-definition', language: 'c') add_project_arguments('-Wall', '-Wextra', language: 'cpp') add_project_arguments('-Werror=return-type', language: 'cpp') if get_option('valgrind-compat') add_project_arguments('-DTERMPAINTP_VALGRIND', language : 'c') endif lib_rt = cc.find_library('rt', required : false) # clock_gettime silence_warnings = [ '-Wno-padded' ] main_lib_cargs = [] ###### blob ttyrescue # This option should only default to true on linux (or other supported platforms). But meson # does not really allow doing that in a reasonable way. So just ignore it on other platforms. if get_option('ttyrescue-fexec-blob') and host_machine.system() == 'linux' ttyrescue_fexec_blob_type = get_option('ttyrescue-fexec-blob-type') if ttyrescue_fexec_blob_type == 'default' if (host_machine.cpu_family() == 'ppc64' or host_machine.cpu_family() == 's390x' or host_machine.cpu_family() == 'aarch64') # the vendored version of linux-syscall-support is known not to work on these architectures ttyrescue_fexec_blob_type = 'musl-gcc' else ttyrescue_fexec_blob_type = 'lss' endif endif if ttyrescue_fexec_blob_type == 'lss' ttyrescue_blob_exe = executable('ttyrescue_nolibc', 'ttyrescue_nolibc.c', c_args: ['-fno-asynchronous-unwind-tables', '-fno-ident', '-ffreestanding', '-nostdlib', '-static', '-Os', '-fvisibility=hidden', '-std=gnu11'], link_args: ['-fno-asynchronous-unwind-tables', '-fno-ident', '-nostdlib', '-static', '-Os', '-fvisibility=hidden', '-std=gnu11']) elif ttyrescue_fexec_blob_type == 'musl-gcc' # In the future this should be made cross compilation compatible ttyrescue_blob_exe = custom_target('ttyrescue_blob', input: 'ttyrescue_mini.c', output: 'ttyrescue_musl', command: [find_program('musl-gcc'), '-static', '-no-pie', '-Wl,-znoseparate-code,-z,norelro', '-fno-asynchronous-unwind-tables', '-fno-ident', '-Os', '-fvisibility=hidden', '-std=gnu11', '@INPUT@', '-o', '@OUTPUT@']) endif ttyrescue_blob_stripped = custom_target('ttyrescue_blob_stripped', input: ttyrescue_blob_exe, output: 'ttyrescue_blob_stripped', command: [find_program('strip'), '--strip-all', '--remove-section=.comment', '--remove-section=.note', '--remove-section=.eh_frame_hdr', '--remove-section=.eh_frame', '--remove-section=.note.gnu.gold-version', '--remove-section=.note.gnu.build-id', '@INPUT@', '-o', '@OUTPUT@']) ttyrescue_blob_inc = custom_target('ttyrescue_blob_inc', input: ttyrescue_blob_stripped, output: ['ttyrescue_blob.inc'], command: [find_program('./filetoinc.py'), 'ttyrescue_blob', '@INPUT@', '@OUTPUT0@']) main_lib_cargs += '-DTERMPAINT_RESCUE_FEXEC' else ttyrescue_blob_inc = [] endif ###### /blob ttyrescue if get_option('errorlog') debugwin_inc = custom_target('debugwin_inc', output: ['debugwin.py.inc'], command: [find_program('./filetoinc.py'), 'debugwin', files('debugwin.py'), '@OUTPUT0@']) main_lib_cargs += '-DUSE_TK_DEBUGLOG' else debugwin_inc = [] endif main_vscript = 'termpaint.symver' if host_machine.system() == 'linux' # for now, only do this on linux, expand supported platforms as needed main_ld_vscript = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), main_vscript) else main_ld_vscript = [] endif #ide:editable-filelist main_lib_files = [ 'termpaint.c', 'termpaint_event.c', 'termpaint_input.c', 'termpaintx.c', 'termpaintx_ttyrescue.c', 'ttyrescue.c', debugwin_inc, ttyrescue_blob_inc ] main_lib_cargs += '-DTERMPAINT_RESCUE_EMBEDDED' main_lib_cargs += '-DTERMPAINT_RESCUE_PATH="@0@"'.format(get_option('ttyrescue-path')) main_lib = library('termpaint', main_lib_files, dependencies: lib_rt, c_args: main_lib_cargs, soversion: '0a', darwin_versions: ['1', '1'], link_args : main_ld_vscript, link_depends : main_vscript, install: true) main_lib_installed_headers = [ 'termpaint.h', 'termpaint_event.h', 'termpaint_input.h', 'termpaintx.h', 'termpaintx_ttyrescue.h', ] install_headers(main_lib_installed_headers) import('pkgconfig').generate(main_lib, subdirs : '.', name : 'libtermpaint', filebase : 'termpaint', description : 'A terminal cell display and keyboard abstraction library.', ) termpaint_dep = declare_dependency( include_directories: include_directories('.'), link_with: main_lib ) if meson.version().version_compare('>=0.54') meson.override_dependency('termpaint', termpaint_dep) endif #ide:editable-filelist ttyrescue_files = [ 'ttyrescue.c', ] ttyrescue_exe_args = {} if get_option('ttyrescue-install') ttyrescue_exe_args += {'install': true, 'install_dir': get_option('ttyrescue-path')} endif executable('ttyrescue', ttyrescue_files, kwargs: ttyrescue_exe_args) if cc.has_header('picojson.h', required : get_option('system-picojson')) picojson_dep = [] else picojson_dep = declare_dependency(compile_args: ['-DBUNDLED_PICOJSON']) endif image_vscript = 'termpaint_image.symver' if host_machine.system() == 'linux' image_ld_vscript = '-Wl,--version-script,@0@/@1@'.format(meson.current_source_dir(), image_vscript) else image_ld_vscript = [] endif #ide:editable-filelist image_lib_files = [ 'termpaint_image.cpp', ] image_lib = library('termpaint_image', image_lib_files, link_with: [main_lib], dependencies: [picojson_dep], soversion: '0a', darwin_versions: ['1', '1'], link_args : image_ld_vscript, link_depends : image_vscript, install: true) image_lib_installed_headers = [ 'termpaint_image.h', ] install_headers(image_lib_installed_headers) import('pkgconfig').generate(image_lib, subdirs : '.', name : 'libtermpaint_image', filebase : 'termpaint_image', description : 'File load and save support for termpaint.', ) termpaint_image_dep = declare_dependency( include_directories: include_directories('.'), link_with: image_lib, dependencies: termpaint_dep ) if meson.version().version_compare('>=0.54') meson.override_dependency('termpaint_image', termpaint_image_dep) endif tools_kwargs = {} if get_option('tools-path') != '' tools_kwargs += {'install': true, 'install_dir': get_option('tools-path')} endif #ide:editable-filelist inputevents_files = [ 'inputevents.cpp', ] executable('inputevents', inputevents_files, link_with: [main_lib], kwargs: tools_kwargs) #ide:editable-filelist attrdemo_files = [ 'demo/attrs.c', ] executable('attrdemo', attrdemo_files, link_with: [main_lib]) #ide:editable-filelist chardemo_files = [ 'demo/chars.c', ] executable('chardemo', chardemo_files, link_with: [main_lib]) #ide:editable-filelist shuffledemo_files = [ 'demo/shuffle.c', ] executable('shuffle', shuffledemo_files, link_with: [main_lib]) #ide:editable-filelist lifedemo_files = [ 'demo/life.c', ] executable('life', lifedemo_files, link_with: [main_lib]) #ide:editable-filelist textwrapdemo_files = [ 'demo/textwrap.c', ] executable('textwrap', textwrapdemo_files, link_with: [main_lib]) #ide:editable-filelist detect_files = [ 'demo/detect.c', ] executable('detect', detect_files, link_with: [main_lib], kwargs: tools_kwargs) #ide:editable-filelist keyboardcollector_files = [ 'tools/keyboardcollector.cpp', ] executable('keyboardcollector', keyboardcollector_files, link_with: [main_lib], dependencies: [picojson_dep]) if get_option('ssh') executable('keyboardcollector-ssh', 'tools/keyboardcollector.cpp', 'tools/SshServer.cpp', cpp_args: ['-DUSE_SSH'], link_with: [main_lib], dependencies: dependency('libssh')) endif docopt_dep = dependency('docopt', required : get_option('system-docopt')) if not docopt_dep.found() docopt_lib = static_library('libdocopt', 'third-party/docopt/docopt.cpp', cpp_args: ['-Wno-unknown-pragmas']) docopt_dep = declare_dependency(link_with: docopt_lib, compile_args: ['-DBUNDLED_DOCOPT']) endif fmt_dep = dependency('fmt', required : get_option('system-fmt')) if not fmt_dep.found() fmt_lib = static_library('libfmt', 'third-party/format.cc') fmt_dep = declare_dependency(link_with: fmt_lib, compile_args: ['-DBUNDLED_FMT']) endif executable('mcheck', 'tools/mcheck.cpp', link_with: [main_lib], dependencies: [docopt_dep, fmt_dep, picojson_dep]) executable('termquery', 'termquery.cpp', link_with: [main_lib]) catch2_dep = dependency('catch2', required : get_option('system-catch2')) if not catch2_dep.found() catch2_dep = declare_dependency(compile_args: ['-DBUNDLED_CATCH2']) else if catch2_dep.version().version_compare('>=3.0') catch2_dep = [catch2_dep, declare_dependency(compile_args: ['-DCATCH3'])] endif endif testlib = static_library('testlib', 'tests/catch_main.cpp', dependencies: [catch2_dep]) #ide:editable-filelist test_files = [ 'tests/fingerprintingtests.cpp', 'tests/hashtest.cpp', 'tests/input_tests.cpp', 'tests/measurement_tests.cpp', 'tests/surface.cpp', 'tests/terminal_misc.cpp', 'tests/utf8_tests.cpp', ] testtermpaint = executable('testtermpaint', test_files, link_with: [main_lib, testlib], cpp_args: ['-fno-inline', silence_warnings], dependencies: [catch2_dep, picojson_dep]) testtermpaint_env = environment() testtermpaint_env.set('TERMPAINT_TEST_DATA', meson.current_source_dir() / ('tests')) test('testtermpaint', testtermpaint, timeout: 1200, env: testtermpaint_env) #ide:editable-filelist test_terminaloutput_files = [ 'tests/terminaloutput.cpp', 'tests/terminaloutput_main.cpp', ] testtermpaint_terminaloutput = executable('testtermpaint_terminaloutput', test_terminaloutput_files, link_with: [main_lib], dependencies: [dependency('threads'), catch2_dep, picojson_dep], cpp_args: ['-fno-inline', silence_warnings]) # currently can't be run as meson registered test, because it needs a path to a terminal test driver #test('testtermpaint_terminaloutput', testtermpaint_terminaloutput, # timeout: 300) # doc snippets executable('doc-getting-started', 'doc/getting-started.c', link_with: [main_lib]) executable('doc-sync-event-handling', 'doc/sync-event-handling.c', link_with: [main_lib]) executable('doc-callback-event-handling', 'doc/callback-event-handling.c', link_with: [main_lib]) termpaint-0.3.1/meson_options.txt000066400000000000000000000014621477303547200171720ustar00rootroot00000000000000# SPDX-License-Identifier: BSL-1.0 # termpaintx option('ttyrescue-path', type : 'string', value : '/usr/libexec') option('ttyrescue-install', type : 'boolean', value : false) option('ttyrescue-fexec-blob', type : 'boolean', value : true) option('ttyrescue-fexec-blob-type', type : 'combo', choices: ['default', 'lss', 'musl-gcc'], value : 'default') option('tools-path', type : 'string', value : '') option('valgrind-compat', type : 'boolean', value : true) option('system-docopt', type : 'feature', value : 'disabled') option('system-fmt', type : 'feature', value : 'disabled') option('system-catch2', type : 'feature', value : 'disabled') option('system-picojson', type : 'feature', value : 'disabled') option('errorlog', type : 'boolean', value : false) # samples etc option('ssh', type : 'boolean', value : false) termpaint-0.3.1/termpaint.c000066400000000000000000007007231477303547200157120ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "termpaint.h" #include #include #include #include #include #include #include #include "termpaint_compiler.h" #include "termpaint_input.h" #include "termpaint_utf8.h" #include "termpaint_hash.h" #include "termpaint_char_width.h" /* TODO and known problems: * What about composing code points? * * */ #ifndef __STDC_VERSION_STDDEF_H__ #ifndef nullptr #define nullptr ((void*)0) #endif #endif // First cast to void* in order to silence alignment warnings because the containing structure, // ensures that the offsets work out in a way that alignment is correct. #define container_of(ptr, type, member) ((type *)(void*)(((char*)(ptr)) - offsetof(type, member))) #define BUG(x) abort() typedef unsigned char uchar; /* Data model * * A surface is a 2 dimensional array of cells. A cluster occupies a continuous span of * 1 to 16 cells in one line. * * Each cluster can contain a string of unicode codepoints that the terminal will use * to form an grapheme. These strings consist of a base character and possible a * sequence of nonspacing combining marks. These describe the minimal unit the terminal * will display. If this unit is split or partially erased the whole unit will disappear. * * Additionally cluster has display (and/or semantic) attributes. These include foreground * and background color as well as a variety of decorations. Additionally there is a low * level extension mechanism to allow adding, as of now, not supported additional attributes * to the cluster. In the later case the application is responible for not breaking * rendering with these low level patches. * * Data representation * * The unicode codepoints for an cluster are either represented as a up to 8 byte utf8 * sequence inline in the cell structure or by an reference to an separate overflow node * in case they do not fit within that space. These overflow nodes are managed with an * auxilary (per surface) hash table. Unused entries are expired when the hash table would * have to grow otherwise. * * Attributes consist of the following: * - bold (yes/no) * - italic (yes/no) * - underline (none, single, double, curly) with decoration color (default or 256 color * or direct color value) * - blinking (yes/no) * - overline (yes/no) * - inverse (yes/no) * - strikethrough (yes/no) * - foreground color (default or 16 colors (named or bright named) * or 256 color or direct color) * - background color (same options as foreground color) * - patch (an beginning and ending string of control sequences; 0 no patch else index + 1 of patches array in surface) * * text_len == 0 && text_overflow == nullptr -> same as ' ' * text_len == 0 && text_overflow == WIDE_RIGHT_PADDING -> character hidden by multi cell cluster * text_len == 1 && text[0] == '\x01', only in cells_last_flush => cell was hidden, will need repaint if start of char. * * Additional invariants: * - The colors and flags (except CELL_SOFTWRAP_MARKER) of all cells in a cluster are identical. */ struct termpaint_attr_ { uint32_t fg_color; uint32_t bg_color; uint32_t deco_color; uint16_t flags; // If no patch is active all these are 0, otherwise both setup and cleanup contain non zero pointers. bool patch_optimize; unsigned char* patch_setup; unsigned char* patch_cleanup; }; #define CELL_ATTR_BOLD (1 << 0) #define CELL_ATTR_ITALIC (1 << 1) #define CELL_ATTR_UNDERLINE_MASK (3 << 2) #define CELL_ATTR_UNDERLINE_SINGLE (1 << 2) #define CELL_ATTR_UNDERLINE_DOUBLE (2 << 2) #define CELL_ATTR_UNDERLINE_CURLY (3 << 2) #define CELL_ATTR_BLINK (1 << 4) #define CELL_ATTR_OVERLINE (1 << 5) #define CELL_ATTR_INVERSE (1 << 6) #define CELL_ATTR_STRIKE (1 << 7) #define CELL_ATTR_DECO_MASK CELL_ATTR_UNDERLINE_MASK #define CELL_SOFTWRAP_MARKER (1 << 15) #define CELL_ATTR_MASK ((uint16_t)(~CELL_SOFTWRAP_MARKER)) #define TERMPAINT_STYLE_PASSTHROUGH (TERMPAINT_STYLE_BOLD | TERMPAINT_STYLE_ITALIC | TERMPAINT_STYLE_BLINK \ | TERMPAINT_STYLE_OVERLINE | TERMPAINT_STYLE_INVERSE | TERMPAINT_STYLE_STRIKE) #define WIDE_RIGHT_PADDING ((termpaint_hash_item*)-1) typedef struct cell_ { uint32_t fg_color; uint32_t bg_color; uint32_t deco_color; uint16_t flags; // bold, italic, underline[2], blinking, overline, inverse, strikethrough. softwrap marker uint8_t attr_patch_idx; uint8_t cluster_expansion : 4; uint8_t text_len : 4; // == 0 -> text_overflow is active or WIDE_RIGHT_PADDING. union { termpaint_hash_item* text_overflow; unsigned char text[8]; }; } cell; _Static_assert(sizeof(void*) > 8 || sizeof(cell) == 24, "bad cell size"); typedef struct termpaintp_patch_ { bool optimize; uint32_t setup_hash; unsigned char *setup; uint32_t cleanup_hash; unsigned char *cleanup; bool unused; } termpaintp_patch; struct termpaint_surface_ { termpaint_terminal *terminal; bool primary; cell* cells; cell* cells_last_flush; unsigned cells_allocated; int width; int height; termpaint_hash overflow_text; termpaintp_patch *patches; }; typedef enum auto_detect_state_ { AD_NONE, AD_INITIAL, AD_FINISHED, // does \033[5n work? AD_BASICCOMPAT, // Basics: cursor position, secondary id, device ready? AD_BASIC_REQ, AD_BASIC_CURPOS_RECVED, AD_BASIC_REQ_FAILED_CURPOS_RECVED, AD_BASIC_CURPOS_RECVED_NO_SEC_DEV_ATTRIB, AD_BASIC_NO_SEC_DEV_ATTRIB_MISPARSING, AD_BASIC_SEC_DEV_ATTRIB_RECVED, AD_BASIC_SEC_DEV_ATTRIB_RECVED_CONSUME_CURPOS, // urxvt palette size detection AD_URXVT_88_256_REQ, // finger print 1: Test for 'private' cursor position, xterm secondary id quirk, vte CSI 1x quirk AD_FP1_REQ, AD_FP1_REQ_TERMID_RECVED, AD_FP1_REQ_TERMID_RECVED_SEC_DEV_ATTRIB_RECVED, AD_FP1_SEC_DEV_ATTRIB_RECVED, AD_FP1_SEC_DEV_ATTRIB_QMCURSOR_POS_RECVED, AD_FP1_QMCURSOR_POS_RECVED, AD_FP1_3RD_DEV_ATTRIB_ALIASED_TO_PRI, AD_FP1_CLEANUP_AFTER_SYNC, AD_FP1_CLEANUP, AD_EXPECT_SYNC_TO_FINISH, AD_WAIT_FOR_SYNC_TO_FINISH, // finger print 2: Test for konsole repeated secondary id quirk (2 ansers), Test for VTE secondary id quirk (no answer) AD_FP2_REQ, AD_FP2_CURSOR_DONE, AD_FP2_SEC_DEV_ATTRIB_RECVED1, AD_FP2_SEC_DEV_ATTRIB_RECVED2, // post AD_WAIT_FOR_SYNC_TO_SELF_REPORTING, AD_EXPECT_SYNC_TO_SELF_REPORTING, AD_SELF_REPORTING, // sub routine AD_GLITCH_PATCHING, // hacks AD_HTERM_RECOVERY1, AD_HTERM_RECOVERY2 } auto_detect_state; typedef enum terminal_type_enum_ { TT_INCOMPATIBLE, // does not respond to ESC 5n, or similar deal breakers TT_TOODUMB, TT_MISPARSING, // valid sequences (bare or >) leave visual traces TT_UNKNOWN, TT_BASE, TT_XTERM, TT_URXVT, TT_MLTERM, TT_KONSOLE, TT_VTE, TT_SCREEN, TT_TMUX, TT_LINUXVC, TT_MACOS, TT_ITERM2, TT_TERMINOLOGY, TT_KITTY, TT_MINTTY, TT_MSFT_TERMINAL, TT_FULL, } terminal_type_enum; enum termpaint_color_entry_save_state_t { termpaint_save_state_new_entry = 0, // set via calloc termpaint_save_state_ready, termpaint_save_state_save_requested }; typedef struct termpaint_str_ { unsigned len; unsigned alloc; unsigned char *data; } termpaint_str; typedef struct termpaint_color_entry_ { termpaint_hash_item base; termpaint_str restore; termpaint_str requested; bool dirty; enum termpaint_color_entry_save_state_t save_state; struct termpaint_color_entry_ *next_dirty; } termpaint_color_entry; typedef struct termpaint_unpause_snippet_ { termpaint_hash_item base; termpaint_str sequences; } termpaint_unpause_snippet; typedef struct termpaint_integration_private_ { void (*free)(struct termpaint_integration_ *integration); void (*write)(struct termpaint_integration_ *integration, const char *data, int length); void (*flush)(struct termpaint_integration_ *integration); _Bool (*is_bad)(struct termpaint_integration_ *integration); void (*request_callback)(struct termpaint_integration_ *integration); void (*awaiting_response)(struct termpaint_integration_ *integration); void (*restore_sequence_updated)(struct termpaint_integration_ *integration, const char *data, int length); void (*logging_func)(struct termpaint_integration_ *integration, const char *data, int length); } termpaint_integration_private; #define NUM_CAPABILITIES 16 #define SETUP_STATE_FULLSCREEN 1 #define SETUP_STATE_INLINE 2 typedef struct termpaint_terminal_ { termpaint_integration *integration; termpaint_integration_private *integration_vtbl; termpaint_surface primary; termpaint_input *input; bool force_full_repaint; bool data_pending_after_input_received : 1; bool request_repaint : 1; termpaint_str auto_detect_sec_device_attributes; // line of the terminal cursor relativ to the top of the inline display in the terminal; -1 if not in inline mode. int inline_current_terminal_cursor_line; // number of rows currently shown in terminal in inline mode; 0 if no lines are currently painted in inline mode. int last_inline_height; int setup_state; int terminal_type; const termpaintp_width *char_width_table; int terminal_version; int terminal_type_confidence; termpaint_str terminal_self_reported_name_version; void (*event_cb)(void *, termpaint_event *); void *event_user_data; bool (*raw_input_filter_cb)(void *user_data, const char *data, unsigned length, bool overflow); void *raw_input_filter_user_data; int initial_cursor_x; int initial_cursor_y; bool cursor_visible; int cursor_x; int cursor_y; int cursor_style; bool cursor_blink; unsigned did_terminal_push_title : 1; unsigned did_terminal_add_mouse_to_restore : 1; unsigned did_terminal_enable_mouse : 1; unsigned did_terminal_add_focusreporting_to_restore : 1; unsigned did_terminal_add_bracketedpaste_to_restore : 1; unsigned did_terminal_disable_wrap : 1; unsigned altscreen_active : 1; unsigned cache_should_use_truecolor : 1; termpaint_str unpause_basic_setup; termpaint_hash unpause_snippets; bool glitch_on_oom; unsigned log_mask; int cursor_prev_data; // -1 -> no touched yet, restore not setup, -2 -> force resend sequence (e.g. atfer unpause) termpaint_hash colors; termpaint_color_entry *colors_dirty; termpaint_str restore_seq_partial; termpaint_str restore_seq_cached; auto_detect_state ad_state; // additional auto detect state machine temporary space int glitch_cursor_x; int glitch_cursor_y; bool seen_dec_terminal_param; auto_detect_state glitch_patching_next_state; // bool capabilities[NUM_CAPABILITIES]; int max_csi_parameters; } termpaint_terminal; typedef enum termpaint_text_measurement_state_ { TM_INITIAL, TM_IN_CLUSTER } termpaint_text_measurement_state; typedef enum termpaint_text_measurement_decoder_state_ { TMD_INITIAL, TMD_PARTIAL_UTF16, TMD_PARTIAL_UTF8 } termpaint_text_measurement_decoder_state; struct termpaint_text_measurement_ { termpaint_terminal *terminal; int pending_codepoints; int pending_clusters; int pending_width; int pending_ref; int last_codepoints; int last_clusters; int last_width; int last_ref; termpaint_text_measurement_state state; int limit_codepoints; int limit_clusters; int limit_width; int limit_ref; // utf decoder state termpaint_text_measurement_decoder_state decoder_state; uint16_t utf_16_high; uint8_t utf8_size; uint8_t utf8_available; uint8_t utf8_units[6]; }; static size_t ustrlen (const uchar *s) { return strlen((const char*)s); } static unsigned char *ustrdup (const uchar *s) { return (unsigned char*)strdup((const char*)s); } static int ustrcmp (const uchar *s1, const uchar *s2) { return strcmp((const char*)s1, (const char*)s2); } static bool ustr_eq (const uchar *s1, const uchar *s2) { return strcmp((const char*)s1, (const char*)s2) == 0; } static void int_debuglog_puts(termpaint_terminal *term, const char *str); static void int_debuglog_printf(termpaint_terminal *term, const char *fmt, ...) ATTRIBUTE_PRINTF; static void termpaintp_oom_log_only(termpaint_terminal *term) { int_debuglog_puts(term, "failed to allocate memory, output will be incomplete\n"); } static void termpaintp_oom(termpaint_terminal *term) { int_debuglog_puts(term, "failed to allocate memory, aborting\n"); abort(); } static void termpaintp_oom_int(termpaint_integration *integration) { const char *msg = "failed to allocate memory, aborting\n"; if (integration->p->logging_func) { integration->p->logging_func(integration, msg, strlen(msg)); } abort(); } static void termpaintp_oom_nolog(void) { abort(); } static int replace_unusable_codepoints(int codepoint) { if (codepoint < 32 || (codepoint >= 0x7f && codepoint < 0xa0)) { return ' '; } else if (codepoint == 0xad) { // soft hyphen // Some implementations see this as spacing others as non spacing, make sure we get something spacing. return '-'; } else { return codepoint; } } static bool termpaintp_string_prefix(const unsigned char * prefix, const unsigned char *s, int len) { const int plen = ustrlen(prefix); if (plen <= len) { return memcmp(prefix, s, plen) == 0; } return false; } static bool termpaintp_char_ascii_num(char c) { return '0' <= c && c <= '9'; } static char termpaintp_char_ascii_to_lower(char c) { if ('A' <= c && c <= 'Z') { return c | 0x20; } return c; } static bool termpaintp_mem_ascii_case_insensitive_equals(const char *a, const char *b, size_t len) { for (size_t i = 0; i < len; i++) { char ch1 = termpaintp_char_ascii_to_lower(a[i]); char ch2 = termpaintp_char_ascii_to_lower(b[i]); if (ch1 == ch2) continue; return false; } return true; } static void termpaintp_str_realloc(termpaint_str *tps, unsigned len); static void termpaintp_prepend_str(termpaint_str *tps, const unsigned char* src) { size_t old_len = tps->len; size_t src_len = ustrlen(src); termpaintp_str_realloc(tps, old_len + src_len); if (old_len) { memmove(tps->data + src_len, tps->data, old_len); } tps->data[src_len + old_len] = 0; memcpy(tps->data, src, src_len); tps->len = src_len + old_len; } static bool termpaintp_str_ends_with(const unsigned char* str, const unsigned char* postfix) { size_t str_len = ustrlen(str); size_t postfix_len = ustrlen(postfix); if (str_len < postfix_len) return false; return memcmp(str + str_len - postfix_len, postfix, postfix_len) == 0; } static bool termpaintp_str_preallocate(termpaint_str *tps, unsigned len) { if (tps->len || tps->data) { BUG("preallocation only valid on unused tps"); } tps->alloc = len + 1; tps->data = malloc(tps->alloc); if (!tps->data) { tps->alloc = 0; return false; } tps->data[0] = 0; return true; } // make sure tps has len capacity. contents of *data is undefined after this static void termpaintp_str_w_e(termpaint_str *tps, unsigned len) { if (tps->alloc <= len) { free(tps->data); tps->alloc = len + 1; tps->data = malloc(tps->alloc); if (!tps->data) { termpaintp_oom_nolog(); } } } static bool termpaintp_str_w_e_mustcheck(termpaint_str *tps, unsigned len) { if (tps->alloc <= len) { unsigned char* data_new = malloc(len + 1); if (!data_new) { return false; } free(tps->data); tps->alloc = len + 1; tps->data = data_new; } return true; } static void termpaintp_str_realloc(termpaint_str *tps, unsigned len) { if (tps->alloc <= len) { tps->alloc = len + 1; tps->data = realloc(tps->data, tps->alloc); if (!tps->data) { termpaintp_oom_nolog(); } } } static void termpaintp_str_assign_n(termpaint_str *tps, const char *s, unsigned len) { termpaintp_str_w_e(tps, len); memcpy(tps->data, s, len); tps->data[len] = 0; tps->len = len; } static void termpaintp_str_assign(termpaint_str *tps, const char *s) { unsigned len = (unsigned)strlen(s); termpaintp_str_w_e(tps, len); memcpy(tps->data, s, len + 1); tps->len = len; } static bool termpaintp_str_assign_mustcheck(termpaint_str *tps, const char *s) { unsigned len = (unsigned)strlen(s); if (!termpaintp_str_w_e_mustcheck(tps, len)) { return false; } memcpy(tps->data, s, len + 1); tps->len = len; return true; } static void termpaintp_str_destroy(termpaint_str *tps) { free(tps->data); tps->data = nullptr; tps->len = 0; tps->alloc = 0; } static void termpaintp_str_append_n(termpaint_str *tps, const char *s, unsigned len) { termpaintp_str_realloc(tps, tps->len + len); memcpy(tps->data + tps->len, s, len); tps->len = tps->len + len; tps->data[tps->len] = 0; } static void termpaintp_str_append(termpaint_str *tps, const char *s) { termpaintp_str_append_n(tps, s, (unsigned)strlen(s)); } static void termpaintp_str_append_printable_n(termpaint_str *tps, const char *str, int len) { int input_bytes_used = 0; termpaintp_str_realloc(tps, tps->len + (unsigned)len); while (input_bytes_used < len) { int size = termpaintp_utf8_len((unsigned char)str[input_bytes_used]); // check termpaintp_utf8_decode_from_utf8 precondition if (input_bytes_used + size > len) { // bogus, bail return; } if (termpaintp_check_valid_sequence((const unsigned char*)str + input_bytes_used, size)) { int codepoint = termpaintp_utf8_decode_from_utf8((const unsigned char*)str + input_bytes_used, size); int new_codepoint = replace_unusable_codepoints(codepoint); if (codepoint == new_codepoint) { termpaintp_str_append_n(tps, str + input_bytes_used, size); } else { if (new_codepoint < 128) { char ch; ch = new_codepoint; termpaintp_str_append_n(tps, &ch, 1); } } } input_bytes_used += size; } } #define TERMPAINTP_STR_STORE_S(var, value) const char *var = (value) #define TERMPAINTP_STR_SIZER_S(value) strlen(value) #define TERMPAINTP_STR_APPEND_S(tps, value, len) termpaintp_str_append_n((tps), (value), (len)) #define TERMPAINTP_STR_STORE_F(var, value) const char *var = (value) #define TERMPAINTP_STR_SIZER_F(value) strlen(value) #define TERMPAINTP_STR_APPEND_F(tps, value, len) termpaintp_str_append_printable_n((tps), (value), (len)) #define TERMPAINT_STR_ASSIGN3_MUSTCHECK(tps, type1, value1, type2, value2, type3, value3) \ do { \ termpaint_str* tps_TMP_tps = (tps); \ TERMPAINTP_STR_STORE_##type1(tps_TMP_1, (value1)); \ TERMPAINTP_STR_STORE_##type2(tps_TMP_2, (value2)); \ TERMPAINTP_STR_STORE_##type3(tps_TMP_3, (value3)); \ unsigned tps_TMP_len1 = (unsigned)TERMPAINTP_STR_SIZER_##type1(tps_TMP_1); \ unsigned tps_TMP_len2 = (unsigned)TERMPAINTP_STR_SIZER_##type2(tps_TMP_2); \ unsigned tps_TMP_len3 = (unsigned)TERMPAINTP_STR_SIZER_##type3(tps_TMP_3); \ tps_TMP_tps->len = 0; \ if (termpaintp_str_w_e_mustcheck(tps_TMP_tps, tps_TMP_len1 + tps_TMP_len2 + tps_TMP_len3)) { \ TERMPAINTP_STR_APPEND_##type1(tps_TMP_tps, tps_TMP_1, (tps_TMP_len1)); \ TERMPAINTP_STR_APPEND_##type2(tps_TMP_tps, tps_TMP_2, (tps_TMP_len2)); \ TERMPAINTP_STR_APPEND_##type3(tps_TMP_tps, tps_TMP_3, (tps_TMP_len3)); \ } \ } while (0) \ /* end */ bool termpaint_integration_init_mustcheck(termpaint_integration *integration, void (*free)(termpaint_integration *integration), void (*write)(termpaint_integration *integration, const char *data, int length), void (*flush)(termpaint_integration *integration)) { integration->p = calloc(1, sizeof(termpaint_integration_private)); if (!integration->p) { return false; } integration->p->free = free; integration->p->write = write; integration->p->flush = flush; return true; } _tERMPAINT_PUBLIC void termpaint_integration_init(termpaint_integration *integration, void (*free)(termpaint_integration *integration), void (*write)(termpaint_integration *integration, const char *data, int length), void (*flush)(termpaint_integration *integration)) { if (!termpaint_integration_init_mustcheck(integration, free, write, flush)) { termpaintp_oom_nolog(); } } _tERMPAINT_PUBLIC void termpaint_integration_set_is_bad(termpaint_integration *integration, _Bool (*is_bad)(termpaint_integration *integration)) { integration->p->is_bad = is_bad; } _tERMPAINT_PUBLIC void termpaint_integration_set_request_callback(termpaint_integration *integration, void (*request_callback)(termpaint_integration *integration)) { integration->p->request_callback = request_callback; } _tERMPAINT_PUBLIC void termpaint_integration_set_awaiting_response(termpaint_integration *integration, void (*awaiting_response)(termpaint_integration *integration)) { integration->p->awaiting_response = awaiting_response; } _tERMPAINT_PUBLIC void termpaint_integration_set_restore_sequence_updated(termpaint_integration *integration, void (*restore_sequence_updated)(termpaint_integration *integration, const char *data, int length)) { integration->p->restore_sequence_updated = restore_sequence_updated; } _tERMPAINT_PUBLIC void termpaint_integration_set_logging_func(termpaint_integration *integration, void (*logging_func)(termpaint_integration *integration, const char *data, int length)) { integration->p->logging_func = logging_func; } void termpaint_integration_deinit(termpaint_integration *integration) { free(integration->p); integration->p = nullptr; } static void termpaintp_collapse(termpaint_surface *surface) { surface->width = 0; surface->height = 0; surface->cells_allocated = 0; surface->cells = nullptr; surface->cells_last_flush = nullptr; } static bool termpaintp_resize_mustcheck(termpaint_surface *surface, int width, int height) { // TODO move contents along? surface->width = width; surface->height = height; _Static_assert(sizeof(int) <= sizeof(size_t), "int smaller than size_t"); int bytes; int cell_count; if ( (width < 0) || (height < 0) || termpaint_smul_overflow(width, height, &cell_count) || termpaint_smul_overflow(cell_count, sizeof(cell), &bytes)) { // collapse and bail int_debuglog_printf(surface->terminal, "surface resize: Invalid size %dx%d, collapsing surface.", width, height); free(surface->cells); free(surface->cells_last_flush); termpaintp_collapse(surface); return true; // This is debatable, but the previous code did allow this and there are tests for this. } surface->cells_allocated = cell_count; free(surface->cells); free(surface->cells_last_flush); surface->cells_last_flush = nullptr; surface->cells = calloc(1, bytes); if (!surface->cells) { termpaintp_collapse(surface); return false; } if (surface->primary) { surface->terminal->force_full_repaint = true; surface->cells_last_flush = calloc(1, surface->cells_allocated * sizeof(cell)); if (!surface->cells_last_flush) { free(surface->cells); termpaintp_collapse(surface); return false; } } return true; } static inline cell* termpaintp_getcell(const termpaint_surface *surface, int x, int y) { unsigned index = y*surface->width + x; if (x >= 0 && y >= 0 && x < surface->width && y < surface->height && index < surface->cells_allocated) { return &surface->cells[index]; } else { BUG("cell out of range"); } } static inline cell* termpaintp_getcell_or_null(const termpaint_surface *surface, int x, int y) { unsigned index = y*surface->width + x; if (x >= 0 && y >= 0 && x < surface->width && y < surface->height) { if (index < surface->cells_allocated) { return &surface->cells[index]; } else { BUG("cell out of range"); } } else { return nullptr; } } static void termpaintp_set_overflow_text(termpaint_surface *surface, cell *dst_cell, const unsigned char* data) { // hash_ensure needs to be done before touching text_len, because it can cause garbage collection which would // see an inconistant state if text_len is already set to zero. void* overflow_ptr = termpaintp_hash_ensure(&surface->overflow_text, data); if (!overflow_ptr) { if (!surface->terminal->glitch_on_oom) { termpaintp_oom(surface->terminal); } else { termpaintp_oom_log_only(surface->terminal); dst_cell->text_len = 1; dst_cell->text[0] = '?'; } } dst_cell->text_len = 0; dst_cell->text_overflow = overflow_ptr; } static void termpaintp_surface_destroy(termpaint_surface *surface) { free(surface->cells); free(surface->cells_last_flush); termpaintp_hash_destroy(&surface->overflow_text); if (surface->patches) { for (int i = 0; i < 255; ++i) { free(surface->patches[i].setup); free(surface->patches[i].cleanup); } free(surface->patches); surface->patches = nullptr; } termpaintp_collapse(surface); } static uint8_t termpaintp_surface_ensure_patch_idx(termpaint_surface *surface, bool optimize, unsigned char *setup, unsigned char *cleanup) { if (!setup || !cleanup) { return 0; } if (!surface->patches) { surface->patches = calloc(255, sizeof(termpaintp_patch)); if (!surface->patches) { if (!surface->terminal->glitch_on_oom) { termpaintp_oom(surface->terminal); } else { termpaintp_oom_log_only(surface->terminal); return 0; } } } uint32_t setup_hash = termpaintp_hash_fnv1a(setup); uint32_t cleanup_hash = termpaintp_hash_fnv1a(cleanup); int free_slot = -1; for (int i = 0; i < 255; ++i) { if (!surface->patches[i].setup) { if (free_slot == -1) { free_slot = i; } continue; } if (surface->patches[i].setup_hash == setup_hash && surface->patches[i].cleanup_hash == cleanup_hash && ustrcmp(setup, surface->patches[i].setup) == 0 && ustrcmp(cleanup, surface->patches[i].cleanup) == 0) { return i + 1; } } if (free_slot == -1) { // try to free unused entries for (int i = 0; i < 255; ++i) { surface->patches[i].unused = true; } for (int y = 0; y < surface->height; y++) { for (int x = 0; x < surface->width; x++) { cell* c = termpaintp_getcell(surface, x, y); if (c->attr_patch_idx) { surface->patches[c->attr_patch_idx - 1].unused = false; } if (surface->cells_last_flush) { cell* old_c = &surface->cells_last_flush[y*surface->width+x]; if (old_c->attr_patch_idx) { surface->patches[old_c->attr_patch_idx - 1].unused = false; } } } } for (int i = 0; i < 255; ++i) { if (surface->patches[i].unused) { free(surface->patches[i].setup); free(surface->patches[i].cleanup); surface->patches[i].setup = nullptr; surface->patches[i].cleanup = nullptr; if (free_slot == -1) { free_slot = i; } } } } if (free_slot != -1) { unsigned char *setup_copy = ustrdup(setup); unsigned char *cleanup_copy = ustrdup(cleanup); if (!setup_copy || !cleanup_copy) { if (!surface->terminal->glitch_on_oom) { termpaintp_oom(surface->terminal); } else { free(setup_copy); free(cleanup_copy); termpaintp_oom_log_only(surface->terminal); return 0; } } surface->patches[free_slot].optimize = optimize; surface->patches[free_slot].setup_hash = setup_hash; surface->patches[free_slot].cleanup_hash = cleanup_hash; surface->patches[free_slot].setup = setup_copy; surface->patches[free_slot].cleanup = cleanup_copy; return free_slot + 1; } // can't fit anymore, just ignore it. return 0; } void termpaint_surface_write_with_colors(termpaint_surface *surface, int x, int y, const char *string, int fg, int bg) { termpaint_surface_write_with_colors_clipped(surface, x, y, string, fg, bg, 0, surface->width-1); } void termpaint_surface_write_with_len_colors(termpaint_surface *surface, int x, int y, const char *string, int len, int fg, int bg) { termpaint_surface_write_with_len_colors_clipped(surface, x, y, string, len, fg, bg, 0, surface->width-1); } void termpaint_surface_write_with_colors_clipped(termpaint_surface *surface, int x, int y, const char *string, int fg, int bg, int clip_x0, int clip_x1) { int len = strlen(string); termpaint_surface_write_with_len_colors_clipped(surface, x, y, string, len, fg, bg, clip_x0, clip_x1); } void termpaint_surface_write_with_len_colors_clipped(termpaint_surface *surface, int x, int y, const char *string, int len, int fg, int bg, int clip_x0, int clip_x1) { termpaint_attr attr; attr.fg_color = fg; attr.bg_color = bg; attr.deco_color = TERMPAINT_DEFAULT_COLOR; attr.flags = 0; attr.patch_setup = nullptr; attr.patch_cleanup = nullptr; attr.patch_optimize = false; termpaint_surface_write_with_len_attr_clipped(surface, x, y, string, len, &attr, clip_x0, clip_x1); } void termpaint_surface_write_with_attr(termpaint_surface *surface, int x, int y, const char *string, const termpaint_attr *attr) { termpaint_surface_write_with_attr_clipped(surface, x, y, string, attr, 0, surface->width-1); } void termpaint_surface_write_with_len_attr(termpaint_surface *surface, int x, int y, const char *string, int len, const termpaint_attr *attr) { termpaint_surface_write_with_len_attr_clipped(surface, x, y, string, len, attr, 0, surface->width-1); } // This ensures that cells [x, x + cluster_width) have cluster_expansion = 0 static void termpaintp_surface_vanish_char(termpaint_surface *surface, int x, int y, int cluster_width) { // narrow contract, x + cluster_width <= width cell *cell = termpaintp_getcell(surface, x, y); int rightmost_vanished = x; if (cell->text_len == 0 && cell->text_overflow == WIDE_RIGHT_PADDING) { int i = x; while (cell->text_len == 0 && cell->text_overflow == WIDE_RIGHT_PADDING) { cell->text_len = 1; cell->text[0] = ' '; rightmost_vanished = i; // cell->cluster_expansion == 0 already because padding cell if (i + 1 == surface->width) { break; } ++i; cell = termpaintp_getcell(surface, i, y); } i = x - 1; do { cell = termpaintp_getcell(surface, i, y); cell->text_len = 1; cell->text[0] = ' '; // cell->cluster_expansion == 0 already unless this is the last iteration, see fixup below --i; } while (cell->cluster_expansion == 0); cell->cluster_expansion = 0; } for (int i = rightmost_vanished; i <= x + cluster_width - 1; i++) { cell = termpaintp_getcell(surface, i, y); int expansion = cell->cluster_expansion; int j = 0; while (1) { cell->cluster_expansion = 0; cell->text_len = 1; cell->text[0] = ' '; ++j; if (j > expansion) { break; } cell = termpaintp_getcell(surface, i + j, y); } i += expansion; } } static void termpaintp_surface_attr_apply(termpaint_surface *surface, cell *cell, termpaint_attr const *attr) { cell->fg_color = attr->fg_color; cell->bg_color = attr->bg_color; cell->deco_color = attr->deco_color; cell->flags = attr->flags; cell->attr_patch_idx = termpaintp_surface_ensure_patch_idx(surface, attr->patch_optimize, attr->patch_setup, attr->patch_cleanup); } void termpaint_surface_write_with_attr_clipped(termpaint_surface *surface, int x, int y, const char *string_s, termpaint_attr const *attr, int clip_x0, int clip_x1) { int len = strlen(string_s); termpaint_surface_write_with_len_attr_clipped(surface, x, y, string_s, len, attr, clip_x0, clip_x1); } void termpaint_surface_write_with_len_attr_clipped(termpaint_surface *surface, int x, int y, const char *string_s, int len, termpaint_attr const *attr, int clip_x0, int clip_x1) { const termpaintp_width *char_width_table = surface->terminal->char_width_table; const unsigned char *string = (const unsigned char *)string_s; if (y < 0) return; if (clip_x0 < 0) clip_x0 = 0; if (clip_x1 >= surface->width) { clip_x1 = surface->width-1; } while (len) { if (x > clip_x1 || y >= surface->height) { return; } unsigned char cluster_utf8[40]; int cluster_width = 1; int input_bytes_used = 0; size_t output_bytes_used = 0; // ATTENTION keep this in sync with termpaint_text_measurement_feed_codepoint while (len - input_bytes_used) { int size = termpaintp_utf8_len(string[input_bytes_used]); // check termpaintp_utf8_decode_from_utf8 precondition if (input_bytes_used + size > len) { // bogus, bail return; } int codepoint; if (termpaintp_check_valid_sequence(string + input_bytes_used, size)) { codepoint = termpaintp_utf8_decode_from_utf8(string + input_bytes_used, size); } else { // This is bogus usage, but just paper over it codepoint = 0xFFFD; } if (codepoint != '\x7f' || output_bytes_used != 0) { codepoint = replace_unusable_codepoints(codepoint); int width = termpaintp_char_width(char_width_table, codepoint); if (!output_bytes_used) { if (width == 0) { // if start is 0 width use U+00a0 as base output_bytes_used += termpaintp_encode_to_utf8(0xa0, cluster_utf8 + output_bytes_used); } else { cluster_width = width; } output_bytes_used += termpaintp_encode_to_utf8(codepoint, cluster_utf8 + output_bytes_used); } else { if (width > 0) { // don't increase input_bytes_used here because this codepoint will need to be reprocessed. break; } if (output_bytes_used + 6 < sizeof (cluster_utf8)) { output_bytes_used += termpaintp_encode_to_utf8(codepoint, cluster_utf8 + output_bytes_used); } else { // just ignore further combining codepoints, likely this is way over the limit // of the terminal anyway } } } else { output_bytes_used = 0; input_bytes_used += size; // do not allow any non spacing modifiers break; } input_bytes_used += size; } if (cluster_width == 2 && x + 1 == clip_x0) { // char is split by clipping boundary. Fill in right half as if the char was split later cell *c = termpaintp_getcell(surface, x + 1, y); termpaintp_surface_vanish_char(surface, x + 1, y, cluster_width - 1); c->cluster_expansion = 0; termpaintp_surface_attr_apply(surface, c, attr); c->text[0] = ' '; c->text_len = 1; } else if (x + cluster_width - 1 > clip_x1) { // char is split by clipping boundary. Fill in left half as if the char was split later cell *c = termpaintp_getcell(surface, x, y); termpaintp_surface_vanish_char(surface, x, y, cluster_width - 1); c->cluster_expansion = 0; termpaintp_surface_attr_apply(surface, c, attr); c->text[0] = ' '; c->text_len = 1; } else if (x >= clip_x0) { cell *c = termpaintp_getcell(surface, x, y); termpaintp_surface_vanish_char(surface, x, y, cluster_width); termpaintp_surface_attr_apply(surface, c, attr); c->cluster_expansion = cluster_width - 1; if (output_bytes_used <= 8) { if (output_bytes_used) { memcpy(c->text, cluster_utf8, output_bytes_used); c->text_len = output_bytes_used; } else { c->text_len = 0; c->text_overflow = nullptr; } } else { cluster_utf8[output_bytes_used] = 0; termpaintp_set_overflow_text(surface, c, cluster_utf8); } for (int i = 1; i < cluster_width; i++) { cell *c = termpaintp_getcell(surface, x + i, y); termpaintp_surface_attr_apply(surface, c, attr); c->cluster_expansion = 0; c->text_len = 0; c->text_overflow = WIDE_RIGHT_PADDING; } } string += input_bytes_used; len -= input_bytes_used; x = x + cluster_width; } } void termpaint_surface_clear_with_attr(termpaint_surface *surface, const termpaint_attr *attr) { termpaint_surface_clear_rect_with_attr(surface, 0, 0, surface->width, surface->height, attr); } void termpaint_surface_clear_with_attr_char(termpaint_surface *surface, const termpaint_attr *attr, int codepoint) { termpaint_surface_clear_rect_with_attr_char(surface, 0, 0, surface->width, surface->height, attr, codepoint); } void termpaint_surface_clear(termpaint_surface *surface, int fg, int bg) { termpaint_surface_clear_rect(surface, 0, 0, surface->width, surface->height, fg, bg); } void termpaint_surface_clear_with_char(termpaint_surface *surface, int fg, int bg, int codepoint) { termpaint_surface_clear_rect_with_char(surface, 0, 0, surface->width, surface->height, fg, bg, codepoint); } static void termpaintp_surface_clear_rect_with_attr_and_string(termpaint_surface *surface, int x, int y, int width, int height, const termpaint_attr *attr, const unsigned char* str, unsigned len) { if (x < 0) { width += x; x = 0; } if (y < 0) { height += y; y = 0; } if (width <= 0) return; if (x >= surface->width) return; if (y >= surface->height) return; if (x+width > surface->width) width = surface->width - x; if (y+height > surface->height) height = surface->height - y; for (int y1 = y; y1 < y + height; y1++) { termpaintp_surface_vanish_char(surface, x, y1, 1); termpaintp_surface_vanish_char(surface, x + width - 1, y1, 1); for (int x1 = x; x1 < x + width; x1++) { cell* c = termpaintp_getcell(surface, x1, y1); c->cluster_expansion = 0; if (str) { c->text_len = len; memcpy(c->text, str, len); } else { c->text_len = 0; c->text_overflow = nullptr; } c->bg_color = attr->bg_color; c->fg_color = attr->fg_color; c->deco_color = TERMPAINT_DEFAULT_COLOR; c->flags = attr->flags; c->attr_patch_idx = 0; } } } void termpaint_surface_clear_rect_with_attr(termpaint_surface *surface, int x, int y, int width, int height, const termpaint_attr *attr) { termpaintp_surface_clear_rect_with_attr_and_string(surface, x, y, width, height, attr, nullptr, 0); } void termpaint_surface_clear_rect_with_attr_char(termpaint_surface *surface, int x, int y, int width, int height, const termpaint_attr *attr, int codepoint) { const termpaintp_width *char_width_table = surface->terminal->char_width_table; int codepointSanitized = replace_unusable_codepoints(codepoint); int codepointWidth = termpaintp_char_width(char_width_table, codepoint); if (codepoint == '\x7f' || codepointWidth != 1) { termpaint_surface_clear_rect_with_attr(surface, x, y, width, height, attr); } else { unsigned char buf[6]; int len = termpaintp_encode_to_utf8(codepointSanitized, buf); termpaintp_surface_clear_rect_with_attr_and_string(surface, x, y, width, height, attr, buf, len); } } void termpaint_surface_clear_rect(termpaint_surface *surface, int x, int y, int width, int height, int fg, int bg) { termpaint_attr attr; attr.fg_color = fg; attr.bg_color = bg; attr.deco_color = TERMPAINT_DEFAULT_COLOR; attr.flags = 0; attr.patch_setup = nullptr; attr.patch_cleanup = nullptr; termpaint_surface_clear_rect_with_attr(surface, x, y, width, height, &attr); } void termpaint_surface_clear_rect_with_char(termpaint_surface *surface, int x, int y, int width, int height, int fg, int bg, int codepoint) { const termpaintp_width *char_width_table = surface->terminal->char_width_table; int codepointSanitized = replace_unusable_codepoints(codepoint); int codepointWidth = termpaintp_char_width(char_width_table, codepoint); if (codepoint == '\x7f' || codepointWidth != 1) { termpaint_surface_clear_rect(surface, x, y, width, height, fg, bg); } else { termpaint_attr attr; attr.fg_color = fg; attr.bg_color = bg; attr.deco_color = TERMPAINT_DEFAULT_COLOR; attr.flags = 0; attr.patch_setup = nullptr; attr.patch_cleanup = nullptr; termpaint_surface_clear_rect_with_attr_char(surface, x, y, width, height, &attr, codepointSanitized); } } void termpaint_surface_set_fg_color(const termpaint_surface *surface, int x, int y, unsigned fg) { if (x < 0) return; if (y < 0) return; if (x >= surface->width) return; if (y >= surface->height) return; cell* c = termpaintp_getcell(surface, x, y); if (c->text_len == 0 && c->text_overflow == WIDE_RIGHT_PADDING) { // only the first cell of a multi cell character can be used to change it. // This prevents duplicate changes with naive adjust each cell code. return; } c->fg_color = fg; for (int i = 0; i < c->cluster_expansion; i++) { cell* exp_cell = termpaintp_getcell(surface, x + 1 + i, y); exp_cell->fg_color = fg; } } void termpaint_surface_set_bg_color(const termpaint_surface *surface, int x, int y, unsigned bg) { if (x < 0) return; if (y < 0) return; if (x >= surface->width) return; if (y >= surface->height) return; cell* c = termpaintp_getcell(surface, x, y); if (c->text_len == 0 && c->text_overflow == WIDE_RIGHT_PADDING) { // only the first cell of a multi cell character can be used to change it. // This prevents duplicate changes with naive adjust each cell code. return; } c->bg_color = bg; for (int i = 0; i < c->cluster_expansion; i++) { cell* exp_cell = termpaintp_getcell(surface, x + 1 + i, y); exp_cell->bg_color = bg; } } void termpaint_surface_set_deco_color(const termpaint_surface *surface, int x, int y, unsigned deco_color) { if (x < 0) return; if (y < 0) return; if (x >= surface->width) return; if (y >= surface->height) return; cell* c = termpaintp_getcell(surface, x, y); if (c->text_len == 0 && c->text_overflow == WIDE_RIGHT_PADDING) { // only the first cell of a multi cell character can be used to change it. // This prevents duplicate changes with naive adjust each cell code. return; } c->deco_color = deco_color; for (int i = 0; i < c->cluster_expansion; i++) { cell* exp_cell = termpaintp_getcell(surface, x + 1 + i, y); exp_cell->deco_color = deco_color; } } void termpaint_surface_set_softwrap_marker(termpaint_surface *surface, int x, int y, bool state) { if (x < 0) return; if (y < 0) return; if (x >= surface->width) return; if (y >= surface->height) return; cell* c = termpaintp_getcell(surface, x, y); if (c->text_len == 0 && c->text_overflow == WIDE_RIGHT_PADDING) { // only the first cell of a multi cell character can be used to set the marker return; } if (state) { c->flags |= CELL_SOFTWRAP_MARKER; } else { c->flags &= ~CELL_SOFTWRAP_MARKER; } } bool termpaint_surface_resize_mustcheck(termpaint_surface *surface, int width, int height) { if (width < 0 || height < 0) { free(surface->cells); free(surface->cells_last_flush); termpaintp_collapse(surface); } else { if (!termpaintp_resize_mustcheck(surface, width, height)) { return false; } } return true; } void termpaint_surface_resize(termpaint_surface *surface, int width, int height) { if (!termpaint_surface_resize_mustcheck(surface, width, height)) { termpaintp_oom(surface->terminal); } } int termpaint_surface_width(const termpaint_surface *surface) { return surface->width; } int termpaint_surface_height(const termpaint_surface *surface) { return surface->height; } static void termpaintp_surface_gc_mark_cb(termpaint_hash *hash) { termpaint_surface *surface = container_of(hash, termpaint_surface, overflow_text); for (int y = 0; y < surface->height; y++) { for (int x = 0; x < surface->width; x++) { cell* c = termpaintp_getcell(surface, x, y); if (c->text_len == 0 && c->text_overflow != nullptr && c->text_overflow != WIDE_RIGHT_PADDING) { c->text_overflow->unused = false; } if (surface->cells_last_flush) { cell* old_c = &surface->cells_last_flush[y*surface->width+x]; if (old_c->text_len == 0 && old_c->text_overflow != nullptr && c->text_overflow != WIDE_RIGHT_PADDING) { old_c->text_overflow->unused = false; } } } } } static void termpaintp_surface_init(termpaint_surface *surface, termpaint_terminal *term) { surface->overflow_text.gc_mark_cb = termpaintp_surface_gc_mark_cb; surface->overflow_text.item_size = sizeof(termpaint_hash_item); surface->terminal = term; } termpaint_surface *termpaint_terminal_new_surface_or_nullptr(termpaint_terminal *term, int width, int height) { termpaint_surface *ret = calloc(1, sizeof(termpaint_surface)); if (!ret) { return nullptr; } termpaintp_surface_init(ret, term); termpaintp_collapse(ret); if (!termpaintp_resize_mustcheck(ret, width, height)) { termpaint_surface_free(ret); return nullptr; } return ret; } termpaint_surface *termpaint_terminal_new_surface(termpaint_terminal *term, int width, int height) { termpaint_surface *ret = termpaint_terminal_new_surface_or_nullptr(term, width, height); if (!ret) { termpaintp_oom(term); } return ret; } termpaint_surface *termpaint_surface_new_surface(termpaint_surface *surface, int width, int height) { return termpaint_terminal_new_surface(surface->terminal, width, height); } termpaint_surface *termpaint_surface_new_surface_or_nullptr(termpaint_surface *surface, int width, int height) { return termpaint_terminal_new_surface_or_nullptr(surface->terminal, width, height); } void termpaint_surface_free(termpaint_surface *surface) { if (!surface) { return; } // guard against freeing the primary surface if (surface->primary) { int_debuglog_puts(surface->terminal, "surface_free: Attempt to free primary surface. This is a bug in your application"); return; } termpaintp_surface_destroy(surface); free(surface); } static void termpaintp_copy_colors_and_attibutes(termpaint_surface *src_surface, cell *src_cell, termpaint_surface *dst_surface, cell *dst_cell) { dst_cell->fg_color = src_cell->fg_color; dst_cell->bg_color = src_cell->bg_color; dst_cell->deco_color = src_cell->deco_color; dst_cell->flags = src_cell->flags; if (src_cell->attr_patch_idx) { termpaintp_patch* patch = &src_surface->patches[src_cell->attr_patch_idx - 1]; dst_cell->attr_patch_idx = termpaintp_surface_ensure_patch_idx(dst_surface, patch->optimize, patch->setup, patch->cleanup); } } void termpaint_surface_tint(termpaint_surface *surface, void (*recolor)(void *user_data, unsigned *fg, unsigned *bg, unsigned *deco), void *user_data) { for (int y = 0; y < surface->height; y++) { for (int x = 0; x < surface->width; x++) { cell *cell = termpaintp_getcell(surface, x, y); // Don't give out pointers to internal cell structure contents. unsigned fg = cell->fg_color; unsigned bg = cell->bg_color; unsigned deco = cell->deco_color; recolor(user_data, &fg, &bg, &deco); int expansion = cell->cluster_expansion; // update cluster at once, different colors in one cluster are not allowed for (int i = 0; i <= expansion; i++) { cell = termpaintp_getcell(surface, x + i, y); cell->fg_color = fg; cell->bg_color = bg; cell->deco_color = deco; } x += expansion; } } } static void termpaintp_surface_copy_rect_same_surface(termpaint_surface *src_surface, int x, int y, int width, int height, int dst_x, int dst_y, int tile_left, int tile_right); void termpaint_surface_copy_rect(termpaint_surface *src_surface, int x, int y, int width, int height, termpaint_surface *dst_surface, int dst_x, int dst_y, int tile_left, int tile_right) { if (x < 0) { width += x; dst_x -= x; x = 0; // also switch left mode to erase tile_left = TERMPAINT_COPY_NO_TILE; } if (y < 0) { dst_y -= y; height += y; y = 0; } if (x >= src_surface->width) { return; } if (y >= src_surface->height) { return; } if (x + width > src_surface->width) { width = src_surface->width - x; // also switch right mode to erase tile_right = TERMPAINT_COPY_NO_TILE; } if (y + height > src_surface->height) { height = src_surface->height - y; } if (dst_x < 0) { x -= dst_x; width += dst_x; dst_x = 0; // also switch left mode to erase tile_left = TERMPAINT_COPY_NO_TILE; } if (dst_y < 0) { y -= dst_y; height += dst_y; dst_y = 0; } if (dst_x + width > dst_surface->width) { width = dst_surface->width - dst_x; // also switch right mode to erase tile_right = TERMPAINT_COPY_NO_TILE; } if (tile_right >= TERMPAINT_COPY_TILE_PUT && dst_x + width + 1 >= dst_surface->width) { tile_right = TERMPAINT_COPY_NO_TILE; } if (dst_y + height >= dst_surface->height) { height = dst_surface->height - dst_y; } if (width == 0) { return; } if (src_surface == dst_surface) { termpaintp_surface_copy_rect_same_surface(src_surface, x, y, width, height, dst_x, dst_y, tile_left, tile_right); return; } for (int yOffset = 0; yOffset < height; yOffset++) { bool in_complete_cluster = false; int xOffset = 0; { cell *src_cell = termpaintp_getcell(src_surface, x, y + yOffset); if (src_cell->text_len == 0 && src_cell->text_overflow == WIDE_RIGHT_PADDING) { if (tile_left == TERMPAINT_COPY_TILE_PRESERVE) { for (int i = 0; i < width; i++) { cell *src_scan = termpaintp_getcell(src_surface, x + i, y + yOffset); cell *dst_scan = termpaintp_getcell(dst_surface, dst_x + i, dst_y + yOffset); if (!(src_scan->text_len == 0 && src_scan->text_overflow == WIDE_RIGHT_PADDING) && !(dst_scan->text_len == 0 && dst_scan->text_overflow == WIDE_RIGHT_PADDING)) { // end of cluster in both surfaces. // skip over same length cluster in src and dst. xOffset = i; break; } if (!(dst_scan->text_len == 0 && dst_scan->text_overflow == WIDE_RIGHT_PADDING)) { // cluster in dst is shorter than in src or shifted. This can not be valid tiling. break; } if (i == width - 1) { // whole line in src is one cluster, dst also has a cluster there xOffset = width; } } } else if (tile_left >= TERMPAINT_COPY_TILE_PUT && x > 0 && dst_x > 0) { cell *src_scan = termpaintp_getcell(src_surface, x - 1, y + yOffset); cell *dst_scan = termpaintp_getcell(dst_surface, dst_x - 1, dst_y + yOffset); if ((src_scan->text_len != 0 || src_scan->text_overflow != WIDE_RIGHT_PADDING) && src_scan->cluster_expansion > 0 && src_scan->cluster_expansion <= width) { in_complete_cluster = true; termpaintp_surface_vanish_char(dst_surface, dst_x - 1, dst_y + yOffset, src_scan->cluster_expansion + 1); termpaintp_copy_colors_and_attibutes(src_surface, src_scan, dst_surface, dst_scan); dst_scan->cluster_expansion = src_scan->cluster_expansion; if (src_scan->text_len > 0) { memcpy(dst_scan->text, src_scan->text, src_scan->text_len); dst_scan->text_len = src_scan->text_len; } else if (src_scan->text_len == 0) { termpaintp_set_overflow_text(dst_surface, dst_scan, src_scan->text_overflow->text); } } } } } int extra_width = 0; for (; xOffset < width + extra_width; xOffset++) { cell *src_cell = termpaintp_getcell(src_surface, x + xOffset, y + yOffset); cell *dst_cell = termpaintp_getcell(dst_surface, dst_x + xOffset, dst_y + yOffset); if (src_cell->text_len == 0 && src_cell->text_overflow == WIDE_RIGHT_PADDING) { termpaintp_surface_vanish_char(dst_surface, dst_x + xOffset, dst_y + yOffset, 1); termpaintp_copy_colors_and_attibutes(src_surface, src_cell, dst_surface, dst_cell); if (in_complete_cluster) { dst_cell->text_len = 0; dst_cell->text_overflow = WIDE_RIGHT_PADDING; } else { dst_cell->text_len = 1; dst_cell->text[0] = ' '; } } else { if (tile_right == TERMPAINT_COPY_TILE_PRESERVE) { if (src_cell->cluster_expansion && xOffset + src_cell->cluster_expansion >= width) { if (src_cell->cluster_expansion == dst_cell->cluster_expansion) { // same cluster length in both, preserve cluster in dst break; } } } const bool crosses_boundary = xOffset + src_cell->cluster_expansion >= width; const bool write_over_boundary = tile_right >= TERMPAINT_COPY_TILE_PUT && src_cell->cluster_expansion == 1; termpaintp_surface_vanish_char(dst_surface, dst_x + xOffset, dst_y + yOffset, (crosses_boundary && !write_over_boundary) ? 1 : src_cell->cluster_expansion + 1); termpaintp_copy_colors_and_attibutes(src_surface, src_cell, dst_surface, dst_cell); bool vanish = false; if (src_cell->cluster_expansion) { if (crosses_boundary) { if (write_over_boundary) { extra_width = 1; dst_cell->cluster_expansion = src_cell->cluster_expansion; in_complete_cluster = true; } else { vanish = true; in_complete_cluster = false; } } else { dst_cell->cluster_expansion = src_cell->cluster_expansion; in_complete_cluster = true; } } else { in_complete_cluster = false; } if (!vanish) { if (src_cell->text_len > 0) { memcpy(dst_cell->text, src_cell->text, src_cell->text_len); dst_cell->text_len = src_cell->text_len; } else if (src_cell->text_len == 0) { if (src_cell->text_overflow != nullptr) { termpaintp_set_overflow_text(dst_surface, dst_cell, src_cell->text_overflow->text); } else { dst_cell->text_len = 0; dst_cell->text_overflow = nullptr; } } } else { dst_cell->text_len = 1; dst_cell->text[0] = ' '; } } } } } static void termpaintp_surface_copy_rect_same_surface(termpaint_surface *dst_surface, int x, int y, int width, int height, int dst_x, int dst_y, int tile_left, int tile_right) { // precondition: All rectangles are already fully within the surface. termpaint_surface *src_surface = termpaint_surface_new_surface(dst_surface, width + (x != 0 ? 1 : 0) + ((x + width != dst_surface->width) ? 1 : 0), height + (y != 0 ? 1 : 0) + ((y + height != dst_surface->height) ? 1 : 0)); termpaint_surface_copy_rect(dst_surface, x - (x != 0 ? 1 : 0), y - (y != 0 ? 1 : 0), src_surface->width, src_surface->height, src_surface, 0, 0, TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_NO_TILE); termpaint_surface_copy_rect(src_surface, (x != 0 ? 1 : 0), (y != 0 ? 1 : 0), width, height, dst_surface, dst_x, dst_y, tile_left, tile_right); termpaint_surface_free(src_surface); } termpaint_surface *termpaint_surface_duplicate(termpaint_surface *surface) { termpaint_surface *ret = termpaint_surface_new_surface(surface, surface->width, surface->height); termpaint_surface_copy_rect(surface, 0, 0, surface->width, surface->height, ret, 0, 0, TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_NO_TILE); return ret; } unsigned termpaint_surface_peek_fg_color(const termpaint_surface *surface, int x, int y) { cell *cell = termpaintp_getcell_or_null(surface, x, y); if (!cell) { return 0; } return cell->fg_color; } unsigned termpaint_surface_peek_bg_color(const termpaint_surface *surface, int x, int y) { cell *cell = termpaintp_getcell_or_null(surface, x, y); if (!cell) { return 0; } return cell->bg_color; } unsigned termpaint_surface_peek_deco_color(const termpaint_surface *surface, int x, int y) { cell *cell = termpaintp_getcell_or_null(surface, x, y); if (!cell) { return 0; } return cell->deco_color; } int termpaint_surface_peek_style(const termpaint_surface *surface, int x, int y) { cell *cell = termpaintp_getcell_or_null(surface, x, y); if (!cell) { return 0; } unsigned flags = cell->flags; int style = flags & TERMPAINT_STYLE_PASSTHROUGH; if ((flags & CELL_ATTR_UNDERLINE_MASK) == CELL_ATTR_UNDERLINE_SINGLE) { style |= TERMPAINT_STYLE_UNDERLINE; } else if ((flags & CELL_ATTR_UNDERLINE_MASK) == CELL_ATTR_UNDERLINE_DOUBLE) { style |= TERMPAINT_STYLE_UNDERLINE_DBL; } else if ((flags & CELL_ATTR_UNDERLINE_MASK) == CELL_ATTR_UNDERLINE_CURLY) { style |= TERMPAINT_STYLE_UNDERLINE_CURLY; } return style; } void termpaint_surface_peek_patch(const termpaint_surface *surface, int x, int y, const char **setup, const char **cleanup, bool *optimize) { cell *cell = termpaintp_getcell_or_null(surface, x, y); if (!cell || !cell->attr_patch_idx) { *setup = nullptr; *cleanup = nullptr; *optimize = true; return; } termpaintp_patch* patch = &surface->patches[cell->attr_patch_idx - 1]; *setup = (const char *)patch->setup; *cleanup = (const char *)patch->cleanup; *optimize = patch->optimize; } const char *termpaint_surface_peek_text(const termpaint_surface *surface, int x, int y, int *len, int *left, int *right) { cell *cell = termpaintp_getcell_or_null(surface, x, y); if (!cell) { if (left) { *left = x; } if (right) { *right = x; } *len = 1; return TERMPAINT_ERASED; } while (x > 0) { if (cell->text_len != 0 || cell->text_overflow != WIDE_RIGHT_PADDING) { break; } --x; cell = termpaintp_getcell(surface, x, y); } if (left) { *left = x; } const char *text; if (cell->text_len > 0) { text = (const char*)cell->text; *len = cell->text_len; } else if (cell->text_overflow == nullptr) { text = TERMPAINT_ERASED; *len = 1; } else { text = (const char*)cell->text_overflow->text; *len = strlen(text); } if (right) { *right = x + cell->cluster_expansion; } return text; } bool termpaint_surface_peek_softwrap_marker(const termpaint_surface *surface, int x, int y) { cell *cell = termpaintp_getcell_or_null(surface, x, y); if (!cell) { return false; } return !!(cell->flags & CELL_SOFTWRAP_MARKER); } bool termpaint_surface_same_contents(const termpaint_surface *surface1, const termpaint_surface *surface2) { if (surface1 == surface2) { return true; } if (surface1->width != surface2->width || surface1->height != surface2->height) { return false; } for (int y = 0; y < surface1->height; y++) { for (int x = 0; x < surface1->width; x++) { if (termpaint_surface_peek_fg_color(surface1, x, y) != termpaint_surface_peek_fg_color(surface2, x, y)) { return false; } if (termpaint_surface_peek_bg_color(surface1, x, y) != termpaint_surface_peek_bg_color(surface2, x, y)) { return false; } if (termpaint_surface_peek_deco_color(surface1, x, y) != termpaint_surface_peek_deco_color(surface2, x, y)) { return false; } if (termpaint_surface_peek_style(surface1, x, y) != termpaint_surface_peek_style(surface2, x, y)) { return false; } if (termpaint_surface_peek_softwrap_marker(surface1, x, y) != termpaint_surface_peek_softwrap_marker(surface2, x, y)) { return false; } { const char *setup1, *setup2; const char *cleanup1, *cleanup2; bool optimize1, optimize2; termpaint_surface_peek_patch(surface1, x, y, &setup1, &cleanup1, &optimize1); termpaint_surface_peek_patch(surface2, x, y, &setup2, &cleanup2, &optimize2); if ((setup1 == nullptr || setup2 == nullptr || strcmp(setup1, setup2) != 0) && !(setup1 == nullptr && setup2 == nullptr)) { return false; } if ((cleanup1 == nullptr || cleanup2 == nullptr || strcmp(cleanup1, cleanup2) != 0) && !(setup1 == nullptr && setup2 == nullptr)) { return false; } if (optimize1 != optimize2) { return false; } } { int left1, right1, len1; int left2, right2, len2; const char *text1 = termpaint_surface_peek_text(surface1, x, y, &len1, &left1, &right1); const char *text2 = termpaint_surface_peek_text(surface2, x, y, &len2, &left2, &right2); if (left1 != left2 || right1 != right2 || len1 != len2) { return false; } if (memcmp(text1, text2, len1) != 0) { return false; } } } } return true; } int termpaint_surface_char_width(const termpaint_surface *surface, int codepoint) { const termpaintp_width *char_width_table = surface->terminal->char_width_table; return termpaintp_char_width(char_width_table, codepoint); } static void int_puts(termpaint_integration *integration, const char *str) { integration->p->write(integration, str, strlen(str)); } static void int_uputs(termpaint_integration *integration, const unsigned char *str) { integration->p->write(integration, (const char*)str, ustrlen(str)); } static void int_write(termpaint_integration *integration, const char *str, int len) { integration->p->write(integration, str, len); } static void int_debuglog(termpaint_terminal *term, const char *str, int len) { if (term->integration_vtbl->logging_func) { term->integration_vtbl->logging_func(term->integration, str, len); } } static void int_debuglog_puts(termpaint_terminal *term, const char *str) { int_debuglog(term, str, strlen(str)); } static void int_debuglog_printf(termpaint_terminal *term, const char *fmt, ...) { va_list args; va_start(args, fmt); char buff[1024]; vsnprintf(buff, sizeof(buff), fmt, args); va_end(args); buff[sizeof(buff) - 1] = 0; int_debuglog_puts(term, buff); } static void int_put_num(termpaint_integration *integration, int num) { char buf[12]; int len = sprintf(buf, "%d", num); integration->p->write(integration, buf, len); } static void int_put_tps(termpaint_integration *integration, const termpaint_str *tps) { integration->p->write(integration, (const char*)tps->data, (int)tps->len); } static void int_awaiting_response(termpaint_integration *integration) { if (integration->p->awaiting_response) { integration->p->awaiting_response(integration); } } static void int_restore_sequence_complete(termpaint_terminal *term) { if (term->altscreen_active) { termpaintp_str_assign_n(&term->restore_seq_cached, (const char*)term->restore_seq_partial.data, term->restore_seq_partial.len); termpaintp_str_append(&term->restore_seq_cached, "\r\n\033[?1049l"); } else { termpaintp_str_assign_n(&term->restore_seq_cached, (const char*)term->restore_seq_partial.data, term->restore_seq_partial.len); } } static void int_restore_sequence_updated(termpaint_terminal *term) { termpaint_integration_private *vtbl = term->integration_vtbl; if (vtbl->restore_sequence_updated) { vtbl->restore_sequence_updated(term->integration, (char*)term->restore_seq_cached.data, term->restore_seq_cached.len); } } static void int_flush(termpaint_integration *integration) { integration->p->flush(integration); } static void termpaintp_terminal_set_cursor(termpaint_terminal *term, int x, int y) { termpaint_integration *integration = term->integration; int_puts(integration, "\033["); int_put_num(integration, y+1); int_puts(integration, ";"); int_put_num(integration, x+1); int_puts(integration, "H"); } static void termpaintp_terminal_hide_cursor(termpaint_terminal *term) { termpaint_integration *integration = term->integration; int_puts(integration, "\033[?25l"); } static void termpaintp_terminal_show_cursor(termpaint_terminal *term) { termpaint_integration *integration = term->integration; int_puts(integration, "\033[?25h"); } static void termpaintp_terminal_update_cursor_style(termpaint_terminal *term) { bool nonharmful = termpaint_terminal_capable(term, TERMPAINT_CAPABILITY_MAY_TRY_CURSOR_SHAPE); if (term->cursor_style != -1 && nonharmful) { const char *resetSequence = "\033[0 q"; int cmd = term->cursor_style + (term->cursor_blink ? 0 : 1); if (term->cursor_style == TERMPAINT_CURSOR_STYLE_BAR && !termpaint_terminal_capable(term, TERMPAINT_CAPABILITY_MAY_TRY_CURSOR_SHAPE_BAR)) { // e.g. xterm < 282 does not support BAR style. cmd = TERMPAINT_CURSOR_STYLE_BLOCK + (term->cursor_blink ? 0 : 1); } if (cmd != term->cursor_prev_data) { termpaint_integration *integration = term->integration; if (termpaint_terminal_capable(term, TERMPAINT_CAPABILITY_CURSOR_SHAPE_OSC50)) { // e.g. konsole. (konsole starting at version 18.07.70 could do the CSI space q one too, but // we don't have the konsole version.) if (term->cursor_style == TERMPAINT_CURSOR_STYLE_BAR) { int_puts(integration, "\x1b]50;CursorShape=1;BlinkingCursorEnabled="); } else if (term->cursor_style == TERMPAINT_CURSOR_STYLE_UNDERLINE) { int_puts(integration, "\x1b]50;CursorShape=2;BlinkingCursorEnabled="); } else { int_puts(integration, "\x1b]50;CursorShape=0;BlinkingCursorEnabled="); } if (term->cursor_blink) { int_puts(integration, "1\a"); } else { int_puts(integration, "0\a"); } resetSequence = "\x1b]50;CursorShape=0;BlinkingCursorEnabled=0\a"; } else { int_puts(integration, "\033["); int_put_num(integration, cmd); int_puts(integration, " q"); } } if (term->cursor_prev_data == -1) { // add style reset. We don't know the original style, so just reset to terminal default. termpaintp_prepend_str(&term->restore_seq_partial, (const uchar*)resetSequence); int_restore_sequence_complete(term); int_restore_sequence_updated(term); } term->cursor_prev_data = cmd; } } static void termpaintp_input_event_callback(void *user_data, termpaint_event *event); static bool termpaintp_input_raw_filter_callback(void *user_data, const char *data, unsigned length, _Bool overflow); static void termpaint_color_entry_destroy(termpaint_color_entry *entry) { termpaintp_str_destroy(&entry->restore); termpaintp_str_destroy(&entry->requested); } static void termpaint_unpause_snippet_destroy(termpaint_unpause_snippet *entry) { termpaintp_str_destroy(&entry->sequences); } static void termpaintp_terminal_reset_capabilites(termpaint_terminal *terminal); termpaint_terminal *termpaint_terminal_new_or_nullptr(termpaint_integration *integration) { termpaint_terminal *ret = calloc(1, sizeof(termpaint_terminal)); if (!ret) { return nullptr; } termpaintp_surface_init(&ret->primary, ret); ret->primary.primary = true; // start collapsed termpaintp_collapse(&ret->primary); ret->integration = integration; ret->integration_vtbl = integration->p; ret->inline_current_terminal_cursor_line = -1; ret->cursor_visible = true; ret->cursor_x = -1; ret->cursor_y = -1; ret->cursor_style = -1; ret->cursor_blink = false; ret->cursor_prev_data = -1; ret->data_pending_after_input_received = false; ret->ad_state = AD_NONE; ret->initial_cursor_x = -1; ret->initial_cursor_y = -1; ret->char_width_table = &termpaintp_char_width_default; termpaintp_terminal_reset_capabilites(ret); ret->terminal_type = TT_UNKNOWN; ret->terminal_type_confidence = 0; ret->max_csi_parameters = 15; ret->input = termpaint_input_new(); if (!ret->input) { free(ret); return nullptr; } termpaint_input_set_event_cb(ret->input, termpaintp_input_event_callback, ret); termpaint_input_set_raw_filter_cb(ret->input, termpaintp_input_raw_filter_callback, ret); ret->colors.item_size = sizeof(termpaint_color_entry); ret->colors.destroy_cb = (void (*)(termpaint_hash_item*))termpaint_color_entry_destroy; ret->unpause_snippets.item_size = sizeof (termpaint_unpause_snippet); ret->unpause_snippets.destroy_cb = (void (*)(termpaint_hash_item*))termpaint_unpause_snippet_destroy; if (!termpaintp_str_preallocate(&ret->unpause_basic_setup, 64)) { termpaint_input_free(ret->input); free(ret); return nullptr; } if (!termpaintp_str_preallocate(&ret->restore_seq_cached, 256)) { termpaintp_str_destroy(&ret->unpause_basic_setup); termpaint_input_free(ret->input); free(ret); return nullptr; } if (!termpaintp_str_preallocate(&ret->restore_seq_partial, 256)) { termpaintp_str_destroy(&ret->restore_seq_cached); termpaintp_str_destroy(&ret->unpause_basic_setup); termpaint_input_free(ret->input); free(ret); return nullptr; } if (!termpaintp_str_preallocate(&ret->terminal_self_reported_name_version, 64)) { termpaintp_str_destroy(&ret->restore_seq_partial); termpaintp_str_destroy(&ret->restore_seq_cached); termpaintp_str_destroy(&ret->unpause_basic_setup); termpaint_input_free(ret->input); free(ret); return nullptr; } if (!termpaintp_str_preallocate(&ret->auto_detect_sec_device_attributes, 64)) { termpaintp_str_destroy(&ret->terminal_self_reported_name_version); termpaintp_str_destroy(&ret->restore_seq_partial); termpaintp_str_destroy(&ret->restore_seq_cached); termpaintp_str_destroy(&ret->unpause_basic_setup); termpaint_input_free(ret->input); free(ret); return nullptr; } termpaintp_prepend_str(&ret->restore_seq_partial, (const uchar*)"\033[?25h\033[m"); int_restore_sequence_complete(ret); int_restore_sequence_updated(ret); return ret; } termpaint_terminal *termpaint_terminal_new(termpaint_integration *integration) { termpaint_terminal *ret = termpaint_terminal_new_or_nullptr(integration); if (!ret) { termpaintp_oom_int(integration); } return ret; } void termpaint_terminal_set_log_mask(termpaint_terminal *term, unsigned mask) { term->log_mask = mask; } void termpaint_terminal_glitch_on_out_of_memory(termpaint_terminal *term) { term->glitch_on_oom = true; } void termpaint_terminal_free(termpaint_terminal *term) { if (!term) { return; } termpaintp_str_destroy(&term->auto_detect_sec_device_attributes); termpaintp_str_destroy(&term->terminal_self_reported_name_version); termpaintp_surface_destroy(&term->primary); termpaintp_str_destroy(&term->restore_seq_partial); termpaintp_str_destroy(&term->restore_seq_cached); termpaint_input_free(term->input); term->input = nullptr; term->integration_vtbl->free(term->integration); term->integration = nullptr; term->integration_vtbl = nullptr; termpaintp_str_destroy(&term->unpause_basic_setup); termpaintp_hash_destroy(&term->colors); termpaintp_hash_destroy(&term->unpause_snippets); free(term); } static void termpaintp_erase_inline(termpaint_terminal *term) { termpaint_integration *integration = term->integration; int to_move = term->last_inline_height - 1 - term->inline_current_terminal_cursor_line; int_puts(integration, "\r"); if (to_move > 0) { int_puts(integration, "\033["); int_put_num(integration, to_move); int_puts(integration, "B"); } int_write(integration, "\033[K", 3); for (int i = 1; i < term->last_inline_height; i++) { int_write(integration, "\033[A", 3); int_write(integration, "\033[K", 3); } term->inline_current_terminal_cursor_line = 0; term->last_inline_height = 0; } static void termpaintp_terminal_flush_with_surface(termpaint_terminal *term, bool full_repaint, termpaint_surface *surface); void termpaint_terminal_free_with_restore(termpaint_terminal *term) { termpaint_terminal_free_with_restore_and_persistent(term, nullptr); } void termpaint_terminal_free_with_restore_and_persistent(termpaint_terminal *term, termpaint_surface *surface) { if (!term) { return; } termpaint_integration *integration = term->integration; if (term->setup_state != SETUP_STATE_INLINE) { if (term->primary.height && term->primary.height) { termpaintp_terminal_set_cursor(term, 0, term->primary.height - 1); } if (surface) { if (term->altscreen_active) { int_puts(integration, "\033[?1049l"); term->altscreen_active = false; int_restore_sequence_complete(term); } term->setup_state = SETUP_STATE_INLINE; term->inline_current_terminal_cursor_line = 0; term->last_inline_height = 0; term->cursor_x = term->cursor_y = -1; termpaintp_terminal_flush_with_surface(term, true, surface); int_puts(integration, "\r\n"); // Output is persistent term->inline_current_terminal_cursor_line = 0; term->last_inline_height = 0; } } else { termpaintp_erase_inline(term); if (surface) { term->cursor_x = term->cursor_y = -1; termpaintp_terminal_flush_with_surface(term, true, surface); int_puts(integration, "\r\n"); // Output is persistent term->inline_current_terminal_cursor_line = 0; term->last_inline_height = 0; } } if (term->restore_seq_cached.len) { int_write(integration, (const char*)term->restore_seq_cached.data, term->restore_seq_cached.len); } int_flush(integration); termpaint_terminal_free(term); } static void termpaintp_terminal_reset_capabilites(termpaint_terminal *terminal) { for (int i = 0; i < NUM_CAPABILITIES; i++) { terminal->capabilities[i] = false; } // cursor bar is on by default. Some terminals don't understand any sequence, for these this does not matter. // But some terminals recognize block and underline, but ignore bar. In this case it's better to remap bar to block. termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_MAY_TRY_CURSOR_SHAPE_BAR); // Start with assuming that terminals have in principle solid support for unicode fonts with common // linedrawing / semigraphics codepoints. But leave this to opt out for implementations with very // limited character repertoire. // One example is the kernel internal terminal in linux that is limited to 256 or 512 different glyphs total. termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_EXTENDED_CHARSET); // Start with assuming a terminal might have true-color support. If a terminal (+ version) is known // not to support true-color this capability should be removed during detection. If a terminal (+ version) // is known to support true-color additionally set TERMPAINT_CAPABILITY_TRUECOLOR_SUPPORTED during // detection termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_TRUECOLOR_MAYBE_SUPPORTED); // Most terminals support background color erase (bce) and allow multiple colors in cleared parts // of lines. termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_CLEARED_COLORING); // VTE specific quirk termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_CLEARED_COLORING_DEFCOLOR); // Most terminals support support 7-bit ST (ESC backslash) for terminating OSC/DCS sequences, // as that's what all traditional standards say. termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_7BIT_ST); } inline bool termpaint_terminal_capable(const termpaint_terminal *terminal, int capability) { if (capability < 0 || capability >= NUM_CAPABILITIES) { return false; } return terminal->capabilities[capability]; } static char* termpaintp_terminal_correct_string_terminator(const termpaint_terminal *terminal) { if (termpaint_terminal_capable(terminal, TERMPAINT_CAPABILITY_7BIT_ST)) { return "\033\\"; } else { return "\a"; } } static void termpaintp_update_cache_from_capabilities(termpaint_terminal *terminal) { terminal->cache_should_use_truecolor = termpaint_terminal_capable(terminal, TERMPAINT_CAPABILITY_TRUECOLOR_MAYBE_SUPPORTED) || termpaint_terminal_capable(terminal, TERMPAINT_CAPABILITY_TRUECOLOR_SUPPORTED); } void termpaint_terminal_promise_capability(termpaint_terminal *terminal, int capability) { if (capability < 0 || capability >= NUM_CAPABILITIES) { return; } terminal->capabilities[capability] = true; termpaintp_update_cache_from_capabilities(terminal); } void termpaint_terminal_disable_capability(termpaint_terminal *terminal, int capability) { if (capability < 0 || capability >= NUM_CAPABILITIES) { return; } terminal->capabilities[capability] = false; termpaintp_update_cache_from_capabilities(terminal); } bool termpaint_terminal_should_use_truecolor(termpaint_terminal *terminal) { return terminal->cache_should_use_truecolor; } termpaint_surface *termpaint_terminal_get_surface(termpaint_terminal *term) { return &term->primary; } static int termpaintp_quantize_color_grid6(int val) { // index of nearest int to [0, 95, 135, 175, 215, 255] // cut points: 47, 115, 155, 195, 235 if (val <= 47) { return 0; } if (val < 115) { return 1; } return 2 + (val - 115) / 40; } static int termpaintp_quantize_color_grid4(int val) { // index of nearest int to [0, 139, 205, 255] // cut points: 69, 172, 230 if (val <= 172) { if (val <= 69) { return 0; } else { return 1; } } else { if (val < 230) { return 2; } else { return 3; } } } static const int termpaintp_grid6_values[] = {0, 95, 135, 175, 215, 255}; static const int termpaintp_grid4_values[] = {0, 139, 205, 255}; static const int termpaintp_ramp8_values[] = {46, 92, 115, 139, 162, 185, 208, 231}; static uint32_t termpaintp_quantize_color(termpaint_terminal *term, uint32_t color) { if (!term->cache_should_use_truecolor) { if ((color & 0xff000000) == TERMPAINT_RGB_COLOR_OFFSET) { const int r = (color >> 16) & 0xff; const int g = (color >> 8) & 0xff; const int b = (color) & 0xff; const int grey = (r+g+b) / 3; if (termpaint_terminal_capable(term, TERMPAINT_CAPABILITY_88_COLOR)) { // find nearest color on grid ((r,g,b) for r,b,g in [0, 95, 135, 175, 215, 255]) const int red_index = termpaintp_quantize_color_grid4(r); const int green_index = termpaintp_quantize_color_grid4(g); const int blue_index = termpaintp_quantize_color_grid4(b); const int red_quantized = termpaintp_grid4_values[red_index]; const int green_quantized = termpaintp_grid4_values[green_index]; const int blue_quantized = termpaintp_grid4_values[blue_index]; color = TERMPAINT_INDEXED_COLOR + 16 + red_index * 16 + green_index * 4 + blue_index; #define SQ(x) ((x) * (x)) int best_metric = SQ(red_quantized - r) + SQ(green_quantized - g) + SQ(blue_quantized - b); for (int grey_index = 0; grey_index < 8; grey_index++) { const int grey_quantized = termpaintp_ramp8_values[grey_index]; const int cur_metric = SQ(grey_quantized - r) + SQ(grey_quantized - g) + SQ(grey_quantized - b); if (cur_metric < best_metric) { color = TERMPAINT_INDEXED_COLOR + 80 + grey_index; best_metric = cur_metric; } } #undef SQ } else { // find nearest grey value in [8, 18, ..., 228, 238] int grey_index = (((grey - 8)+5) / 10); // -3 / 10 rounds to zero, so grey == 0 still gives index 0 if (grey_index >= 24) { grey_index = 23; } const int grey_quantized = 8 + grey_index * 10; // find nearest color on grid ((r,g,b) for r,b,g in [0, 95, 135, 175, 215, 255]) const int red_index = termpaintp_quantize_color_grid6(r); const int green_index = termpaintp_quantize_color_grid6(g); const int blue_index = termpaintp_quantize_color_grid6(b); const int red_quantized = termpaintp_grid6_values[red_index]; const int green_quantized = termpaintp_grid6_values[green_index]; const int blue_quantized = termpaintp_grid6_values[blue_index]; #define SQ(x) ((x) * (x)) if ((SQ(grey_quantized - r) + SQ(grey_quantized - g) + SQ(grey_quantized - b)) < (SQ(red_quantized - r) + SQ(green_quantized - g) + SQ(blue_quantized - b))) { #undef SQ color = TERMPAINT_INDEXED_COLOR + 232 + grey_index; } else { color = TERMPAINT_INDEXED_COLOR + 16 + red_index * 36 + green_index * 6 + blue_index; } } } } return color; } typedef struct { int index; int max; } termpaintp_sgr_params; static inline void write_color_sgr_values(termpaint_integration *integration, termpaintp_sgr_params *params, uint32_t color, char *direct, char *indexed, char *sep, unsigned named, unsigned bright_named) { if ((color & 0xff000000) == TERMPAINT_RGB_COLOR_OFFSET) { if (params->index + 5 >= params->max) { int_puts(integration, "m\033["); params->index = 0; int_puts(integration, direct + 1); // skip first ";" } else { int_puts(integration, direct); } int_put_num(integration, (color >> 16) & 0xff); int_puts(integration, sep); int_put_num(integration, (color >> 8) & 0xff); int_puts(integration, sep); int_put_num(integration, (color) & 0xff); params->index += 5; } else if (TERMPAINT_INDEXED_COLOR <= color && TERMPAINT_INDEXED_COLOR + 255 >= color) { if (params->index + 3 >= params->max) { int_puts(integration, "m\033["); params->index = 0; int_puts(integration, indexed + 1); // skip first ";" } else { int_puts(integration, indexed); } int_put_num(integration, (color) & 0xff); params->index += 3; } else { if (named) { if (TERMPAINT_NAMED_COLOR <= color && TERMPAINT_NAMED_COLOR + 7 >= color) { if (params->index + 1 >= params->max) { int_puts(integration, "m\033["); params->index = 0; } else { int_puts(integration, ";"); } int_put_num(integration, named + (color - TERMPAINT_NAMED_COLOR)); params->index += 1; } else if (TERMPAINT_NAMED_COLOR + 8 <= color && TERMPAINT_NAMED_COLOR + 15 >= color) { if (params->index + 1 >= params->max) { int_puts(integration, "m\033["); params->index = 0; } else { int_puts(integration, ";"); } int_put_num(integration, bright_named + (color - (TERMPAINT_NAMED_COLOR + 8))); params->index += 1; } } else { if (TERMPAINT_NAMED_COLOR <= color && TERMPAINT_NAMED_COLOR + 15 >= color) { if (params->index + 3 >= params->max) { int_puts(integration, "m\033["); params->index = 0; int_puts(integration, indexed + 1); // skip first ";" } else { int_puts(integration, indexed); } int_put_num(integration, (color - TERMPAINT_NAMED_COLOR)); params->index += 3; } } } } static void termpaintp_terminal_flush_with_surface(termpaint_terminal *term, bool full_repaint, termpaint_surface *surface) { termpaint_integration *integration = term->integration; if (surface == &term->primary) { full_repaint |= term->force_full_repaint; term->force_full_repaint = false; } termpaintp_terminal_hide_cursor(term); if (term->setup_state == SETUP_STATE_INLINE) { int_puts(integration, "\r"); if (term->inline_current_terminal_cursor_line != 0) { int_puts(integration, "\033["); int_put_num(integration, term->inline_current_terminal_cursor_line); int_puts(integration, "A"); } if (surface->height < term->last_inline_height) { int_puts(integration, "\033["); int_put_num(integration, term->last_inline_height - 1); int_puts(integration, "B"); int_write(integration, "\033[K", 3); for (int i = 1; i < term->last_inline_height - surface->height; i++) { int_puts(integration, "\033[A\033[K"); } int_puts(integration, "\033["); int_put_num(integration, surface->height); int_puts(integration, "A"); } term->last_inline_height = surface->height; } else { int_puts(integration, "\033[H"); } char speculation_buffer[30]; int speculation_buffer_state = 0; // 0 = cursor position matches current cell, -1 = force move, > 0 bytes to print instead of move int pending_row_move = 0; int pending_colum_move = 0; int pending_colum_move_digits = 1; int pending_colum_move_digits_step = 10; const bool cleared_defcolor = termpaint_terminal_capable(term, TERMPAINT_CAPABILITY_CLEARED_COLORING_DEFCOLOR); enum { sw_no, sw_single, sw_double } softwrap_prev = sw_no, softwrap = sw_no; for (int y = 0; y < surface->height; y++) { speculation_buffer_state = 0; pending_colum_move = 0; pending_colum_move_digits = 1; pending_colum_move_digits_step = 10; uint32_t current_fg = -1; uint32_t current_bg = -1; uint32_t current_deco = -1; uint32_t current_flags = -1; uint32_t current_patch_idx = 0; // patch index is special because it could do anything. bool cleared = false; softwrap = sw_no; if (y+1 < surface->height && surface->width) { cell* first_next_line = termpaintp_getcell(surface, 0, y + 1); if (first_next_line->flags & CELL_SOFTWRAP_MARKER && (first_next_line->text_len || first_next_line->text_overflow != nullptr)) { cell* last_this_line = termpaintp_getcell(surface, surface->width - 1, y); if (last_this_line->flags & CELL_SOFTWRAP_MARKER && (last_this_line->text_len || last_this_line->text_overflow != nullptr)) { softwrap = sw_single; } else if (last_this_line->text_len == 0 && last_this_line->text_overflow == nullptr && surface->width >= 2) { last_this_line = termpaintp_getcell(surface, surface->width - 2, y); if (last_this_line->flags & CELL_SOFTWRAP_MARKER && (last_this_line->text_len || last_this_line->text_overflow != nullptr) && first_next_line->cluster_expansion == 1) { softwrap = sw_double; } } } } int first_noncopy_space = surface->width; if (termpaint_terminal_capable(term, TERMPAINT_CAPABILITY_CLEARED_COLORING)) { if (softwrap == sw_no) { for (int x = surface->width - 1; x >= 0; x--) { cell* c = termpaintp_getcell(surface, x, y); if ((c->text_len == 0 && c->text_overflow == nullptr) && (c->flags & CELL_ATTR_INVERSE) == 0 && (cleared_defcolor || c->bg_color != TERMPAINT_DEFAULT_COLOR)) { first_noncopy_space = x; } else { break; } } } } for (int x = 0; x < surface->width; x++) { cell* c = termpaintp_getcell(surface, x, y); cell* old_c = surface->cells_last_flush ? &surface->cells_last_flush[y*surface->width+x] : c; int code_units; bool text_changed; const unsigned char* text; if (c->text_len) { code_units = c->text_len; text = c->text; text_changed = old_c->text_len != c->text_len || memcmp(text, old_c->text, code_units) != 0; } else { if (c->text_overflow == nullptr) { code_units = 1; text = (const uchar*)" "; text_changed = old_c->text_len || c->text_overflow != old_c->text_overflow; } else { // TODO should we avoid crash here when cluster skipping failed? code_units = strlen((char*)c->text_overflow->text); text = c->text_overflow->text; text_changed = old_c->text_len || c->text_overflow != old_c->text_overflow; } } uint32_t effective_fg_color = termpaintp_quantize_color(term, c->fg_color); uint32_t effective_bg_color = termpaintp_quantize_color(term, c->bg_color); bool needs_paint = full_repaint || effective_bg_color != old_c->bg_color || effective_fg_color != old_c->fg_color || c->flags != old_c->flags || c->attr_patch_idx != old_c->attr_patch_idx || text_changed; uint32_t effective_deco_color; if (c->flags & CELL_ATTR_DECO_MASK) { effective_deco_color = c->deco_color; needs_paint |= effective_deco_color != old_c->deco_color; } else { effective_deco_color = TERMPAINT_DEFAULT_COLOR; } bool needs_attribute_change = effective_bg_color != current_bg || effective_fg_color != current_fg || effective_deco_color != current_deco || (c->flags & CELL_ATTR_MASK) != current_flags || c->attr_patch_idx != current_patch_idx; if (first_noncopy_space < x) { needs_paint = needs_attribute_change || (needs_paint && !cleared); } if (softwrap == sw_single && x == surface->width - 1) { needs_paint = true; if (term->did_terminal_disable_wrap) { // terminals like urxvt, screen and libvterm need this before the cursor goes // into pending wrap state. int_puts(integration, "\033[?7h"); } } if (softwrap == sw_double && x == surface->width - 2) { needs_paint = true; x += 1; // skip last cell if (term->did_terminal_disable_wrap) { // terminals like urxvt, screen and libvterm need this before the cursor goes // into pending wrap state. int_puts(integration, "\033[?7h"); } } if (softwrap_prev != sw_no) { needs_paint = true; } *old_c = *c; old_c->bg_color = effective_bg_color; old_c->fg_color = effective_fg_color; if (surface->cells_last_flush) { for (int i = 0; i < c->cluster_expansion; i++) { cell* wipe_c = &surface->cells_last_flush[y*surface->width+x+i+1]; wipe_c->text_len = 1; wipe_c->text[0] = '\x01'; // impossible value, filtered out earlier in output pipeline } } if (!needs_paint) { if (current_patch_idx) { int_uputs(integration, surface->patches[current_patch_idx-1].cleanup); current_patch_idx = 0; } pending_colum_move += 1 + c->cluster_expansion; if (speculation_buffer_state != -1) { if (needs_attribute_change) { // needs_attribute_change needs >= 24 chars, so repositioning will likely be cheaper (and easier to implement) speculation_buffer_state = -1; } else { if (pending_colum_move >= pending_colum_move_digits_step) { pending_colum_move_digits += 1; pending_colum_move_digits_step *= 10; } if (pending_colum_move_digits + 3 < speculation_buffer_state + code_units) { // the move sequence is shorter than moving by printing chars speculation_buffer_state = -1; } else if (speculation_buffer_state + code_units < (int)sizeof (speculation_buffer)) { memcpy(speculation_buffer + speculation_buffer_state, (char*)text, code_units); } else { // speculation buffer to small speculation_buffer_state = -1; } } } x += c->cluster_expansion; continue; } else { if (pending_row_move) { int_puts(integration, "\r"); if (pending_row_move < 4) { while (pending_row_move) { int_puts(integration, "\n"); --pending_row_move; } } else { int_puts(integration, "\033["); int_put_num(integration, pending_row_move); int_puts(integration, "B"); pending_row_move = 0; } } if (pending_colum_move) { if (speculation_buffer_state > 0) { int_write(integration, speculation_buffer, speculation_buffer_state); } else { int_puts(integration, "\033["); if (pending_colum_move != 1) { int_put_num(integration, pending_colum_move); } int_puts(integration, "C"); } speculation_buffer_state = 0; pending_colum_move = 0; pending_colum_move_digits = 1; pending_colum_move_digits_step = 10; } } if (needs_attribute_change) { int_puts(integration, "\033[0"); termpaintp_sgr_params params; params.index = 1; params.max = term->max_csi_parameters; #define PUT_PARAMETER(s) \ do { if (params.index + 1 >= params.max) { \ int_puts(integration, "m\033["); \ int_puts(integration, ((const char*)s) + 1); \ params.index = 1; \ } else { \ int_puts(integration, s); \ params.index += 1; \ } } while (false) \ /* end macro */ write_color_sgr_values(integration, ¶ms, effective_bg_color, ";48;2;", ";48;5;", ";", 40, 100); write_color_sgr_values(integration, ¶ms, effective_fg_color, ";38;2;", ";38;5;", ";", 30, 90); write_color_sgr_values(integration, ¶ms, effective_deco_color, ";58:2:", ";58:5:", ":", 0, 0); if (c->flags) { if (c->flags & CELL_ATTR_BOLD) { PUT_PARAMETER(";1"); } if (c->flags & CELL_ATTR_ITALIC) { PUT_PARAMETER(";3"); } uint32_t underline = c->flags & CELL_ATTR_UNDERLINE_MASK; if (underline == CELL_ATTR_UNDERLINE_SINGLE) { PUT_PARAMETER(";4"); } else if (underline == CELL_ATTR_UNDERLINE_DOUBLE) { PUT_PARAMETER(";21"); } else if (underline == CELL_ATTR_UNDERLINE_CURLY) { // TODO maybe filter this by terminal capability somewhere? if (params.index + 2 >= params.max) { int_puts(integration, "m\033["); int_puts(integration, "4:3"); params.index = 2; } else { int_puts(integration, ";4:3"); params.index += 2; } } if (c->flags & CELL_ATTR_BLINK) { PUT_PARAMETER(";5"); } if (c->flags & CELL_ATTR_OVERLINE) { PUT_PARAMETER(";53"); } if (c->flags & CELL_ATTR_INVERSE) { PUT_PARAMETER(";7"); } if (c->flags & CELL_ATTR_STRIKE) { PUT_PARAMETER(";9"); } } int_puts(integration, "m"); #undef PUT_PARAMETER current_bg = effective_bg_color; current_fg = effective_fg_color; current_deco = effective_deco_color; current_flags = c->flags & CELL_ATTR_MASK; if (current_patch_idx != c->attr_patch_idx) { if (current_patch_idx) { int_uputs(integration, surface->patches[current_patch_idx-1].cleanup); } if (c->attr_patch_idx) { int_uputs(integration, surface->patches[c->attr_patch_idx-1].setup); } } current_patch_idx = c->attr_patch_idx; } if (first_noncopy_space <= x) { int_write(integration, "\033[K", 3); pending_colum_move++; speculation_buffer_state = -1; cleared = true; } else { int_write(integration, (char*)text, code_units); if (softwrap_prev != sw_no) { softwrap_prev = sw_no; if (term->did_terminal_disable_wrap) { int_puts(integration, "\033[?7l"); } } if (softwrap == sw_double && x == surface->width - 1) { // clear gap cell when a double width character causes wrap int_write(integration, "\033[K", 3); } } if (current_patch_idx) { if (!surface->patches[c->attr_patch_idx-1].optimize) { int_uputs(integration, surface->patches[c->attr_patch_idx-1].cleanup); current_patch_idx = 0; } } x += c->cluster_expansion; } if (current_patch_idx) { int_uputs(integration, surface->patches[current_patch_idx-1].cleanup); current_patch_idx = 0; } if (softwrap == sw_no) { if (full_repaint) { if (y+1 < surface->height) { int_puts(integration, "\r\n"); } } else { pending_row_move += 1; } } softwrap_prev = softwrap; } if (pending_row_move > 1) { --pending_row_move; // don't move after paint rect int_puts(integration, "\r"); if (pending_row_move < 4) { while (pending_row_move) { int_puts(integration, "\n"); --pending_row_move; } } else { int_puts(integration, "\033["); int_put_num(integration, pending_row_move); int_puts(integration, "B"); } } if (term->cursor_x != -1 && term->cursor_y != -1) { if (term->setup_state == SETUP_STATE_INLINE) { term->inline_current_terminal_cursor_line = term->cursor_y; int_puts(integration, "\r"); if (term->cursor_x) { int_puts(integration, "\033["); int_put_num(integration, term->cursor_x); int_puts(integration, "C"); } int move_up = surface->height - 1 - term->cursor_y; if (move_up) { int_puts(integration, "\033["); int_put_num(integration, move_up); int_puts(integration, "A"); } } else { termpaintp_terminal_set_cursor(term, term->cursor_x, term->cursor_y); } } else { if (term->setup_state == SETUP_STATE_INLINE) { term->inline_current_terminal_cursor_line = surface->height - 1; } if (pending_colum_move) { int_puts(integration, "\033["); if (pending_colum_move != 1) { int_put_num(integration, pending_colum_move); } int_puts(integration, "C"); } } termpaintp_terminal_update_cursor_style(term); if (term->cursor_visible) { termpaintp_terminal_show_cursor(term); } if (term->colors_dirty) { termpaint_color_entry *entry = term->colors_dirty; term->colors_dirty = nullptr; while (entry) { termpaint_color_entry *next = entry->next_dirty; entry->dirty = false; entry->next_dirty = nullptr; if (entry->requested.len) { int_puts(integration, "\033]"); int_uputs(integration, entry->base.text); int_puts(integration, ";"); int_uputs(integration, entry->requested.data); int_puts(integration, termpaintp_terminal_correct_string_terminator(term)); } else { int_uputs(integration, entry->restore.data); } entry = next; } } int_puts(integration, "\033[m"); int_flush(integration); } void termpaint_terminal_flush(termpaint_terminal *term, bool full_repaint) { termpaintp_terminal_flush_with_surface(term, full_repaint, &term->primary); } void termpaint_terminal_set_cursor_position(termpaint_terminal *term, int x, int y) { if (x < 0 || y < 0) { term->cursor_x = -1; term->cursor_y = -1; } else { term->cursor_x = x; term->cursor_y = y; } } void termpaint_terminal_set_cursor_visible(termpaint_terminal *term, bool visible) { term->cursor_visible = visible; } void termpaint_terminal_set_cursor_style(termpaint_terminal *term, int style, bool blink) { switch (style) { case TERMPAINT_CURSOR_STYLE_TERM_DEFAULT: term->cursor_style = style; term->cursor_blink = true; break; case TERMPAINT_CURSOR_STYLE_BLOCK: case TERMPAINT_CURSOR_STYLE_UNDERLINE: case TERMPAINT_CURSOR_STYLE_BAR: term->cursor_style = style; term->cursor_blink = blink; break; } } static void termpaintp_terminal_dirty_color_entry(termpaint_terminal *term, termpaint_color_entry *entry) { if (!entry->dirty) { entry->dirty = true; entry->next_dirty = term->colors_dirty; term->colors_dirty = entry; } } bool termpaint_terminal_set_color_mustcheck(termpaint_terminal *term, int color_slot, int r, int g, int b) { char buff_slot[30]; char buff_color[20]; sprintf(buff_slot, "%d", color_slot); sprintf(buff_color, "#%02x%02x%02x", r & 0xff, g & 0xff, b & 0xff); termpaint_color_entry *entry = termpaintp_hash_get(&term->colors, (uchar*)buff_slot); if (!entry) { termpaint_str prealloc_requested = { 0 }; termpaint_str prealloc_restore = { 0 }; if (!termpaintp_str_preallocate(&prealloc_restore, 32)) { return false; } if (!termpaintp_str_preallocate(&prealloc_requested, 7)) { termpaintp_str_destroy(&prealloc_restore); return false; } entry = termpaintp_hash_ensure(&term->colors, (uchar*)buff_slot); if (!entry) { termpaintp_str_destroy(&prealloc_restore); termpaintp_str_destroy(&prealloc_requested); return false; } entry->restore = prealloc_restore; /*move ownership*/ entry->requested = prealloc_requested; /*move ownership*/ entry->save_state = termpaint_save_state_save_requested; if (color_slot == TERMPAINT_COLOR_SLOT_CURSOR) { // even requesting a color report does not allow to restore this, so just reset. // terminals tend to internally store more than just color (e.g. an automatic mode) but can't report those if (termpaint_terminal_capable(term, TERMPAINT_CAPABILITY_7BIT_ST)) { termpaintp_prepend_str(&term->restore_seq_partial, (const uchar*)"\033]112\033\\"); } else { termpaintp_prepend_str(&term->restore_seq_partial, (const uchar*)"\033]112\a"); } int_restore_sequence_complete(term); int_restore_sequence_updated(term); if (termpaint_terminal_capable(term, TERMPAINT_CAPABILITY_7BIT_ST)) { termpaintp_str_assign(&entry->restore, "\033]112\033\\"); } else { termpaintp_str_assign(&entry->restore, "\033]112\a"); } entry->save_state = termpaint_save_state_ready; termpaintp_terminal_dirty_color_entry(term, entry); } else { termpaint_integration *integration = term->integration; int_puts(integration, "\033]"); int_put_num(integration, color_slot); if (termpaint_terminal_capable(term, TERMPAINT_CAPABILITY_7BIT_ST)) { int_puts(integration, ";?\033\\"); } else { int_puts(integration, ";?\a"); } int_awaiting_response(integration); int_flush(integration); } } if (entry->requested.len && ustrcmp(entry->requested.data, (uchar*)buff_color) == 0) { return true; } if (entry->save_state == termpaint_save_state_ready) { termpaintp_terminal_dirty_color_entry(term, entry); } else if (entry->save_state == termpaint_save_state_save_requested) { // nothing to do, color should be dirty already and color query event will trigger further processing. } termpaintp_str_assign(&entry->requested, buff_color); return true; } void termpaint_terminal_set_color(termpaint_terminal *term, int color_slot, int r, int g, int b) { if (!termpaint_terminal_set_color_mustcheck(term, color_slot, r, g, b)) { termpaintp_oom(term); } } void termpaint_terminal_reset_color(termpaint_terminal *term, int color_slot) { char buff[100]; sprintf(buff, "%d", color_slot); termpaint_color_entry *entry = termpaintp_hash_get(&term->colors, (uchar*)buff); if (!entry) { // not set, so no need to reset return; } if (entry->save_state == termpaint_save_state_ready) { termpaintp_terminal_dirty_color_entry(term, entry); } termpaintp_str_assign(&entry->requested, ""); } static bool termpaintp_terminal_auto_detect_event(termpaint_terminal *terminal, termpaint_event *event); static bool termpaintp_input_raw_filter_callback(void *user_data, const char *data, unsigned length, _Bool overflow) { termpaint_terminal *term = user_data; if (term->ad_state == AD_NONE || term->ad_state == AD_FINISHED) { if (term->raw_input_filter_cb) { return term->raw_input_filter_cb(term->raw_input_filter_user_data, data, length, overflow); } else { return false; } } else { return false; } } static int termpaintp_parse_delimited_uint(char *s, char delim) { int res = 0; for (; *s; s++) { if (termpaintp_char_ascii_num(*s)) { res = res * 10 + *s - '0'; } else if (*s == delim) { return res; } else { return -1; } } return -1; } static int termpaintp_parse_version(char *s) { int res = 0; int place = 0; int tmp = 0; for (; *s; s++) { if (termpaintp_char_ascii_num(*s)) { tmp = tmp * 10 + *s - '0'; } else if (*s == '.') { if (tmp >= 1000) { tmp = 999; } if (place == 0) { res += tmp * 1000000; } else if (place == 1) { res += tmp * 1000; } else if (place == 2) { break; } tmp = 0; ++place; } else { break; } } if (tmp >= 1000) { tmp = 999; } if (place == 0) { res += tmp * 1000000; } else if (place == 1) { res += tmp * 1000; } else if (place == 2) { res += tmp; return res; } return res; } static int termpaintp_parse_version_strict(char *s, int placeMul) { int res = 0; int place = 0; int tmp = 0; for (; *s; s++) { if (termpaintp_char_ascii_num(*s)) { tmp = tmp * 10 + *s - '0'; } else if (*s == '.') { if (tmp >= placeMul) { tmp = placeMul - 1; } if (place == 0) { res += tmp * placeMul * placeMul; } else if (place == 1) { res += tmp * placeMul; } else if (place == 2) { break; } tmp = 0; ++place; } else { tmp = 0; break; } } if (tmp >= placeMul) { tmp = placeMul - 1; } if (place == 0) { res += tmp * placeMul * placeMul; } else if (place == 1) { res += tmp * placeMul; } else if (place == 2) { res += tmp; return res; } return res; } static void termpaintp_auto_detect_init_terminal_version_and_caps(termpaint_terminal *term) { if (termpaint_terminal_capable(term, TERMPAINT_CAPABILITY_CSI_GREATER)) { // use TERMPAINT_CAPABILITY_CSI_GREATER as indication for more advanced parsing capabilities, // as there is no dedicated detection for this. termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_CSI_POSTFIX_MOD); termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_MAY_TRY_CURSOR_SHAPE); } if (term->terminal_type == TT_MISPARSING) { termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_EXTENDED_CHARSET); } else if (term->terminal_type == TT_TOODUMB) { termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_EXTENDED_CHARSET); } else if (term->terminal_type == TT_BASE) { if (!term->auto_detect_sec_device_attributes.len) { // This is primarily because of linux vc, see somment in TT_LINUX for details. // It's fairly easy for other terminals to work around by implementing ESC [ >c or ESC [ =c termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_EXTENDED_CHARSET); } } else if (term->terminal_type == TT_VTE) { termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_MAY_TRY_TAGGED_PASTE); int version = 0; if (term->terminal_self_reported_name_version.len) { char *version_part = strchr((const char*)term->terminal_self_reported_name_version.data, '('); if (version_part) { int tmp = termpaintp_parse_delimited_uint(version_part + 1, ')'); if (tmp > 0) { version = tmp; term->terminal_version = version; } } } if (!version && term->auto_detect_sec_device_attributes.len > 11) { const unsigned char* data = term->auto_detect_sec_device_attributes.data; bool vte_gt0_54 = memcmp(data, "\033[>65;", 6) == 0; bool vte_old = memcmp(data, "\033[>1;", 5) == 0; if (vte_gt0_54 || vte_old) { if (vte_old) { data += 5; } else { data += 6; } while (termpaintp_char_ascii_num(*data)) { version = version * 10 + *data - '0'; ++data; } if (*data == ';' && (version < 5400) == vte_old) { term->terminal_version = version; } } } if (term->terminal_version < 4000) { termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_MAY_TRY_CURSOR_SHAPE); } else { termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_MAY_TRY_CURSOR_SHAPE); } if (term->terminal_version >= 5400) { termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_TITLE_RESTORE); } if (term->terminal_version < 5400) { // fragile dictinary base parsing. termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_CSI_GREATER); termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_CSI_EQUALS); termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_CSI_POSTFIX_MOD); } if (term->terminal_version < 3600) { termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_TRUECOLOR_MAYBE_SUPPORTED); } else { termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_TRUECOLOR_SUPPORTED); } termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_CLEARED_COLORING_DEFCOLOR); } else if (term->terminal_type == TT_XTERM) { if (term->auto_detect_sec_device_attributes.len > 10) { const unsigned char* data = term->auto_detect_sec_device_attributes.data; while (*data != ';' && *data != 0) { ++data; } if (*data == ';') { ++data; int version = 0; while (termpaintp_char_ascii_num(*data)) { version = version * 10 + *data - '0'; ++data; } if (*data == ';') { term->terminal_version = version; if (term->terminal_version < 282) { // xterm < 282 does not support BAR style. Enable remapping. termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_MAY_TRY_CURSOR_SHAPE_BAR); } } } } termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_TITLE_RESTORE); if (term->terminal_version < 282) { termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_TRUECOLOR_MAYBE_SUPPORTED); } else { termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_TRUECOLOR_SUPPORTED); } // tab is converted to space by the default settings starting from version 333. // But that's true regardless of state of bracketed paste. termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_MAY_TRY_TAGGED_PASTE); } else if (term->terminal_type == TT_SCREEN) { const unsigned char* data = term->auto_detect_sec_device_attributes.data; if (term->auto_detect_sec_device_attributes.len > 10 && memcmp(data, "\033[>83;", 6) == 0) { data += 6; int version = 0; while (termpaintp_char_ascii_num(*data)) { version = version * 10 + *data - '0'; ++data; } if (*data == ';') { term->terminal_version = version; } } termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_TRUECOLOR_MAYBE_SUPPORTED); termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_CLEARED_COLORING); } else if (term->terminal_type == TT_TMUX) { if (term->terminal_self_reported_name_version.len) { char *version_part = strchr((const char*)term->terminal_self_reported_name_version.data, ' '); if (version_part) { term->terminal_version = termpaintp_parse_version(version_part + 1); } } termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_TRUECOLOR_SUPPORTED); } else if (term->terminal_type == TT_KONSOLE) { if (term->terminal_type_confidence == 2) { // this is set by 3RD_DEV_ATTRIB, which was introduced in version 22.03.70 term->terminal_version = 220370; } if (term->terminal_self_reported_name_version.len) { char *version_part = strchr((const char*)term->terminal_self_reported_name_version.data, ' '); if (version_part) { term->terminal_version = termpaintp_parse_version_strict(version_part + 1, 100); } } termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_MAY_TRY_TAGGED_PASTE); // konsole starting at version 18.07.70 could do the CSI space q one too, but // we don't have the konsole version. termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_CURSOR_SHAPE_OSC50); if (term->terminal_version < 220370) { // konsole starting at version 19.08.2 supports 7-bit ST, but // we don't have a fine konsole version. termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_7BIT_ST); // konsole maps some ordinary non spacing characters to width 1, unbreak output by using a specific width table. term->char_width_table = &termpaintp_char_width_konsole2018; } else { term->char_width_table = &termpaintp_char_width_konsole2022; } termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_TRUECOLOR_SUPPORTED); } else if (term->terminal_type == TT_URXVT) { termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_TRUECOLOR_MAYBE_SUPPORTED); // XXX: urxvt 9.19 seems to crash on bracketed paste, so don't set it // at least till 9.22 urxvt sends just ESC as terminator in replies when using ESC \ in the request. termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_7BIT_ST); } else if (term->terminal_type == TT_LINUXVC) { // Linux VC has to fit all character choices into 8 bit or 9 bit (depending on config) // thus is has a very limited set of characters available. What is available exactly // depends on the font, with most fonts optimizing for a given set of languages and // only providing a small set of line drawing characters etc. termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_EXTENDED_CHARSET); } else if (term->terminal_type == TT_MACOS) { termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_TRUECOLOR_MAYBE_SUPPORTED); // does background color erase (bce) but does not allow multiple colors of cleared cells termpaint_terminal_disable_capability(term, TERMPAINT_CAPABILITY_CLEARED_COLORING); } else if (term->terminal_type == TT_TERMINOLOGY) { termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_MAY_TRY_TAGGED_PASTE); // To get here terminology has to be at least 1.4 (first version to support DA3) if (term->terminal_self_reported_name_version.len) { char *version_part = strchr((const char*)term->terminal_self_reported_name_version.data, ' '); if (version_part) { term->terminal_version = termpaintp_parse_version(version_part + 1); } } // terminology approximates to 256 color palette internally (since 1.2.0), but that's ok. termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_TRUECOLOR_SUPPORTED); if (term->terminal_version >= 1007000) { // supported since 1.7.0 termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_TITLE_RESTORE); } // all shapes have been added in 1.2 so this is always safe. termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_MAY_TRY_CURSOR_SHAPE_BAR); } else if (term->terminal_type == TT_MINTTY) { termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_MAY_TRY_TAGGED_PASTE); const unsigned char* data = term->auto_detect_sec_device_attributes.data; if (term->auto_detect_sec_device_attributes.len > 10 && memcmp(data, "\033[>77;", 6) == 0) { data += 6; int version = 0; while (termpaintp_char_ascii_num(*data)) { version = version * 10 + *data - '0'; ++data; } if (*data == ';') { term->terminal_version = version; } } termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_TRUECOLOR_SUPPORTED); termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT); termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_TITLE_RESTORE); } else if (term->terminal_type == TT_KITTY) { if (term->auto_detect_sec_device_attributes.len > 5 && termpaintp_string_prefix((const uchar*)"\033[>1;", term->auto_detect_sec_device_attributes.data, term->auto_detect_sec_device_attributes.len)) { int val = 0; for (const unsigned char *tmp = term->auto_detect_sec_device_attributes.data + 5; *tmp; tmp++) { if (termpaintp_char_ascii_num(*tmp)) { val = val * 10 + (*tmp - '0'); } else if (*tmp == ';') { if (val >= 4000) { int version = (val - 4000 ) * 1000; val = 0; tmp++; for (; *tmp; tmp++) { if (termpaintp_char_ascii_num(*tmp)) { val = val * 10 + (*tmp - '0'); } else if (*tmp == ';' || *tmp == 'c') { if (val < 1000) { version += val; } else { version += 999; } term->terminal_version = version; } else { break; } } } break; } else { break; } } } termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_TRUECOLOR_SUPPORTED); termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_MAY_TRY_TAGGED_PASTE); termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_TITLE_RESTORE); } else if (term->terminal_type == TT_ITERM2) { if (term->terminal_self_reported_name_version.len) { char *version_part = strchr((const char*)term->terminal_self_reported_name_version.data, ' '); if (version_part) { term->terminal_version = termpaintp_parse_version_strict(version_part + 1, 1000); } } termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_TRUECOLOR_SUPPORTED); termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_MAY_TRY_TAGGED_PASTE); } else if (term->terminal_type == TT_MLTERM) { if (term->terminal_self_reported_name_version.len) { char *version_part = strchr((const char*)term->terminal_self_reported_name_version.data, '('); if (version_part) { term->terminal_version = termpaintp_parse_version(version_part + 1); } } termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_MAY_TRY_TAGGED_PASTE); termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_TRUECOLOR_SUPPORTED); term->max_csi_parameters = 10; } else if (term->terminal_type == TT_MSFT_TERMINAL) { termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_TRUECOLOR_SUPPORTED); } else if (term->terminal_type == TT_FULL) { // full is promised to claim support for everything // But TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT, TERMPAINT_CAPABILITY_CSI_GREATER // and TERMPAINT_CAPABILITY_CSI_EQUALS are detected in main finger printing. // 88_COLOR disables 256 color support and is quite rxvt-unicode specific // CURSOR_SHAPE_OSC50 is konsole (and derived) specific, general terminals are expected to use // the ESC[ VAL SP q sequence for cursor shape and blink setup. termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_MAY_TRY_TAGGED_PASTE); termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_TITLE_RESTORE); termpaint_terminal_promise_capability(term, TERMPAINT_CAPABILITY_TRUECOLOR_SUPPORTED); } } void termpaint_terminal_auto_detect_apply_input_quirks(termpaint_terminal *terminal, bool backspace_is_x08) { // For now apply backspace_is_x08 for all terminal types. This can be tuned to disregard this for // specific types when needed. // Note: terminology does not support ctrl-backspace in backspace_is_x08 mode, but does when !backspace_is_x08. // so it seems swapped is still a ok mapping. if (backspace_is_x08) { termpaint_input_activate_quirk(terminal->input, TERMPAINT_INPUT_QUIRK_BACKSPACE_X08_AND_X7F_SWAPPED); } if (terminal->terminal_type == TT_MINTTY) { termpaint_input_activate_quirk(terminal->input, TERMPAINT_INPUT_QUIRK_C1_FOR_CTRL_SHIFT); } } static void termpaintp_input_event_callback(void *user_data, termpaint_event *event) { termpaint_terminal *term = user_data; if (term->ad_state == AD_NONE || term->ad_state == AD_FINISHED) { if (event->type == TERMPAINT_EV_COLOR_SLOT_REPORT) { char buff[100]; sprintf(buff, "%d", event->color_slot_report.slot); termpaint_color_entry *entry = termpaintp_hash_ensure(&term->colors, (uchar*)buff); if (entry) { // entry may be nullptr if not requested via termpaint and out of memory if (entry->save_state == termpaint_save_state_save_requested || entry->save_state == termpaint_save_state_new_entry) { entry->save_state = termpaint_save_state_ready; termpaintp_str_assign(&entry->restore, "\033]"); termpaintp_str_append(&entry->restore, (const char*)entry->base.text); termpaintp_str_append(&entry->restore, ";"); termpaintp_str_append_n(&entry->restore, event->color_slot_report.color, event->color_slot_report.length); termpaintp_str_append(&entry->restore, termpaintp_terminal_correct_string_terminator(term)); termpaintp_prepend_str(&term->restore_seq_partial, entry->restore.data); int_restore_sequence_complete(term); int_restore_sequence_updated(term); if (entry->requested.len && !entry->dirty) { term->request_repaint = true; termpaintp_terminal_dirty_color_entry(term, entry); } } } } term->event_cb(term->event_user_data, event); } else { termpaintp_terminal_auto_detect_event(term, event); int_flush(term->integration); if (term->ad_state == AD_FINISHED) { termpaintp_auto_detect_init_terminal_version_and_caps(term); if (term->event_cb) { termpaint_event event; event.type = TERMPAINT_EV_AUTO_DETECT_FINISHED; term->event_cb(term->event_user_data, &event); } } } } void termpaint_terminal_callback(termpaint_terminal *term) { if (term->data_pending_after_input_received) { term->data_pending_after_input_received = false; termpaint_integration *integration = term->integration; int_puts(integration, "\033[5n"); int_awaiting_response(integration); int_flush(integration); } } void termpaint_terminal_set_raw_input_filter_cb(termpaint_terminal *term, bool (*cb)(void *, const char *, unsigned, bool), void *user_data) { term->raw_input_filter_cb = cb; term->raw_input_filter_user_data = user_data; } void termpaint_terminal_set_event_cb(termpaint_terminal *term, void (*cb)(void *, termpaint_event *), void *user_data) { term->event_cb = cb; term->event_user_data = user_data; } void termpaint_terminal_add_input_data(termpaint_terminal *term, const char *data, unsigned length) { if (term->log_mask & TERMPAINT_LOG_TRACE_RAW_INPUT) { int_debuglog_puts(term, "Input: "); for (unsigned i = 0; i < length; i++) { int_debuglog_printf(term, "%.2hhx", (unsigned char)data[i]); } int_debuglog_puts(term, "\n"); } termpaint_input_add_data(term->input, data, length); bool not_in_autodetect = (term->ad_state == AD_NONE || term->ad_state == AD_FINISHED); if (not_in_autodetect && term->request_repaint) { termpaint_event event; event.type = TERMPAINT_EV_REPAINT_REQUESTED; term->event_cb(term->event_user_data, &event); term->request_repaint = false; } if (not_in_autodetect && termpaint_input_peek_buffer_length(term->input)) { term->data_pending_after_input_received = true; if (term->integration_vtbl->request_callback) { term->integration_vtbl->request_callback(term->integration); } else { termpaint_terminal_callback(term); } } else { term->data_pending_after_input_received = false; } } const char *termpaint_terminal_peek_input_buffer(const termpaint_terminal *term) { return termpaint_input_peek_buffer(term->input); } int termpaint_terminal_peek_input_buffer_length(const termpaint_terminal *term) { return termpaint_input_peek_buffer_length(term->input); } void termpaint_terminal_expect_cursor_position_report(termpaint_terminal *term) { termpaint_input_expect_cursor_position_report(term->input); } void termpaint_terminal_expect_legacy_mouse_reports(termpaint_terminal *term, int s) { termpaint_input_expect_legacy_mouse_reports(term->input, s); } void termpaint_terminal_handle_paste(termpaint_terminal *term, bool enabled) { termpaint_input_handle_paste(term->input, enabled); } void termpaint_terminal_expect_apc_input_sequences(termpaint_terminal *term, bool enabled) { termpaint_input_expect_apc_sequences(term->input, enabled); } void termpaint_terminal_activate_input_quirk(termpaint_terminal *term, int quirk) { termpaint_input_activate_quirk(term->input, quirk); } static void termpaintp_patch_misparsing_defered(termpaint_terminal *terminal, termpaint_integration *integration, auto_detect_state next_state) { terminal->ad_state = AD_GLITCH_PATCHING; terminal->glitch_patching_next_state = next_state; int reset_x = terminal->initial_cursor_x; int reset_y = terminal->initial_cursor_y; if ((terminal->initial_cursor_y == terminal->glitch_cursor_y) && (terminal->initial_cursor_x > terminal->glitch_cursor_x)) { // when starting on the last line a line wrap caused by glitches will scroll and thus the cursor seemingly // just moves left on the same line. Gliches start on the line that has scrolled up when wraping thus start on // that line reset_y -= 1; } int_puts(integration, "\033["); int_put_num(integration, reset_y + 1); int_puts(integration, ";"); int_put_num(integration, reset_x + 1); int_puts(integration, "H"); int_puts(integration, " "); if (termpaint_terminal_capable(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT)) { int_puts(integration, "\033[?6n"); } else { int_puts(integration, "\033[6n"); termpaint_terminal_expect_cursor_position_report(terminal); } } static void termpaintp_patch_misparsing_from_event(termpaint_terminal *terminal, termpaint_integration *integration, termpaint_event *event, auto_detect_state next_state) { terminal->glitch_cursor_x = event->cursor_position.x; terminal->glitch_cursor_y = event->cursor_position.y; termpaintp_patch_misparsing_defered(terminal, integration, next_state); } static void termpaintp_terminal_auto_detect_prepare_self_reporting(termpaint_terminal *terminal, int new_state) { termpaint_integration *integration = terminal->integration; int_puts(integration, "\033[>q"); bool might_be_kitty = false; bool might_be_iterm2 = false; bool might_be_mlterm = false; if (terminal->auto_detect_sec_device_attributes.len) { const int attr_len = terminal->auto_detect_sec_device_attributes.len; if (termpaintp_string_prefix((const uchar*)"\033[>1;", terminal->auto_detect_sec_device_attributes.data, attr_len)) { int val = 0; for (const unsigned char *tmp = terminal->auto_detect_sec_device_attributes.data + 5; *tmp; tmp++) { if (termpaintp_char_ascii_num(*tmp)) { val = val * 10 + (*tmp - '0'); } else if (*tmp == ';') { if (val >= 4000) { might_be_kitty = true; } break; } else { break; } } } might_be_iterm2 = (!terminal->seen_dec_terminal_param && attr_len == 10 && memcmp(terminal->auto_detect_sec_device_attributes.data, "\033[>0;95;0c", 10) == 0); might_be_mlterm = (terminal->seen_dec_terminal_param && attr_len == 12 && memcmp(terminal->auto_detect_sec_device_attributes.data, "\033[>24;279;0c", 10) == 0); } if (might_be_kitty || might_be_iterm2 || might_be_mlterm) { int_puts(integration, "\033P+q544e\033\\"); } int_puts(integration, "\033[5n"); int_awaiting_response(integration); terminal->ad_state = new_state; } // known terminals where auto detections hangs: freebsd system console using vt module static bool termpaintp_terminal_auto_detect_event(termpaint_terminal *terminal, termpaint_event *event) { termpaint_integration *integration = terminal->integration; if (event == nullptr) { terminal->ad_state = AD_INITIAL; } if (terminal->log_mask & TERMPAINT_LOG_AUTO_DETECT_TRACE) { if (event) { int_debuglog_printf(terminal, "AD: State=%d, Event-type=%d\n", terminal->ad_state, event->type); } else { int_debuglog_printf(terminal, "AD start: State=%d\n", terminal->ad_state); } } switch (terminal->ad_state) { case AD_NONE: case AD_FINISHED: // should not happen break; case AD_INITIAL: terminal->glitch_cursor_y = -1; // disarmed glitch patching state termpaint_input_expect_cursor_position_report(terminal->input); termpaint_input_expect_cursor_position_report(terminal->input); int_puts(integration, "\033[5n"); int_puts(integration, "\033[6n"); int_puts(integration, "\033[>c"); int_puts(integration, "\033[6n"); int_puts(integration, "\033[5n"); int_awaiting_response(integration); terminal->ad_state = AD_BASICCOMPAT; return true; case AD_BASICCOMPAT: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { terminal->ad_state = AD_BASIC_REQ; return true; } else if (event->type == TERMPAINT_EV_CURSOR_POSITION) { terminal->initial_cursor_x = event->cursor_position.x; terminal->initial_cursor_y = event->cursor_position.y; terminal->terminal_type = TT_INCOMPATIBLE; terminal->ad_state = AD_BASIC_REQ_FAILED_CURPOS_RECVED; return true; } else if (event->type == TERMPAINT_EV_CHAR && event->c.length == 1 && event->c.string[0] == '0' && event->c.modifier == TERMPAINT_MOD_ALT) { // hterm has a long standing typo in it's \033[5n reply that replys with a missing '[' terminal->terminal_type = TT_INCOMPATIBLE; terminal->ad_state = AD_HTERM_RECOVERY1; return true; } else if (event->type == TERMPAINT_EV_RAW_SEC_DEV_ATTRIB) { terminal->terminal_type = TT_TOODUMB; terminal->ad_state = AD_FINISHED; return false; } break; case AD_BASIC_REQ_FAILED_CURPOS_RECVED: if (event->type == TERMPAINT_EV_CURSOR_POSITION) { terminal->ad_state = AD_FINISHED; return false; } else if (event->type == TERMPAINT_EV_RAW_SEC_DEV_ATTRIB) { // no use, but still need to wait for cursor report reply terminal->ad_state = AD_BASIC_REQ_FAILED_CURPOS_RECVED; return true; } break; case AD_BASIC_REQ: if (event->type == TERMPAINT_EV_CURSOR_POSITION) { terminal->initial_cursor_x = event->cursor_position.x; terminal->initial_cursor_y = event->cursor_position.y; terminal->ad_state = AD_BASIC_CURPOS_RECVED; return true; } else if (event->type == TERMPAINT_EV_RAW_SEC_DEV_ATTRIB) { terminal->terminal_type = TT_TOODUMB; terminal->ad_state = AD_EXPECT_SYNC_TO_FINISH; return true; } else if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { terminal->terminal_type = TT_TOODUMB; terminal->ad_state = AD_FINISHED; return false; } break; case AD_BASIC_CURPOS_RECVED: if (event->type == TERMPAINT_EV_RAW_SEC_DEV_ATTRIB) { termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_CSI_GREATER); termpaintp_str_assign_n(&terminal->auto_detect_sec_device_attributes, event->raw.string, event->raw.length); if (event->raw.length > 6 && memcmp("\033[>85;", event->raw.string, 6) == 0) { // urxvt source says: first parameter is 'U' / 85 for urxvt (except for 7.[34]) termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_CSI_EQUALS); terminal->terminal_type = TT_URXVT; terminal->terminal_type_confidence = 2; } if (event->raw.length > 6 && memcmp("\033[>83;", event->raw.string, 6) == 0) { // 83 = 'S' // second parameter is version as major*10000 + minor * 100 + patch termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_CSI_EQUALS); terminal->terminal_type = TT_SCREEN; terminal->terminal_type_confidence = 2; } if (event->raw.length > 6 && memcmp("\033[>84;", event->raw.string, 6) == 0) { // 84 = 'T' // no version here termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_CSI_EQUALS); terminal->terminal_type = TT_TMUX; terminal->terminal_type_confidence = 2; } if (event->raw.length > 6 && memcmp("\033[>77;", event->raw.string, 6) == 0) { // 77 = 'M' // second parameter is version as major*10000 + minor * 100 + patch termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_CSI_EQUALS); terminal->terminal_type = TT_MINTTY; terminal->terminal_type_confidence = 2; } terminal->ad_state = AD_BASIC_SEC_DEV_ATTRIB_RECVED_CONSUME_CURPOS; return true; } else if (event->type == TERMPAINT_EV_RAW_PRI_DEV_ATTRIB) { // We never asked for primary device attributes. This means the terminal gets // basic parsing rules wrong. terminal->terminal_type = TT_TOODUMB; terminal->ad_state = AD_WAIT_FOR_SYNC_TO_FINISH; return true; } else if (event->type == TERMPAINT_EV_CURSOR_POSITION) { // check if finger printing left printed characters if (terminal->initial_cursor_x == event->cursor_position.x && terminal->initial_cursor_y == event->cursor_position.y) { termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_CSI_GREATER); terminal->ad_state = AD_BASIC_CURPOS_RECVED_NO_SEC_DEV_ATTRIB; return true; } else { termpaint_terminal_disable_capability(terminal, TERMPAINT_CAPABILITY_CSI_GREATER); terminal->terminal_type = TT_MISPARSING; // prepare defered glitch patching terminal->glitch_cursor_x = event->cursor_position.x; terminal->glitch_cursor_y = event->cursor_position.y; terminal->ad_state = AD_BASIC_NO_SEC_DEV_ATTRIB_MISPARSING; return true; } } break; case AD_BASIC_NO_SEC_DEV_ATTRIB_MISPARSING: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { termpaintp_patch_misparsing_defered(terminal, integration, AD_FINISHED); return true; } break; case AD_BASIC_CURPOS_RECVED_NO_SEC_DEV_ATTRIB: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_CSI_GREATER); int_puts(integration, "\033[=c"); int_puts(integration, "\033[>1c"); int_puts(integration, "\033[?6n"); int_puts(integration, "\033[1x"); int_puts(integration, "\033[5n"); int_awaiting_response(integration); terminal->ad_state = AD_FP1_REQ; return true; } break; case AD_BASIC_SEC_DEV_ATTRIB_RECVED_CONSUME_CURPOS: if (event->type == TERMPAINT_EV_CURSOR_POSITION) { terminal->ad_state = AD_BASIC_SEC_DEV_ATTRIB_RECVED; return true; } break; case AD_BASIC_SEC_DEV_ATTRIB_RECVED: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { if (terminal->terminal_type_confidence >= 2) { if (terminal->terminal_type == TT_URXVT) { // auto detect 88 or 256 color mode by observing if querying color 255 results in a response termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_88_COLOR); // Using BEL as termination, because urxvt doesn't properly support ESC \ as terminator // at least till 9.22 urxvt sends just ESC as terminator when using ESC \ in the request. int_puts(integration, "\033]4;255;?\007"); int_puts(integration, "\033[5n"); terminal->ad_state = AD_URXVT_88_256_REQ; return true; } else { termpaintp_terminal_auto_detect_prepare_self_reporting(terminal, AD_SELF_REPORTING); return true; } } int_puts(integration, "\033[=c"); int_puts(integration, "\033[>1c"); int_puts(integration, "\033[?6n"); int_puts(integration, "\033[1x"); int_puts(integration, "\033[5n"); int_awaiting_response(integration); terminal->ad_state = AD_FP1_REQ; return true; } break; case AD_URXVT_88_256_REQ: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { termpaintp_terminal_auto_detect_prepare_self_reporting(terminal, AD_SELF_REPORTING); return true; } else if (event->type == TERMPAINT_EV_PALETTE_COLOR_REPORT) { termpaint_terminal_disable_capability(terminal, TERMPAINT_CAPABILITY_88_COLOR); return true; } break; case AD_FP1_REQ: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { if (terminal->terminal_type_confidence == 0) { terminal->terminal_type = TT_BASE; } termpaint_terminal_disable_capability(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT); // see if "\033[=c" was misparsed termpaint_input_expect_cursor_position_report(terminal->input); int_puts(integration, "\033[6n"); int_awaiting_response(integration); terminal->ad_state = AD_FP1_CLEANUP; return true; } else if (event->type == TERMPAINT_EV_RAW_3RD_DEV_ATTRIB) { termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_CSI_EQUALS); if (event->raw.length == 8) { // Terminal implementors: DO NOT fake other terminal IDs here! // any unknown id will enable all features here. Allocate a new one! if (memcmp(event->raw.string, "7E565445", 8) == 0) { // ~VTE terminal->terminal_type = TT_VTE; terminal->terminal_type_confidence = 2; } else if (memcmp(event->raw.string, "7E7E5459", 8) == 0) { // ~~TY terminal->terminal_type = TT_TERMINOLOGY; terminal->terminal_type_confidence = 2; } else if (memcmp(event->raw.string, "7E4B4445", 8) == 0) { // ~KDE terminal->terminal_type = TT_KONSOLE; terminal->terminal_type_confidence = 2; } else if (memcmp(event->raw.string, "7E4C4E58", 8) == 0) { // ~LNX terminal->terminal_type = TT_LINUXVC; terminal->terminal_type_confidence = 2; } else if (memcmp(event->raw.string, "00000000", 8) == 0) { // xterm uses this since 336. But this could be something else too. // Microsoft Terminal uses this as well. terminal->terminal_type = TT_BASE; if (terminal->auto_detect_sec_device_attributes.len && ustr_eq(terminal->auto_detect_sec_device_attributes.data, (const uchar*)"\033[>0;10;1c")) { terminal->terminal_type = TT_MSFT_TERMINAL; terminal->terminal_type_confidence = 1; } else { if (terminal->auto_detect_sec_device_attributes.len > 10) { const unsigned char* data = terminal->auto_detect_sec_device_attributes.data; while (*data != ';' && *data != 0) { ++data; } if (*data == ';') { ++data; int version = 0; while (termpaintp_char_ascii_num(*data)) { version = version * 10 + *data - '0'; ++data; } if (*data == ';') { if (version >= 336) { terminal->terminal_type = TT_XTERM; terminal->terminal_type_confidence = 1; } } } } } } else { terminal->terminal_type = TT_FULL; terminal->terminal_type_confidence = 1; } terminal->ad_state = AD_FP1_REQ_TERMID_RECVED; } else if (event->raw.length == 1 && event->raw.string[0] == '0') { // xterm uses this between 280 and 335. But this could be something else too. if (terminal->auto_detect_sec_device_attributes.len == 12 && memcmp(terminal->auto_detect_sec_device_attributes.data, "\033[>41;", 6) == 0 && termpaintp_char_ascii_num(terminal->auto_detect_sec_device_attributes.data[6]) && termpaintp_char_ascii_num(terminal->auto_detect_sec_device_attributes.data[7]) && termpaintp_char_ascii_num(terminal->auto_detect_sec_device_attributes.data[8]) && memcmp(terminal->auto_detect_sec_device_attributes.data + 9, ";0c", 3) == 0) { int version = (terminal->auto_detect_sec_device_attributes.data[6] - '0') * 100 + (terminal->auto_detect_sec_device_attributes.data[7] - '0') * 10 + terminal->auto_detect_sec_device_attributes.data[8] - '0'; if (280 <= version && version <= 335) { terminal->terminal_type = TT_XTERM; terminal->terminal_type_confidence = 1; terminal->ad_state = AD_FP1_REQ_TERMID_RECVED; } } } return true; } else if (event->type == TERMPAINT_EV_RAW_SEC_DEV_ATTRIB) { terminal->ad_state = AD_FP1_SEC_DEV_ATTRIB_RECVED; return true; } else if (event->type == TERMPAINT_EV_CURSOR_POSITION) { if (event->cursor_position.safe) { termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT); } else { termpaint_terminal_disable_capability(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT); } if (terminal->initial_cursor_y != event->cursor_position.y || terminal->initial_cursor_x != event->cursor_position.x) { // prepare defered glitch patching terminal->glitch_cursor_x = event->cursor_position.x; terminal->glitch_cursor_y = event->cursor_position.y; terminal->terminal_type = TT_BASE; } else { termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_CSI_EQUALS); terminal->terminal_type = TT_BASE; } terminal->ad_state = AD_FP1_QMCURSOR_POS_RECVED; return true; } else if (event->type == TERMPAINT_EV_RAW_DECREQTPARM) { terminal->seen_dec_terminal_param = true; if (terminal->terminal_type_confidence == 0) { terminal->terminal_type = TT_BASE; } termpaint_terminal_disable_capability(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT); terminal->ad_state = AD_FP1_CLEANUP_AFTER_SYNC; return true; } else if (event->type == TERMPAINT_EV_RAW_PRI_DEV_ATTRIB) { // For terminals that misinterpret \033[=c as \033[c terminal->ad_state = AD_FP1_3RD_DEV_ATTRIB_ALIASED_TO_PRI; return true; } break; case AD_FP1_3RD_DEV_ATTRIB_ALIASED_TO_PRI: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { terminal->terminal_type = TT_BASE; terminal->ad_state = AD_FINISHED; return false; } else if (event->type == TERMPAINT_EV_RAW_DECREQTPARM) { terminal->seen_dec_terminal_param = true; terminal->terminal_type = TT_MACOS; terminal->ad_state = AD_EXPECT_SYNC_TO_FINISH; return true; } else { terminal->terminal_type = TT_BASE; terminal->ad_state = AD_WAIT_FOR_SYNC_TO_FINISH; return true; } break; case AD_FP1_CLEANUP: if (event->type == TERMPAINT_EV_CURSOR_POSITION) { if (terminal->initial_cursor_y != event->cursor_position.y || terminal->initial_cursor_x != event->cursor_position.x) { termpaintp_patch_misparsing_from_event(terminal, integration, event, AD_FINISHED); return true; } else { termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_CSI_EQUALS); termpaintp_terminal_auto_detect_prepare_self_reporting(terminal, AD_SELF_REPORTING); return true; } } break; case AD_EXPECT_SYNC_TO_FINISH: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { terminal->ad_state = AD_FINISHED; return false; } break; case AD_FP1_CLEANUP_AFTER_SYNC: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { // see if "\033[=c" was misparsed if (termpaint_terminal_capable(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT)) { int_puts(integration, "\033[?6n"); } else { termpaint_input_expect_cursor_position_report(terminal->input); int_puts(integration, "\033[6n"); } int_awaiting_response(integration); terminal->ad_state = AD_FP1_CLEANUP; return true; } break; case AD_WAIT_FOR_SYNC_TO_SELF_REPORTING: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { termpaintp_terminal_auto_detect_prepare_self_reporting(terminal, AD_SELF_REPORTING); return true; } else { if (event->type != TERMPAINT_EV_KEY && event->type != TERMPAINT_EV_CHAR) { return true; } } break; case AD_EXPECT_SYNC_TO_SELF_REPORTING: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { termpaintp_terminal_auto_detect_prepare_self_reporting(terminal, AD_SELF_REPORTING); return true; } break; case AD_SELF_REPORTING: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { terminal->ad_state = AD_FINISHED; return false; } else if (event->type == TERMPAINT_EV_RAW_TERM_NAME) { terminal->ad_state = AD_SELF_REPORTING; termpaintp_str_assign_n(&terminal->terminal_self_reported_name_version, event->raw.string, event->raw.length); if (termpaintp_string_prefix((const uchar*)"terminology ", (const uchar*)event->raw.string, event->raw.length)) { terminal->terminal_type = TT_TERMINOLOGY; } if (termpaintp_string_prefix((const uchar*)"iTerm2 ", (const uchar*)event->raw.string, event->raw.length)) { terminal->terminal_type = TT_ITERM2; } if (termpaintp_string_prefix((const uchar*)"VTE(", (const uchar*)event->raw.string, event->raw.length)) { terminal->terminal_type = TT_VTE; } return true; } else if (event->type == TERMPAINT_EV_RAW_TERMINFO_QUERY_REPLY) { terminal->ad_state = AD_SELF_REPORTING; if (event->raw.length >= 8 && event->raw.string[0] == '1') { // only successful/valid reports if (termpaintp_mem_ascii_case_insensitive_equals(event->raw.string + 3, "544e=", 5)) { if (event->raw.length == 30 && termpaintp_mem_ascii_case_insensitive_equals(event->raw.string + 8, "787465726d2d6b69747479", 22)) { terminal->terminal_type = TT_KITTY; } if (event->raw.length == 20 && termpaintp_mem_ascii_case_insensitive_equals(event->raw.string + 8, "695465726d32", 12)) { terminal->terminal_type = TT_ITERM2; } if (event->raw.length == 20 && termpaintp_mem_ascii_case_insensitive_equals(event->raw.string + 8, "6D6C7465726D", 12)) { terminal->terminal_type = TT_MLTERM; } } } return true; } break; case AD_WAIT_FOR_SYNC_TO_FINISH: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { terminal->ad_state = AD_FINISHED; return false; } else { if (event->type != TERMPAINT_EV_KEY && event->type != TERMPAINT_EV_CHAR) { return true; } } break; case AD_FP1_REQ_TERMID_RECVED: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { termpaint_terminal_disable_capability(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT); termpaintp_terminal_auto_detect_prepare_self_reporting(terminal, AD_SELF_REPORTING); return true; } else if (event->type == TERMPAINT_EV_RAW_SEC_DEV_ATTRIB) { terminal->ad_state = AD_FP1_REQ_TERMID_RECVED_SEC_DEV_ATTRIB_RECVED; return true; } else if (event->type == TERMPAINT_EV_CURSOR_POSITION) { // keep terminal_type from terminal id if (event->cursor_position.safe) { termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT); } else { termpaint_terminal_disable_capability(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT); } terminal->ad_state = AD_WAIT_FOR_SYNC_TO_SELF_REPORTING; return true; } else if (event->type == TERMPAINT_EV_RAW_DECREQTPARM) { terminal->seen_dec_terminal_param = true; termpaint_terminal_disable_capability(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT); terminal->ad_state = AD_EXPECT_SYNC_TO_SELF_REPORTING; return true; } break; case AD_FP1_REQ_TERMID_RECVED_SEC_DEV_ATTRIB_RECVED: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { termpaint_terminal_disable_capability(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT); termpaintp_terminal_auto_detect_prepare_self_reporting(terminal, AD_SELF_REPORTING); return true; } else if (event->type == TERMPAINT_EV_CURSOR_POSITION) { if (event->cursor_position.safe) { termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT); } else { termpaint_terminal_disable_capability(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT); } terminal->ad_state = AD_WAIT_FOR_SYNC_TO_SELF_REPORTING; return true; } else if (event->type == TERMPAINT_EV_RAW_DECREQTPARM) { terminal->seen_dec_terminal_param = true; // ignore return true; } break; case AD_FP1_SEC_DEV_ATTRIB_RECVED: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { termpaint_terminal_disable_capability(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT); termpaint_input_expect_cursor_position_report(terminal->input); int_puts(integration, "\033[6n"); // detect if "\033[=c" was misparsed int_puts(integration, "\033[>0;1c"); int_puts(integration, "\033[5n"); int_awaiting_response(integration); terminal->ad_state = AD_FP2_REQ; return true; } else if (event->type == TERMPAINT_EV_CURSOR_POSITION) { if (event->cursor_position.safe) { termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT); } else { termpaint_terminal_disable_capability(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT); } if (terminal->initial_cursor_y != event->cursor_position.y || terminal->initial_cursor_x != event->cursor_position.x) { // prepare defered glitch patching terminal->glitch_cursor_x = event->cursor_position.x; terminal->glitch_cursor_y = event->cursor_position.y; } else { termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_CSI_EQUALS); } terminal->ad_state = AD_FP1_SEC_DEV_ATTRIB_QMCURSOR_POS_RECVED; return true; } else if (event->type == TERMPAINT_EV_RAW_DECREQTPARM) { terminal->seen_dec_terminal_param = true; // ignore return true; } break; case AD_FP1_QMCURSOR_POS_RECVED: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { if (terminal->glitch_cursor_y != -1) { termpaintp_patch_misparsing_defered(terminal, integration, AD_FINISHED); return true; } else { termpaintp_terminal_auto_detect_prepare_self_reporting(terminal, AD_SELF_REPORTING); return true; } } else if (event->type == TERMPAINT_EV_RAW_DECREQTPARM) { terminal->seen_dec_terminal_param = true; if (terminal->auto_detect_sec_device_attributes.len && termpaint_terminal_capable(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT) && termpaint_terminal_capable(terminal, TERMPAINT_CAPABILITY_CSI_EQUALS) && termpaintp_str_ends_with(terminal->auto_detect_sec_device_attributes.data, (const uchar*)";0c")) { terminal->terminal_type = TT_XTERM; } return true; } break; case AD_FP1_SEC_DEV_ATTRIB_QMCURSOR_POS_RECVED: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { int_puts(integration, "\033[>0;1c"); int_puts(integration, "\033[5n"); int_awaiting_response(integration); terminal->ad_state = AD_FP2_CURSOR_DONE; return true; } else if (event->type == TERMPAINT_EV_RAW_DECREQTPARM) { terminal->seen_dec_terminal_param = true; if (terminal->auto_detect_sec_device_attributes.len && event->raw.length == 4 && memcmp(event->raw.string, "\033[?x", 4) == 0 && terminal->glitch_cursor_y == -1) { // this triggers on VTE < 0.54 which has fragile dictionary based parsing. // The self reporting stage would cause misparsing, so skip it here. terminal->terminal_type = TT_VTE; terminal->ad_state = AD_EXPECT_SYNC_TO_FINISH; } else { // ignore } return true; } break; case AD_FP2_REQ: if (event->type == TERMPAINT_EV_CURSOR_POSITION) { if (terminal->initial_cursor_y != event->cursor_position.y || terminal->initial_cursor_x != event->cursor_position.x) { // prepare defered glitch patching terminal->glitch_cursor_x = event->cursor_position.x; terminal->glitch_cursor_y = event->cursor_position.y; } else { termpaint_terminal_promise_capability(terminal, TERMPAINT_CAPABILITY_CSI_EQUALS); } terminal->ad_state = AD_FP2_CURSOR_DONE; return true; } break; case AD_FP2_CURSOR_DONE: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { if (terminal->terminal_type_confidence == 0) { terminal->terminal_type = TT_BASE; } if (terminal->glitch_cursor_y == -1) { termpaintp_terminal_auto_detect_prepare_self_reporting(terminal, AD_SELF_REPORTING); return true; } else { termpaintp_patch_misparsing_defered(terminal, integration, AD_FINISHED); return true; } } else if (event->type == TERMPAINT_EV_RAW_SEC_DEV_ATTRIB) { terminal->ad_state = AD_FP2_SEC_DEV_ATTRIB_RECVED1; return true; } break; case AD_FP2_SEC_DEV_ATTRIB_RECVED1: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { if (terminal->terminal_type_confidence == 0) { terminal->terminal_type = TT_BASE; } if (terminal->glitch_cursor_y == -1) { termpaintp_terminal_auto_detect_prepare_self_reporting(terminal, AD_SELF_REPORTING); return true; } else { termpaintp_patch_misparsing_defered(terminal, integration, AD_FINISHED); return true; } } else if (event->type == TERMPAINT_EV_RAW_SEC_DEV_ATTRIB) { if (terminal->auto_detect_sec_device_attributes.len) { terminal->terminal_type = TT_KONSOLE; } else if (terminal->terminal_type_confidence == 0) { terminal->terminal_type = TT_BASE; } terminal->ad_state = AD_FP2_SEC_DEV_ATTRIB_RECVED2; return true; } break; case AD_FP2_SEC_DEV_ATTRIB_RECVED2: if (event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync()) { if (terminal->glitch_cursor_y == -1) { termpaintp_terminal_auto_detect_prepare_self_reporting(terminal, AD_SELF_REPORTING); return true; } else { termpaintp_patch_misparsing_defered(terminal, integration, AD_FINISHED); return true; } } break; // sub routine glitch patching case AD_GLITCH_PATCHING: if (event->type == TERMPAINT_EV_CURSOR_POSITION) { if ((event->cursor_position.y < terminal->glitch_cursor_y) || ((event->cursor_position.y == terminal->glitch_cursor_y) && (event->cursor_position.x < terminal->glitch_cursor_x))) { if (termpaint_terminal_capable(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT)) { int_puts(integration, " \033[?6n"); } else { termpaint_input_expect_cursor_position_report(terminal->input); int_puts(integration, " \033[6n"); } return true; } else { terminal->glitch_cursor_y = -1; // disarm terminal->ad_state = terminal->glitch_patching_next_state; if (terminal->glitch_patching_next_state == AD_FINISHED) { return false; } else { BUG("AD_GLITCH_PATCHING called with destination state != AD_FINISHED"); } } } break; case AD_HTERM_RECOVERY1: if (event->type == TERMPAINT_EV_CHAR && event->c.length == 1 && event->c.string[0] == '0' && event->c.modifier == TERMPAINT_MOD_ALT) { terminal->ad_state = AD_HTERM_RECOVERY2; return true; } else if (event->type == TERMPAINT_EV_CHAR && event->c.length == 1 && event->c.string[0] == 'n' && event->c.modifier == 0) { // keep in recovery state. return true; } else if ((event->type == TERMPAINT_EV_CURSOR_POSITION) || (event->type == TERMPAINT_EV_RAW_SEC_DEV_ATTRIB)) { // keep in recovery state. return true; } break; case AD_HTERM_RECOVERY2: if (event->type == TERMPAINT_EV_CHAR && event->c.length == 1 && event->c.string[0] == 'n' && event->c.modifier == 0) { terminal->ad_state = AD_FINISHED; return true; } break; }; // if this code runs auto detection failed by running off into the weeds int_debuglog_printf(terminal, "ran off autodetect: s=%d, e=%d\n", (int)terminal->ad_state, (int)event->type); terminal->terminal_type = TT_TOODUMB; terminal->ad_state = AD_FINISHED; return false; } _Bool termpaint_terminal_auto_detect(termpaint_terminal *terminal) { if (!terminal->event_cb) { // bail out, running this without an event callback risks crashing return false; } // reset state just to be safe terminal->terminal_type = TT_UNKNOWN; terminal->terminal_version = 0; terminal->terminal_type_confidence = 0; terminal->initial_cursor_x = -1; terminal->initial_cursor_y = -1; termpaintp_terminal_reset_capabilites(terminal); termpaintp_terminal_auto_detect_event(terminal, nullptr); int_flush(terminal->integration); return true; } enum termpaint_auto_detect_state_enum termpaint_terminal_auto_detect_state(const termpaint_terminal *terminal) { if (terminal->ad_state == AD_FINISHED) { return termpaint_auto_detect_done; } else if (terminal->ad_state == AD_NONE) { return termpaint_auto_detect_none; } else { return termpaint_auto_detect_running; } } bool termpaint_terminal_might_be_supported(const termpaint_terminal *terminal) { return terminal->terminal_type != TT_INCOMPATIBLE; } void termpaint_terminal_auto_detect_result_text(const termpaint_terminal *terminal, char *buffer, int buffer_length) { const char *term_type = nullptr; switch (terminal->terminal_type) { case TT_INCOMPATIBLE: term_type = "incompatible with input handling"; break; case TT_TOODUMB: term_type = "toodumb"; break; case TT_MISPARSING: term_type = "misparsing"; break; case TT_UNKNOWN: term_type = "unknown"; break; case TT_FULL: term_type = "unknown full featured"; break; case TT_BASE: term_type = "base"; break; case TT_LINUXVC: term_type = "linux vc"; break; case TT_KONSOLE: term_type = "konsole"; break; case TT_XTERM: term_type = "xterm"; break; case TT_VTE: term_type = "vte"; break; case TT_SCREEN: term_type = "screen"; break; case TT_TMUX: term_type = "tmux"; break; case TT_URXVT: term_type = "urxvt"; break; case TT_MLTERM: term_type = "mlterm"; break; case TT_TERMINOLOGY: term_type = "terminology"; break; case TT_MACOS: term_type = "apple terminal"; break; case TT_ITERM2: term_type = "iterm2"; break; case TT_MINTTY: term_type = "mintty"; break; case TT_KITTY: term_type = "kitty"; break; case TT_MSFT_TERMINAL: term_type = "microsoft terminal"; break; }; snprintf(buffer, buffer_length, "Type: %s(%d) %s seq:%s%s", term_type, terminal->terminal_version, termpaint_terminal_capable(terminal, TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT) ? "safe-CPR" : "", termpaint_terminal_capable(terminal, TERMPAINT_CAPABILITY_CSI_GREATER) ? ">" : "", termpaint_terminal_capable(terminal, TERMPAINT_CAPABILITY_CSI_EQUALS) ? "=" : ""); buffer[buffer_length-1] = 0; } const char *termpaint_terminal_self_reported_name_and_version(const termpaint_terminal *terminal) { return terminal->terminal_self_reported_name_version.len ? (const char*)terminal->terminal_self_reported_name_version.data : nullptr; } static bool termpaintp_has_option(const char *options, const char *name) { const char *p = options; int name_len = strlen(name); while (1) { const char *found = strstr(p, name); if (!found) { break; } if (found == options || found[-1] == ' ') { if (found[name_len] == 0 || found[name_len] == ' ') { return true; } } p = found + name_len; } return false; } static void termpaintp_terminal_setup_common(termpaint_terminal *terminal, const char *options) { termpaint_str *init_sequence = &terminal->unpause_basic_setup; termpaintp_prepend_str(&terminal->restore_seq_partial, (const uchar*)"\033[?7h"); terminal->did_terminal_disable_wrap = true; termpaintp_str_assign(init_sequence, "\033[?7l"); termpaintp_prepend_str(&terminal->restore_seq_partial, (const uchar*)"\033[?66l"); termpaintp_str_append(init_sequence, "\033[?66h"); termpaintp_str_append(init_sequence, "\033[?1036h"); if (!termpaintp_has_option(options, "+kbdsig") && terminal->terminal_type == TT_XTERM) { // xterm modify other characters // in this keyboard event mode xterm does no longer send the traditional one byte ^C, ^Z ^\ sequences // that the kernel tty layer uses to raise signals. termpaintp_prepend_str(&terminal->restore_seq_partial, (const uchar*)"\033[>4m"); termpaintp_str_append(init_sequence, "\033[>4;2m"); } } void termpaint_terminal_setup_fullscreen(termpaint_terminal *terminal, int width, int height, const char *options) { termpaint_integration *integration = terminal->integration; termpaintp_terminal_setup_common(terminal, options); termpaint_str *init_sequence = &terminal->unpause_basic_setup; if (!termpaintp_has_option(options, "-altscreen")) { int_puts(integration, "\033[?1049h"); terminal->altscreen_active = 1; } int_put_tps(integration, init_sequence); int_flush(integration); int_restore_sequence_complete(terminal); int_restore_sequence_updated(terminal); terminal->setup_state = SETUP_STATE_FULLSCREEN; termpaint_surface_resize(&terminal->primary, width, height); } void termpaint_terminal_setup_inline(termpaint_terminal *terminal, int width, int height, const char *options) { termpaint_integration *integration = terminal->integration; termpaintp_terminal_setup_common(terminal, options); termpaint_str *init_sequence = &terminal->unpause_basic_setup; int_put_tps(integration, init_sequence); int_flush(integration); int_restore_sequence_complete(terminal); int_restore_sequence_updated(terminal); termpaint_surface_resize(&terminal->primary, width, height); terminal->setup_state = SETUP_STATE_INLINE; terminal->inline_current_terminal_cursor_line = 0; } void termpaint_terminal_set_inline(termpaint_terminal *terminal, _Bool enabled) { if (enabled && terminal->setup_state == SETUP_STATE_INLINE) { // already in inline mode return; } if (!enabled && terminal->setup_state == SETUP_STATE_FULLSCREEN) { // already fullscreen return; } if (terminal->setup_state != SETUP_STATE_INLINE && terminal->setup_state != SETUP_STATE_FULLSCREEN) { // bogus return; } termpaint_integration *integration = terminal->integration; if (!enabled) { termpaintp_erase_inline(terminal); terminal->inline_current_terminal_cursor_line = -1; terminal->setup_state = SETUP_STATE_FULLSCREEN; terminal->altscreen_active = 1; int_restore_sequence_complete(terminal); int_restore_sequence_updated(terminal); int_puts(integration, "\033[?1049h"); } else { // for terminals without alt screen support clear terminal. int_puts(integration, "\033[H\033[2J"); terminal->altscreen_active = 0; int_restore_sequence_complete(terminal); int_restore_sequence_updated(terminal); int_puts(integration, "\033[?1049l"); terminal->inline_current_terminal_cursor_line = 0; terminal->last_inline_height = 0; terminal->setup_state = SETUP_STATE_INLINE; } terminal->force_full_repaint = true; } const char* termpaint_terminal_restore_sequence(const termpaint_terminal *term) { return (const char*)(term->restore_seq_cached.len ? term->restore_seq_cached.data : (const uchar*)""); } void termpaint_terminal_pause(termpaint_terminal *term) { termpaint_terminal_pause_and_persistent(term, nullptr); } void termpaint_terminal_pause_and_persistent(termpaint_terminal *term, termpaint_surface *surface) { termpaint_integration *integration = term->integration; term->force_full_repaint = true; bool saved_altscreen_active = term->altscreen_active; if (term->setup_state == SETUP_STATE_INLINE) { termpaintp_erase_inline(term); if (surface) { int saved_cursor_x = term->cursor_x; int saved_cursor_y = term->cursor_y; term->cursor_x = term->cursor_y = -1; termpaintp_terminal_flush_with_surface(term, true, surface); term->cursor_x = saved_cursor_x; term->cursor_y = saved_cursor_y; int_puts(integration, "\r\n"); // Output is persistent term->inline_current_terminal_cursor_line = 0; term->last_inline_height = 0; } } else { if (surface) { if (term->altscreen_active) { // resetting term->altscreen_active here is important to keep the correct cursor position int_puts(integration, "\033[?1049l"); term->altscreen_active = false; int_restore_sequence_complete(term); } term->inline_current_terminal_cursor_line = 0; term->last_inline_height = 0; int saved_setup_state = term->setup_state; int saved_cursor_x = term->cursor_x; int saved_cursor_y = term->cursor_y; term->setup_state = SETUP_STATE_INLINE; term->cursor_x = term->cursor_y = -1; termpaintp_terminal_flush_with_surface(term, true, surface); term->setup_state = saved_setup_state; term->cursor_x = saved_cursor_x; term->cursor_y = saved_cursor_y; int_puts(integration, "\r\n"); // Output is persistent term->inline_current_terminal_cursor_line = 0; term->last_inline_height = 0; } } if (term->restore_seq_cached.len) { int_write(integration, (const char*)term->restore_seq_cached.data, term->restore_seq_cached.len); } if (saved_altscreen_active != term->altscreen_active) { term->altscreen_active = saved_altscreen_active; int_restore_sequence_complete(term); } int_flush(integration); } void termpaint_terminal_unpause(termpaint_terminal *term) { term->cursor_prev_data = -2; termpaint_integration *integration = term->integration; // reconstruct state after setup_fullscreen int_put_tps(integration, &term->unpause_basic_setup); if (term->altscreen_active) { int_puts(integration, "\033[?1049h"); } // save/push sequences if (term->did_terminal_push_title) { int_puts(integration, "\033[22t"); } // other did_* sequences if (term->did_terminal_enable_mouse) { int_puts(integration, "\033[?1015h\033[?1006h"); } // the rest for (int i = 0; i < term->colors.allocated; i++) { termpaint_color_entry* item_it = (termpaint_color_entry*)term->colors.buckets[i]; while (item_it) { if (item_it->save_state == termpaint_save_state_ready) { if (item_it->requested.len) { int_puts(integration, "\033]"); int_uputs(integration, item_it->base.text); int_puts(integration, ";"); int_uputs(integration, item_it->requested.data); int_puts(integration, termpaintp_terminal_correct_string_terminator(term)); } else { int_uputs(integration, item_it->restore.data); } } item_it = (termpaint_color_entry*)item_it->base.next; } } for (int i = 0; i < term->unpause_snippets.allocated; i++) { termpaint_unpause_snippet* item_it = (termpaint_unpause_snippet*)term->unpause_snippets.buckets[i]; while (item_it) { int_put_tps(integration, &item_it->sequences); item_it = (termpaint_unpause_snippet*)item_it->base.next; } } int_flush(integration); } static termpaint_str* termpaintp_terminal_get_unpause_slot(termpaint_terminal *term, const char *name) { termpaint_unpause_snippet *snippet = termpaintp_hash_ensure(&term->unpause_snippets, (const unsigned char*)name); if (!snippet) { return nullptr; } return &(snippet)->sequences; } termpaint_attr *termpaint_attr_new_or_nullptr(unsigned fg, unsigned bg) { termpaint_attr *attr = calloc(1, sizeof(termpaint_attr)); if (!attr) { return nullptr; } attr->fg_color = fg; attr->bg_color = bg; attr->deco_color = TERMPAINT_DEFAULT_COLOR; return attr; } termpaint_attr *termpaint_attr_new(unsigned fg, unsigned bg) { termpaint_attr *attr = termpaint_attr_new_or_nullptr(fg, bg); if (!attr) { termpaintp_oom_nolog(); } return attr; } void termpaint_attr_free(termpaint_attr *attr) { if (!attr) { return; } free(attr->patch_setup); free(attr->patch_cleanup); free(attr); } termpaint_attr *termpaint_attr_clone_or_nullptr(const termpaint_attr *orig) { termpaint_attr *attr = calloc(1, sizeof(termpaint_attr)); if (!attr) { return nullptr; } attr->fg_color = orig->fg_color; attr->bg_color = orig->bg_color; attr->deco_color = orig->deco_color; attr->flags = orig->flags; if (orig->patch_setup) { attr->patch_setup = ustrdup(orig->patch_setup); if (!attr->patch_setup) { termpaint_attr_free(attr); return nullptr; } attr->patch_optimize = orig->patch_optimize; } if (orig->patch_cleanup) { attr->patch_cleanup = ustrdup(orig->patch_cleanup); if (!attr->patch_cleanup) { termpaint_attr_free(attr); return nullptr; } attr->patch_optimize = orig->patch_optimize; } return attr; } termpaint_attr *termpaint_attr_clone(const termpaint_attr *orig) { termpaint_attr *attr = termpaint_attr_clone_or_nullptr(orig); if (!attr) { termpaintp_oom_nolog(); } return attr; } void termpaint_attr_set_fg(termpaint_attr *attr, unsigned fg) { attr->fg_color = fg; } void termpaint_attr_set_bg(termpaint_attr *attr, unsigned bg) { attr->bg_color = bg; } void termpaint_attr_set_deco(termpaint_attr *attr, unsigned deco_color) { attr->deco_color = deco_color; } void termpaint_attr_set_style(termpaint_attr *attr, int bits) { attr->flags |= bits & TERMPAINT_STYLE_PASSTHROUGH; if (bits & ~TERMPAINT_STYLE_PASSTHROUGH) { if (bits & TERMPAINT_STYLE_UNDERLINE) { attr->flags = (attr->flags & ~CELL_ATTR_UNDERLINE_MASK) | CELL_ATTR_UNDERLINE_SINGLE; } else if (bits & TERMPAINT_STYLE_UNDERLINE_DBL) { attr->flags = (attr->flags & ~CELL_ATTR_UNDERLINE_MASK) | CELL_ATTR_UNDERLINE_DOUBLE; } else if (bits & TERMPAINT_STYLE_UNDERLINE_CURLY) { attr->flags = (attr->flags & ~CELL_ATTR_UNDERLINE_MASK) | CELL_ATTR_UNDERLINE_CURLY; } } } void termpaint_attr_unset_style(termpaint_attr *attr, int bits) { attr->flags &= ~bits | ~TERMPAINT_STYLE_PASSTHROUGH; if (bits & ~TERMPAINT_STYLE_PASSTHROUGH) { if (bits & TERMPAINT_STYLE_UNDERLINE) { attr->flags = attr->flags & ~CELL_ATTR_UNDERLINE_MASK; } else if (bits & TERMPAINT_STYLE_UNDERLINE_DBL) { attr->flags = attr->flags & ~CELL_ATTR_UNDERLINE_MASK; } else if (bits & TERMPAINT_STYLE_UNDERLINE_CURLY) { attr->flags = attr->flags & ~CELL_ATTR_UNDERLINE_MASK; } } } void termpaint_attr_reset_style(termpaint_attr *attr) { attr->flags = 0; } bool termpaint_attr_set_patch_mustcheck(termpaint_attr *attr, bool optimize, const char *setup, const char *cleanup) { free(attr->patch_setup); attr->patch_setup = nullptr; free(attr->patch_cleanup); attr->patch_cleanup = nullptr; if (!setup || !cleanup) { attr->patch_optimize = false; } else { attr->patch_optimize = optimize; attr->patch_setup = (uchar*)strdup(setup); if (!attr->patch_setup) { termpaint_attr_set_patch(attr, false, nullptr, nullptr); return false; } attr->patch_cleanup = (uchar*)strdup(cleanup); if (!attr->patch_cleanup) { termpaint_attr_set_patch(attr, false, nullptr, nullptr); return false; } } return true; } void termpaint_attr_set_patch(termpaint_attr *attr, bool optimize, const char *setup, const char *cleanup) { if (!termpaint_attr_set_patch_mustcheck(attr, optimize, setup, cleanup)) { termpaintp_oom_nolog(); } } termpaint_text_measurement *termpaint_text_measurement_new_or_nullptr(const termpaint_surface *surface) { // Make sure to fail early when a nullptr is passed, as this function only copies the pointer. if (!surface) { BUG("termpaint_text_measurement_new called without valid surface"); } termpaint_text_measurement *m = malloc(sizeof(termpaint_text_measurement)); if (!m) { return nullptr; } m->terminal = surface->terminal; termpaint_text_measurement_reset(m); return m; } termpaint_text_measurement *termpaint_text_measurement_new(const termpaint_surface *surface) { termpaint_text_measurement *m = termpaint_text_measurement_new_or_nullptr(surface); if (!m) { termpaintp_oom(surface->terminal); } return m; } void termpaint_text_measurement_free(termpaint_text_measurement *m) { if (!m) { return; } free(m); } void termpaint_text_measurement_reset(termpaint_text_measurement *m) { m->pending_codepoints = 0; m->pending_ref = 0; m->pending_clusters = 0; m->pending_width = 0; m->last_codepoints = -1; m->last_ref = -1; m->last_clusters = -1; m->last_width = -1; m->state = TM_INITIAL; m->limit_codepoints = -1; m->limit_ref = -1; m->limit_clusters = -1; m->limit_width = -1; m->decoder_state = TMD_INITIAL; } int termpaint_text_measurement_pending_ref(const termpaint_text_measurement *m) { int result = m->pending_ref; if (m->decoder_state == TMD_PARTIAL_UTF16) { result += 1; } else if (m->decoder_state == TMD_PARTIAL_UTF8) { result += m->utf8_available; } return result; } int termpaint_text_measurement_last_codepoints(const termpaint_text_measurement *m) { return m->last_codepoints; } int termpaint_text_measurement_last_clusters(const termpaint_text_measurement *m) { return m->last_clusters; } int termpaint_text_measurement_last_width(const termpaint_text_measurement *m) { return m->last_width; } int termpaint_text_measurement_last_ref(const termpaint_text_measurement *m) { return m->last_ref; } int termpaint_text_measurement_limit_codepoints(const termpaint_text_measurement *m) { return m->limit_codepoints; } void termpaint_text_measurement_set_limit_codepoints(termpaint_text_measurement *m, int new_value) { m->limit_codepoints = new_value; } int termpaint_text_measurement_limit_clusters(const termpaint_text_measurement *m) { return m->limit_clusters; } void termpaint_text_measurement_set_limit_clusters(termpaint_text_measurement *m, int new_value) { m->limit_clusters = new_value; } int termpaint_text_measurement_limit_width(const termpaint_text_measurement *m) { return m->limit_width; } void termpaint_text_measurement_set_limit_width(termpaint_text_measurement *m, int new_value) { m->limit_width = new_value; } int termpaint_text_measurement_limit_ref(const termpaint_text_measurement *m) { return m->limit_ref; } void termpaint_text_measurement_set_limit_ref(termpaint_text_measurement *m, int new_value) { m->limit_ref = new_value; } static inline void termpaintp_text_measurement_commit(termpaint_text_measurement *m) { m->last_codepoints = m->pending_codepoints; m->last_clusters = m->pending_clusters; m->last_width = m->pending_width; m->last_ref = m->pending_ref; } // -1 if no limit reached, 1 if some limit exceeded, 0 if no limit exceeded and some limit reached static int termpaintp_text_measurement_cmp_limits(termpaint_text_measurement *m) { int ret = -1; if (m->limit_codepoints >= 0) { if (m->pending_codepoints == m->limit_codepoints) { ret = 0; } else if (m->pending_codepoints > m->limit_codepoints) { return 1; } } if (m->limit_clusters >= 0) { if (m->pending_clusters == m->limit_clusters) { ret = 0; } else if (m->pending_clusters > m->limit_clusters) { return 1; } } if (m->limit_width >= 0) { if (m->pending_width == m->limit_width) { ret = 0; } else if (m->pending_width > m->limit_width) { return 1; } } if (m->limit_ref >= 0) { if (m->pending_ref == m->limit_ref) { ret = 0; } else if (m->pending_ref > m->limit_ref) { return 1; } } return ret; } static void termpaintp_text_measurement_undo(termpaint_text_measurement *m) { m->pending_codepoints = m->last_codepoints; m->pending_ref = m->last_ref; m->pending_clusters = m->last_clusters; m->pending_width = m->last_width; m->state = TM_IN_CLUSTER; m->decoder_state = TMD_INITIAL; } int termpaint_text_measurement_feed_codepoint(termpaint_text_measurement *m, int ch, int ref_adjust) { const termpaintp_width *char_width_table = m->terminal->char_width_table; // ATTENTION keep this in sync with actual write to surface int ch_sanitized = replace_unusable_codepoints(ch); int width = termpaintp_char_width(char_width_table, ch_sanitized); if (width == 0) { if (m->state == TM_INITIAL) { // Assume this will be input in this way into write which will supply it with U+00a0 as base. // We can ignore this non spaceing mark and just calculate for U+00a0 because what is needed is // * adding ref_adjust to ref (to account for the non spaceing mark) // * adding one codepoint to ref (also to account for the non spaceing mark) // * and incrementing the cluster and width just as U+00a0 would do. While we can ignore the non spacing // mark for cluster/width purposes. // This is exactly what happens on feeding U+00a0, so just do that instead of creating another implementation. return termpaint_text_measurement_feed_codepoint(m, 0xa0, ref_adjust); } ++m->pending_codepoints; m->pending_ref += ref_adjust; // accumulates into cluster. Nothing do do here. return 0; } else { int limit_rel = termpaintp_text_measurement_cmp_limits(m); if (limit_rel == 0) { // no limit exceeded, some limit hit exactly -> commit cluster and use that as best match termpaintp_text_measurement_commit(m); m->state = TM_IN_CLUSTER; return TERMPAINT_MEASURE_NEW_CLUSTER | TERMPAINT_MEASURE_LIMIT_REACHED; } else if (limit_rel < 0) { // no limit reached -> commit cluster and go on // advance last full cluster termpaintp_text_measurement_commit(m); m->state = TM_IN_CLUSTER; ++m->pending_codepoints; m->pending_ref += ref_adjust; m->pending_width += width; m->pending_clusters += 1; if (ch == '\x7f') { // clear marker does not allow any modifiers m->state = TM_INITIAL; } return TERMPAINT_MEASURE_NEW_CLUSTER; } else { // some limit exceeded -> return with previous cluster as best match termpaintp_text_measurement_undo(m); return TERMPAINT_MEASURE_LIMIT_REACHED; } } } _Bool termpaint_text_measurement_feed_utf32(termpaint_text_measurement *m, const uint32_t *chars, int length, _Bool final) { for (int i = 0; i < length; i++) { if (termpaint_text_measurement_feed_codepoint(m, (int)chars[i], 1) & TERMPAINT_MEASURE_LIMIT_REACHED) { return true; } } if (final) { int limit_rel = termpaintp_text_measurement_cmp_limits(m); if (limit_rel == 0) { // no limit exceeded, some limit hit exactly -> commit cluster and use that as best match termpaintp_text_measurement_commit(m); return true; } else if (limit_rel < 0) { // no limit reached -> commit cluster termpaintp_text_measurement_commit(m); return false; } else { // some limit exceeded -> return with previous cluster as best match termpaintp_text_measurement_undo(m); return true; } } return false; } _Bool termpaint_text_measurement_feed_utf16(termpaint_text_measurement *m, const uint16_t *code_units, int length, _Bool final) { if (m->decoder_state != TMD_INITIAL && m->decoder_state != TMD_PARTIAL_UTF16) { // This is bogus usage, but just paper over it m->decoder_state = TMD_INITIAL; } for (int i = 0; i < length; i++) { int ch = code_units[i]; int adjust = 1; if (termpaintp_utf16_is_high_surrogate(code_units[i])) { if (m->decoder_state != TMD_INITIAL) { // This is bogus usage, but just paper over it ch = 0xFFFD; } else { m->decoder_state = TMD_PARTIAL_UTF16; m->utf_16_high = code_units[i]; continue; } } if (termpaintp_utf16_is_low_surrogate(code_units[i])) { if (m->decoder_state != TMD_PARTIAL_UTF16) { // This is bogus usage, but just paper over it ch = 0xFFFD; } else { adjust = 2; ch = termpaintp_utf16_combine(m->utf_16_high, code_units[i]); } } m->decoder_state = TMD_INITIAL; if (termpaint_text_measurement_feed_codepoint(m, ch, adjust) & TERMPAINT_MEASURE_LIMIT_REACHED) { return true; } } if (final) { int limit_rel = termpaintp_text_measurement_cmp_limits(m); if (limit_rel == 0) { // no limit exceeded, some limit hit exactly -> commit cluster and use that as best match termpaintp_text_measurement_commit(m); return true; } else if (limit_rel < 0) { // no limit reached -> commit cluster termpaintp_text_measurement_commit(m); return false; } else { // some limit exceeded -> return with previous cluster as best match termpaintp_text_measurement_undo(m); return true; } } return false; } _Bool termpaint_text_measurement_feed_utf8(termpaint_text_measurement *m, const char *code_units, int length, _Bool final) { if (m->decoder_state != TMD_INITIAL && m->decoder_state != TMD_PARTIAL_UTF8) { // This is bogus usage, but just paper over it m->decoder_state = TMD_INITIAL; } for (int i = 0; i < length; i++) { int ch; int adjust = 1; if (m->decoder_state == TMD_INITIAL) { int len = termpaintp_utf8_len(code_units[i]); if (len == 1) { ch = code_units[i]; } else { m->decoder_state = TMD_PARTIAL_UTF8; m->utf8_size = len; m->utf8_units[0] = code_units[i]; m->utf8_available = 1; continue; } } else { m->utf8_units[m->utf8_available] = code_units[i]; m->utf8_available++; adjust = m->utf8_available; if (m->utf8_available == m->utf8_size) { if (termpaintp_check_valid_sequence(m->utf8_units, m->utf8_size)) { ch = termpaintp_utf8_decode_from_utf8(m->utf8_units, m->utf8_size); } else { // This is bogus usage, but just paper over it ch = 0xFFFD; } } else if (m->utf8_available > m->utf8_size) { // This is bogus usage, but just paper over it ch = 0xFFFD; } else { continue; } m->decoder_state = TMD_INITIAL; } if (termpaint_text_measurement_feed_codepoint(m, ch, adjust) & TERMPAINT_MEASURE_LIMIT_REACHED) { return true; } } if (final) { int limit_rel = termpaintp_text_measurement_cmp_limits(m); if (limit_rel == 0) { // no limit exceeded, some limit hit exactly -> commit cluster and use that as best match termpaintp_text_measurement_commit(m); return true; } else if (limit_rel < 0) { // no limit reached -> commit cluster termpaintp_text_measurement_commit(m); return false; } else { // some limit exceeded -> return with previous cluster as best match termpaintp_text_measurement_undo(m); return true; } } return false; } bool termpaint_terminal_set_title_mustcheck(termpaint_terminal *term, const char *title, int mode) { if (mode != TERMPAINT_TITLE_MODE_PREFER_RESTORE) { if (!termpaint_terminal_capable(term, TERMPAINT_CAPABILITY_TITLE_RESTORE)) { return true; } } termpaint_integration *integration = term->integration; if (!term->did_terminal_push_title) { termpaintp_prepend_str(&term->restore_seq_partial, (const uchar*)"\033[23t"); int_restore_sequence_complete(term); int_restore_sequence_updated(term); int_puts(integration, "\033[22t"); term->did_terminal_push_title = true; } termpaint_str* sequences = termpaintp_terminal_get_unpause_slot(term, "title"); if (!sequences) { return false; } TERMPAINT_STR_ASSIGN3_MUSTCHECK(sequences, S, "\033]2;", F, title, S, termpaintp_terminal_correct_string_terminator(term)); if (!sequences->len) { return false; } int_put_tps(integration, sequences); int_flush(integration); return true; } void termpaint_terminal_set_title(termpaint_terminal *term, const char *title, int mode) { if (!termpaint_terminal_set_title_mustcheck(term, title, mode)) { termpaintp_oom(term); } } bool termpaint_terminal_set_icon_title_mustcheck(termpaint_terminal *term, const char *title, int mode) { if (mode != TERMPAINT_TITLE_MODE_PREFER_RESTORE) { if (!termpaint_terminal_capable(term, TERMPAINT_CAPABILITY_TITLE_RESTORE)) { return true; } } termpaint_integration *integration = term->integration; if (!term->did_terminal_push_title) { termpaintp_prepend_str(&term->restore_seq_partial, (const uchar*)"\033[23t"); int_restore_sequence_complete(term); int_restore_sequence_updated(term); int_puts(integration, "\033[22t"); term->did_terminal_push_title = true; } termpaint_str* sequences = termpaintp_terminal_get_unpause_slot(term, "icon title"); if (!sequences) { return false; } TERMPAINT_STR_ASSIGN3_MUSTCHECK(sequences, S, "\033]1;", F, title, S, termpaintp_terminal_correct_string_terminator(term)); if (!sequences->len) { return false; } int_put_tps(integration, sequences); int_flush(integration); return true; } void termpaint_terminal_set_icon_title(termpaint_terminal *term, const char *title, int mode) { if (!termpaint_terminal_set_icon_title_mustcheck(term, title, mode)) { termpaintp_oom(term); } } void termpaint_terminal_bell(termpaint_terminal *term) { termpaint_integration *integration = term->integration; int_puts(integration, "\a"); int_flush(integration); } #define DISABLE_MOUSE_SEQUENCE "\033[?1003l\033[?1002l\033[?1000l\033[?1006l\033[?1015l" _Bool termpaint_terminal_set_mouse_mode_mustcheck(termpaint_terminal *term, int mouse_mode) { termpaint_integration *integration = term->integration; if (mouse_mode != TERMPAINT_MOUSE_MODE_OFF) { if (!term->did_terminal_add_mouse_to_restore) { termpaint_terminal_expect_legacy_mouse_reports(term, TERMPAINT_INPUT_EXPECT_LEGACY_MOUSE); termpaintp_prepend_str(&term->restore_seq_partial, (const uchar*)DISABLE_MOUSE_SEQUENCE); int_restore_sequence_complete(term); int_restore_sequence_updated(term); term->did_terminal_add_mouse_to_restore = true; } } else { if (term->did_terminal_enable_mouse) { term->did_terminal_enable_mouse = false; int_puts(integration, DISABLE_MOUSE_SEQUENCE); int_flush(integration); termpaint_str* sequences = termpaintp_terminal_get_unpause_slot(term, "mouse"); if (sequences) { if (!termpaintp_str_assign_mustcheck(sequences, "")) { return false; } } } return true; } termpaint_str* sequences = termpaintp_terminal_get_unpause_slot(term, "mouse"); if (!sequences) { return false; } if (mouse_mode == TERMPAINT_MOUSE_MODE_CLICKS) { if (!termpaintp_str_assign_mustcheck(sequences, "\033[?1002l\033[?1003l\033[?1000h")) { return false; } } else if (mouse_mode == TERMPAINT_MOUSE_MODE_DRAG) { if (!termpaintp_str_assign_mustcheck(sequences, "\033[?1003l\033[?1000h\033[?1002h")) { return false; } } else if (mouse_mode == TERMPAINT_MOUSE_MODE_MOVEMENT) { if (!termpaintp_str_assign_mustcheck(sequences, "\033[?1000h\033[?1002h\033[?1003h")) { return false; } } if (!term->did_terminal_enable_mouse) { term->did_terminal_enable_mouse = true; int_puts(integration, "\033[?1015h\033[?1006h"); } int_put_tps(integration, sequences); int_flush(integration); return true; } void termpaint_terminal_set_mouse_mode(termpaint_terminal *term, int mouse_mode) { if (!termpaint_terminal_set_mouse_mode_mustcheck(term, mouse_mode)) { termpaintp_oom(term); } } bool termpaint_terminal_request_focus_change_reports_mustcheck(termpaint_terminal *term, bool enabled) { if (enabled && !term->did_terminal_add_focusreporting_to_restore) { term->did_terminal_add_focusreporting_to_restore = true; termpaintp_prepend_str(&term->restore_seq_partial, (const uchar*)"\033[?1004l"); int_restore_sequence_complete(term); int_restore_sequence_updated(term); } termpaint_integration *integration = term->integration; termpaint_str* sequences = termpaintp_terminal_get_unpause_slot(term, "focus report"); if (!sequences) { return false; } if (enabled) { if (!termpaintp_str_assign_mustcheck(sequences, "\033[?1004h")) { return false; } } else { if (!termpaintp_str_assign_mustcheck(sequences, "\033[?1004l")) { return false; } } int_put_tps(integration, sequences); int_flush(integration); return true; } void termpaint_terminal_request_focus_change_reports(termpaint_terminal *term, bool enabled) { if (!termpaint_terminal_request_focus_change_reports_mustcheck(term, enabled)) { termpaintp_oom(term); } } bool termpaint_terminal_request_tagged_paste_mustcheck(termpaint_terminal *term, bool enabled) { if (enabled && !term->did_terminal_add_bracketedpaste_to_restore) { term->did_terminal_add_bracketedpaste_to_restore = true; termpaintp_prepend_str(&term->restore_seq_partial, (const uchar*)"\033[?2004l"); int_restore_sequence_complete(term); int_restore_sequence_updated(term); } termpaint_integration *integration = term->integration; termpaint_str* sequences = termpaintp_terminal_get_unpause_slot(term, "bracketed paste"); if (!sequences) { return false; } if (enabled) { if (!termpaintp_str_assign_mustcheck(sequences, "\033[?2004h")) { return false; } } else { if (!termpaintp_str_assign_mustcheck(sequences, "\033[?2004l")) { return false; } } int_put_tps(integration, sequences); int_flush(integration); return true; } void termpaint_terminal_request_tagged_paste(termpaint_terminal *term, bool enabled) { if (!termpaint_terminal_request_tagged_paste_mustcheck(term, enabled)) { termpaintp_oom(term); } } // --- tests static bool termpaintp_test_quantize_to_256(void) { termpaint_terminal terminal; terminal.cache_should_use_truecolor = false; terminal.capabilities[TERMPAINT_CAPABILITY_88_COLOR] = false; struct pal_entry { int nr; int r,g,b; }; const struct pal_entry palette[240] = { { 16, 0, 0, 0 }, { 17, 0, 0, 95 }, { 18, 0, 0, 135 }, { 19, 0, 0, 175 }, { 20, 0, 0, 215 }, { 21, 0, 0, 255 }, { 22, 0, 95, 0 }, { 23, 0, 95, 95 }, { 24, 0, 95, 135 }, { 25, 0, 95, 175 }, { 26, 0, 95, 215 }, { 27, 0, 95, 255 }, { 28, 0, 135, 0 }, { 29, 0, 135, 95 }, { 30, 0, 135, 135 }, { 31, 0, 135, 175 }, { 32, 0, 135, 215 }, { 33, 0, 135, 255 }, { 34, 0, 175, 0 }, { 35, 0, 175, 95 }, { 36, 0, 175, 135 }, { 37, 0, 175, 175 }, { 38, 0, 175, 215 }, { 39, 0, 175, 255 }, { 40, 0, 215, 0 }, { 41, 0, 215, 95 }, { 42, 0, 215, 135 }, { 43, 0, 215, 175 }, { 44, 0, 215, 215 }, { 45, 0, 215, 255 }, { 46, 0, 255, 0 }, { 47, 0, 255, 95 }, { 48, 0, 255, 135 }, { 49, 0, 255, 175 }, { 50, 0, 255, 215 }, { 51, 0, 255, 255 }, { 52, 95, 0, 0 }, { 53, 95, 0, 95 }, { 54, 95, 0, 135 }, { 55, 95, 0, 175 }, { 56, 95, 0, 215 }, { 57, 95, 0, 255 }, { 58, 95, 95, 0 }, { 59, 95, 95, 95 }, { 60, 95, 95, 135 }, { 61, 95, 95, 175 }, { 62, 95, 95, 215 }, { 63, 95, 95, 255 }, { 64, 95, 135, 0 }, { 65, 95, 135, 95 }, { 66, 95, 135, 135 }, { 67, 95, 135, 175 }, { 68, 95, 135, 215 }, { 69, 95, 135, 255 }, { 70, 95, 175, 0 }, { 71, 95, 175, 95 }, { 72, 95, 175, 135 }, { 73, 95, 175, 175 }, { 74, 95, 175, 215 }, { 75, 95, 175, 255 }, { 76, 95, 215, 0 }, { 77, 95, 215, 95 }, { 78, 95, 215, 135 }, { 79, 95, 215, 175 }, { 80, 95, 215, 215 }, { 81, 95, 215, 255 }, { 82, 95, 255, 0 }, { 83, 95, 255, 95 }, { 84, 95, 255, 135 }, { 85, 95, 255, 175 }, { 86, 95, 255, 215 }, { 87, 95, 255, 255 }, { 88, 135, 0, 0 }, { 89, 135, 0, 95 }, { 90, 135, 0, 135 }, { 91, 135, 0, 175 }, { 92, 135, 0, 215 }, { 93, 135, 0, 255 }, { 94, 135, 95, 0 }, { 95, 135, 95, 95 }, { 96, 135, 95, 135 }, { 97, 135, 95, 175 }, { 98, 135, 95, 215 }, { 99, 135, 95, 255 }, { 100, 135, 135, 0 }, { 101, 135, 135, 95 }, { 102, 135, 135, 135 }, { 103, 135, 135, 175 }, { 104, 135, 135, 215 }, { 105, 135, 135, 255 }, { 106, 135, 175, 0 }, { 107, 135, 175, 95 }, { 108, 135, 175, 135 }, { 109, 135, 175, 175 }, { 110, 135, 175, 215 }, { 111, 135, 175, 255 }, { 112, 135, 215, 0 }, { 113, 135, 215, 95 }, { 114, 135, 215, 135 }, { 115, 135, 215, 175 }, { 116, 135, 215, 215 }, { 117, 135, 215, 255 }, { 118, 135, 255, 0 }, { 119, 135, 255, 95 }, { 120, 135, 255, 135 }, { 121, 135, 255, 175 }, { 122, 135, 255, 215 }, { 123, 135, 255, 255 }, { 124, 175, 0, 0 }, { 125, 175, 0, 95 }, { 126, 175, 0, 135 }, { 127, 175, 0, 175 }, { 128, 175, 0, 215 }, { 129, 175, 0, 255 }, { 130, 175, 95, 0 }, { 131, 175, 95, 95 }, { 132, 175, 95, 135 }, { 133, 175, 95, 175 }, { 134, 175, 95, 215 }, { 135, 175, 95, 255 }, { 136, 175, 135, 0 }, { 137, 175, 135, 95 }, { 138, 175, 135, 135 }, { 139, 175, 135, 175 }, { 140, 175, 135, 215 }, { 141, 175, 135, 255 }, { 142, 175, 175, 0 }, { 143, 175, 175, 95 }, { 144, 175, 175, 135 }, { 145, 175, 175, 175 }, { 146, 175, 175, 215 }, { 147, 175, 175, 255 }, { 148, 175, 215, 0 }, { 149, 175, 215, 95 }, { 150, 175, 215, 135 }, { 151, 175, 215, 175 }, { 152, 175, 215, 215 }, { 153, 175, 215, 255 }, { 154, 175, 255, 0 }, { 155, 175, 255, 95 }, { 156, 175, 255, 135 }, { 157, 175, 255, 175 }, { 158, 175, 255, 215 }, { 159, 175, 255, 255 }, { 160, 215, 0, 0 }, { 161, 215, 0, 95 }, { 162, 215, 0, 135 }, { 163, 215, 0, 175 }, { 164, 215, 0, 215 }, { 165, 215, 0, 255 }, { 166, 215, 95, 0 }, { 167, 215, 95, 95 }, { 168, 215, 95, 135 }, { 169, 215, 95, 175 }, { 170, 215, 95, 215 }, { 171, 215, 95, 255 }, { 172, 215, 135, 0 }, { 173, 215, 135, 95 }, { 174, 215, 135, 135 }, { 175, 215, 135, 175 }, { 176, 215, 135, 215 }, { 177, 215, 135, 255 }, { 178, 215, 175, 0 }, { 179, 215, 175, 95 }, { 180, 215, 175, 135 }, { 181, 215, 175, 175 }, { 182, 215, 175, 215 }, { 183, 215, 175, 255 }, { 184, 215, 215, 0 }, { 185, 215, 215, 95 }, { 186, 215, 215, 135 }, { 187, 215, 215, 175 }, { 188, 215, 215, 215 }, { 189, 215, 215, 255 }, { 190, 215, 255, 0 }, { 191, 215, 255, 95 }, { 192, 215, 255, 135 }, { 193, 215, 255, 175 }, { 194, 215, 255, 215 }, { 195, 215, 255, 255 }, { 196, 255, 0, 0 }, { 197, 255, 0, 95 }, { 198, 255, 0, 135 }, { 199, 255, 0, 175 }, { 200, 255, 0, 215 }, { 201, 255, 0, 255 }, { 202, 255, 95, 0 }, { 203, 255, 95, 95 }, { 204, 255, 95, 135 }, { 205, 255, 95, 175 }, { 206, 255, 95, 215 }, { 207, 255, 95, 255 }, { 208, 255, 135, 0 }, { 209, 255, 135, 95 }, { 210, 255, 135, 135 }, { 211, 255, 135, 175 }, { 212, 255, 135, 215 }, { 213, 255, 135, 255 }, { 214, 255, 175, 0 }, { 215, 255, 175, 95 }, { 216, 255, 175, 135 }, { 217, 255, 175, 175 }, { 218, 255, 175, 215 }, { 219, 255, 175, 255 }, { 220, 255, 215, 0 }, { 221, 255, 215, 95 }, { 222, 255, 215, 135 }, { 223, 255, 215, 175 }, { 224, 255, 215, 215 }, { 225, 255, 215, 255 }, { 226, 255, 255, 0 }, { 227, 255, 255, 95 }, { 228, 255, 255, 135 }, { 229, 255, 255, 175 }, { 230, 255, 255, 215 }, { 231, 255, 255, 255 }, { 232, 8, 8, 8 }, { 233, 18, 18, 18 }, { 234, 28, 28, 28 }, { 235, 38, 38, 38 }, { 236, 48, 48, 48 }, { 237, 58, 58, 58 }, { 238, 68, 68, 68 }, { 239, 78, 78, 78 }, { 240, 88, 88, 88 }, { 241, 98, 98, 98 }, { 242, 108, 108, 108 }, { 243, 118, 118, 118 }, { 244, 128, 128, 128 }, { 245, 138, 138, 138 }, { 246, 148, 148, 148 }, { 247, 158, 158, 158 }, { 248, 168, 168, 168 }, { 249, 178, 178, 178 }, { 250, 188, 188, 188 }, { 251, 198, 198, 198 }, { 252, 208, 208, 208 }, { 253, 218, 218, 218 }, { 254, 228, 228, 228 }, { 255, 238, 238, 238 }, }; for (int r = 0; r < 256; r++) for (int g = 0; g < 256; g++) for (int b = 0; b < 256; b++) { int best_metric = 0x7fffffff; unsigned candidates[10] = { 0 }; int candidates_count = 0; for (int i = 0; i < 240; i++) { #define SQ(x) ((x) * (x)) const int cur_metric = SQ(palette[i].r - r) + SQ(palette[i].g - g) + SQ(palette[i].b - b); #undef SQ if (cur_metric < best_metric) { candidates_count = 0; candidates[candidates_count++] = TERMPAINT_INDEXED_COLOR + palette[i].nr; best_metric = cur_metric; } else if (cur_metric == best_metric) { candidates[candidates_count++] = TERMPAINT_INDEXED_COLOR + palette[i].nr; } } if (candidates_count == 9) { return false; } unsigned res = termpaintp_quantize_color(&terminal, TERMPAINT_RGB_COLOR(r, g, b)); bool ok = false; for (int i = 0; i < candidates_count; i++) { if (candidates[i] == res) { ok = true; break; } } if (!ok) { return false; } } return true; } static bool termpaintp_test_quantize_to_88(void) { termpaint_terminal terminal; terminal.cache_should_use_truecolor = false; terminal.capabilities[TERMPAINT_CAPABILITY_88_COLOR] = true; struct pal_entry { int nr; int r,g,b; }; const struct pal_entry palette[72] = { { 16, 0x00, 0x00, 0x00 }, { 17, 0x00, 0x00, 0x8b }, { 18, 0x00, 0x00, 0xcd }, { 19, 0x00, 0x00, 0xff }, { 20, 0x00, 0x8b, 0x00 }, { 21, 0x00, 0x8b, 0x8b }, { 22, 0x00, 0x8b, 0xcd }, { 23, 0x00, 0x8b, 0xff }, { 24, 0x00, 0xcd, 0x00 }, { 25, 0x00, 0xcd, 0x8b }, { 26, 0x00, 0xcd, 0xcd }, { 27, 0x00, 0xcd, 0xff }, { 28, 0x00, 0xff, 0x00 }, { 29, 0x00, 0xff, 0x8b }, { 30, 0x00, 0xff, 0xcd }, { 31, 0x00, 0xff, 0xff }, { 32, 0x8b, 0x00, 0x00 }, { 33, 0x8b, 0x00, 0x8b }, { 34, 0x8b, 0x00, 0xcd }, { 35, 0x8b, 0x00, 0xff }, { 36, 0x8b, 0x8b, 0x00 }, { 37, 0x8b, 0x8b, 0x8b }, { 38, 0x8b, 0x8b, 0xcd }, { 39, 0x8b, 0x8b, 0xff }, { 40, 0x8b, 0xcd, 0x00 }, { 41, 0x8b, 0xcd, 0x8b }, { 42, 0x8b, 0xcd, 0xcd }, { 43, 0x8b, 0xcd, 0xff }, { 44, 0x8b, 0xff, 0x00 }, { 45, 0x8b, 0xff, 0x8b }, { 46, 0x8b, 0xff, 0xcd }, { 47, 0x8b, 0xff, 0xff }, { 48, 0xcd, 0x00, 0x00 }, { 49, 0xcd, 0x00, 0x8b }, { 50, 0xcd, 0x00, 0xcd }, { 51, 0xcd, 0x00, 0xff }, { 52, 0xcd, 0x8b, 0x00 }, { 53, 0xcd, 0x8b, 0x8b }, { 54, 0xcd, 0x8b, 0xcd }, { 55, 0xcd, 0x8b, 0xff }, { 56, 0xcd, 0xcd, 0x00 }, { 57, 0xcd, 0xcd, 0x8b }, { 58, 0xcd, 0xcd, 0xcd }, { 59, 0xcd, 0xcd, 0xff }, { 60, 0xcd, 0xff, 0x00 }, { 61, 0xcd, 0xff, 0x8b }, { 62, 0xcd, 0xff, 0xcd }, { 63, 0xcd, 0xff, 0xff }, { 64, 0xff, 0x00, 0x00 }, { 65, 0xff, 0x00, 0x8b }, { 66, 0xff, 0x00, 0xcd }, { 67, 0xff, 0x00, 0xff }, { 68, 0xff, 0x8b, 0x00 }, { 69, 0xff, 0x8b, 0x8b }, { 70, 0xff, 0x8b, 0xcd }, { 71, 0xff, 0x8b, 0xff }, { 72, 0xff, 0xcd, 0x00 }, { 73, 0xff, 0xcd, 0x8b }, { 74, 0xff, 0xcd, 0xcd }, { 75, 0xff, 0xcd, 0xff }, { 76, 0xff, 0xff, 0x00 }, { 77, 0xff, 0xff, 0x8b }, { 78, 0xff, 0xff, 0xcd }, { 79, 0xff, 0xff, 0xff }, { 80, 0x2e, 0x2e, 0x2e }, { 81, 0x5c, 0x5c, 0x5c }, { 82, 0x73, 0x73, 0x73 }, { 83, 0x8b, 0x8b, 0x8b }, { 84, 0xa2, 0xa2, 0xa2 }, { 85, 0xb9, 0xb9, 0xb9 }, { 86, 0xd0, 0xd0, 0xd0 }, { 87, 0xe7, 0xe7, 0xe7 }, }; for (int r = 0; r < 256; r++) for (int g = 0; g < 256; g++) for (int b = 0; b < 256; b++) { int best_metric = 0x7fffffff; unsigned candidates[10] = { 0 }; int candidates_count = 0; for (int i = 0; i < 72; i++) { #define SQ(x) ((x) * (x)) const int cur_metric = SQ(palette[i].r - r) + SQ(palette[i].g - g) + SQ(palette[i].b - b); #undef SQ if (cur_metric < best_metric) { candidates_count = 0; candidates[candidates_count++] = TERMPAINT_INDEXED_COLOR + palette[i].nr; best_metric = cur_metric; } else if (cur_metric == best_metric) { candidates[candidates_count++] = TERMPAINT_INDEXED_COLOR + palette[i].nr; } } if (candidates_count == 9) { return false; } unsigned res = termpaintp_quantize_color(&terminal, TERMPAINT_RGB_COLOR(r, g, b)); bool ok = false; for (int i = 0; i < candidates_count; i++) { if (candidates[i] == res) { ok = true; break; } } if (!ok) { return false; } } return true; } static bool termpaintp_test_parse_version(void) { bool ret = true; ret &= (termpaintp_parse_version("0.5.0") == 5000); ret &= (termpaintp_parse_version("0.5.1") == 5001); ret &= (termpaintp_parse_version("1") == 1000000); ret &= (termpaintp_parse_version("1.0") == 1000000); ret &= (termpaintp_parse_version("1.0.0") == 1000000); ret &= (termpaintp_parse_version("1.7") == 1007000); ret &= (termpaintp_parse_version("1.7.0") == 1007000); ret &= (termpaintp_parse_version("1.7.0a") == 1007000); ret &= (termpaintp_parse_version("1.7a.0") == 1007000); ret &= (termpaintp_parse_version("1.7.0.1") == 1007000); ret &= (termpaintp_parse_version("1.7.1") == 1007001); ret &= (termpaintp_parse_version("1.7.1a") == 1007001); ret &= (termpaintp_parse_version("1.7a.1") == 1007000); return ret; } // this in internal don't link to this externally _tERMPAINT_PUBLIC bool termpaintp_test(void) { bool ret = true; ret &= termpaintp_test_quantize_to_256(); ret &= termpaintp_test_quantize_to_88(); ret &= termpaintp_test_parse_version(); ret &= termpaintp_mem_ascii_case_insensitive_equals("A", "a", 1); ret &= !termpaintp_mem_ascii_case_insensitive_equals("[", "{", 1); return ret; } termpaint-0.3.1/termpaint.h000066400000000000000000000515321477303547200157140ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef TERMPAINT_TERMPAINT_INCLUDED #define TERMPAINT_TERMPAINT_INCLUDED #include #include #ifdef __cplusplus #ifndef _Bool #define _Bool bool #endif #endif #ifdef __cplusplus extern "C" { #define TERMPAINTP_CAST(TYPE, EXPR) static_cast(EXPR) #else #define TERMPAINTP_CAST(TYPE, EXPR) (TYPE)(EXPR) #endif struct termpaint_attr_; typedef struct termpaint_attr_ termpaint_attr; struct termpaint_text_measurement_; typedef struct termpaint_text_measurement_ termpaint_text_measurement; struct termpaint_surface_; typedef struct termpaint_surface_ termpaint_surface; struct termpaint_terminal_; typedef struct termpaint_terminal_ termpaint_terminal; struct termpaint_integration_private_; typedef struct termpaint_integration_private_ termpaint_integration_private; typedef struct termpaint_integration_ { termpaint_integration_private* p; } termpaint_integration; _tERMPAINT_PUBLIC void termpaint_integration_init(termpaint_integration *integration, void (*free)(termpaint_integration *integration), void (*write)(termpaint_integration *integration, const char *data, int length), void (*flush)(termpaint_integration *integration)); _tERMPAINT_PUBLIC _Bool termpaint_integration_init_mustcheck(termpaint_integration *integration, void (*free)(termpaint_integration *integration), void (*write)(termpaint_integration *integration, const char *data, int length), void (*flush)(termpaint_integration *integration)); _tERMPAINT_PUBLIC void termpaint_integration_deinit(termpaint_integration *integration); _tERMPAINT_PUBLIC void termpaint_integration_set_is_bad(termpaint_integration *integration, _Bool (*is_bad)(termpaint_integration *integration)); _tERMPAINT_PUBLIC void termpaint_integration_set_request_callback(termpaint_integration *integration, void (*request_callback)(termpaint_integration *integration)); _tERMPAINT_PUBLIC void termpaint_integration_set_awaiting_response(termpaint_integration *integration, void (*awaiting_response)(termpaint_integration *integration)); _tERMPAINT_PUBLIC void termpaint_integration_set_restore_sequence_updated(termpaint_integration *integration, void (*restore_sequence_updated)(termpaint_integration *integration, const char *data, int length)); _tERMPAINT_PUBLIC void termpaint_integration_set_logging_func(termpaint_integration *integration, void (*logging_func)(termpaint_integration *integration, const char *data, int length)); // getters go here if need arises _tERMPAINT_PUBLIC termpaint_terminal *termpaint_terminal_new(termpaint_integration *integration); _tERMPAINT_PUBLIC termpaint_terminal *termpaint_terminal_new_or_nullptr(termpaint_integration *integration); _tERMPAINT_PUBLIC void termpaint_terminal_free(termpaint_terminal *term); _tERMPAINT_PUBLIC void termpaint_terminal_free_with_restore(termpaint_terminal *term); _tERMPAINT_PUBLIC void termpaint_terminal_free_with_restore_and_persistent(termpaint_terminal *term, termpaint_surface *surface); _tERMPAINT_PUBLIC termpaint_surface *termpaint_terminal_get_surface(termpaint_terminal *term); _tERMPAINT_PUBLIC void termpaint_terminal_flush(termpaint_terminal *term, _Bool full_repaint); _tERMPAINT_PUBLIC const char *termpaint_terminal_restore_sequence(const termpaint_terminal *term); _tERMPAINT_PUBLIC void termpaint_terminal_set_cursor_position(termpaint_terminal *term, int x, int y); _tERMPAINT_PUBLIC void termpaint_terminal_set_cursor_visible(termpaint_terminal *term, _Bool visible); #define TERMPAINT_CURSOR_STYLE_TERM_DEFAULT 0 #define TERMPAINT_CURSOR_STYLE_BLOCK 1 #define TERMPAINT_CURSOR_STYLE_UNDERLINE 3 #define TERMPAINT_CURSOR_STYLE_BAR 5 _tERMPAINT_PUBLIC void termpaint_terminal_set_cursor_style(termpaint_terminal *term, int style, _Bool blink); #define TERMPAINT_COLOR_SLOT_FOREGRUND 10 #define TERMPAINT_COLOR_SLOT_BACKGROUND 11 #define TERMPAINT_COLOR_SLOT_CURSOR 12 _tERMPAINT_PUBLIC _Bool termpaint_terminal_set_color_mustcheck(termpaint_terminal *term, int color_slot, int r, int g, int b); _tERMPAINT_PUBLIC void termpaint_terminal_set_color(termpaint_terminal *term, int color_slot, int r, int g, int b); _tERMPAINT_PUBLIC void termpaint_terminal_reset_color(termpaint_terminal *term, int color_slot); #define TERMPAINT_TITLE_MODE_ENSURE_RESTORE 0 #define TERMPAINT_TITLE_MODE_PREFER_RESTORE 1 _tERMPAINT_PUBLIC void termpaint_terminal_set_title(termpaint_terminal *term, const char* title, int mode); _tERMPAINT_PUBLIC _Bool termpaint_terminal_set_title_mustcheck(termpaint_terminal *term, const char* title, int mode); _tERMPAINT_PUBLIC void termpaint_terminal_set_icon_title(termpaint_terminal *term, const char* title, int mode); _tERMPAINT_PUBLIC _Bool termpaint_terminal_set_icon_title_mustcheck(termpaint_terminal *term, const char* title, int mode); _tERMPAINT_PUBLIC void termpaint_terminal_bell(termpaint_terminal *term); #define TERMPAINT_MOUSE_MODE_OFF 0 #define TERMPAINT_MOUSE_MODE_CLICKS 1 #define TERMPAINT_MOUSE_MODE_DRAG 2 #define TERMPAINT_MOUSE_MODE_MOVEMENT 3 _tERMPAINT_PUBLIC void termpaint_terminal_set_mouse_mode(termpaint_terminal *term, int mouse_mode); _tERMPAINT_PUBLIC _Bool termpaint_terminal_set_mouse_mode_mustcheck(termpaint_terminal *term, int mouse_mode); _tERMPAINT_PUBLIC void termpaint_terminal_request_focus_change_reports(termpaint_terminal *term, _Bool enabled); _tERMPAINT_PUBLIC _Bool termpaint_terminal_request_focus_change_reports_mustcheck(termpaint_terminal *term, _Bool enabled); _tERMPAINT_PUBLIC void termpaint_terminal_request_tagged_paste(termpaint_terminal *term, _Bool enabled); _tERMPAINT_PUBLIC _Bool termpaint_terminal_request_tagged_paste_mustcheck(termpaint_terminal *term, _Bool enabled); _tERMPAINT_PUBLIC void termpaint_terminal_callback(termpaint_terminal *term); _tERMPAINT_PUBLIC void termpaint_terminal_set_raw_input_filter_cb(termpaint_terminal *term, _Bool (*cb)(void *user_data, const char *data, unsigned length, _Bool overflow), void *user_data); _tERMPAINT_PUBLIC void termpaint_terminal_set_event_cb(termpaint_terminal *term, void (*cb)(void *user_data, termpaint_event* event), void *user_data); _tERMPAINT_PUBLIC void termpaint_terminal_add_input_data(termpaint_terminal *term, const char *data, unsigned length); _tERMPAINT_PUBLIC const char* termpaint_terminal_peek_input_buffer(const termpaint_terminal *term); _tERMPAINT_PUBLIC int termpaint_terminal_peek_input_buffer_length(const termpaint_terminal *term); // wrapped input option/state setters _tERMPAINT_PUBLIC void termpaint_terminal_expect_cursor_position_report(termpaint_terminal *term); _tERMPAINT_PUBLIC void termpaint_terminal_expect_legacy_mouse_reports(termpaint_terminal *term, int s); _tERMPAINT_PUBLIC void termpaint_terminal_handle_paste(termpaint_terminal *term, _Bool enabled); _tERMPAINT_PUBLIC void termpaint_terminal_expect_apc_input_sequences(termpaint_terminal *term, _Bool enabled); _tERMPAINT_PUBLIC void termpaint_terminal_activate_input_quirk(termpaint_terminal *term, int quirk); _tERMPAINT_PUBLIC _Bool termpaint_terminal_auto_detect(termpaint_terminal *terminal); enum termpaint_auto_detect_state_enum { termpaint_auto_detect_none, termpaint_auto_detect_running, termpaint_auto_detect_done }; _tERMPAINT_PUBLIC enum termpaint_auto_detect_state_enum termpaint_terminal_auto_detect_state(const termpaint_terminal *terminal); _tERMPAINT_PUBLIC _Bool termpaint_terminal_might_be_supported(const termpaint_terminal *terminal); _tERMPAINT_PUBLIC void termpaint_terminal_auto_detect_apply_input_quirks(termpaint_terminal *terminal, _Bool backspace_is_x08); _tERMPAINT_PUBLIC void termpaint_terminal_auto_detect_result_text(const termpaint_terminal *terminal, char *buffer, int buffer_length); _tERMPAINT_PUBLIC const char *termpaint_terminal_self_reported_name_and_version(const termpaint_terminal *terminal); _tERMPAINT_PUBLIC void termpaint_terminal_setup_fullscreen(termpaint_terminal *terminal, int width, int height, const char *options); _tERMPAINT_PUBLIC void termpaint_terminal_setup_inline(termpaint_terminal *terminal, int width, int height, const char *options); _tERMPAINT_PUBLIC void termpaint_terminal_set_inline(termpaint_terminal *terminal, _Bool enabled); #define TERMPAINT_CAPABILITY_SAFE_POSITION_REPORT 0 #define TERMPAINT_CAPABILITY_CSI_GREATER 1 #define TERMPAINT_CAPABILITY_CSI_EQUALS 2 #define TERMPAINT_CAPABILITY_CSI_POSTFIX_MOD 3 #define TERMPAINT_CAPABILITY_TITLE_RESTORE 4 #define TERMPAINT_CAPABILITY_MAY_TRY_CURSOR_SHAPE_BAR 5 #define TERMPAINT_CAPABILITY_CURSOR_SHAPE_OSC50 6 #define TERMPAINT_CAPABILITY_EXTENDED_CHARSET 7 #define TERMPAINT_CAPABILITY_TRUECOLOR_MAYBE_SUPPORTED 8 #define TERMPAINT_CAPABILITY_TRUECOLOR_SUPPORTED 9 #define TERMPAINT_CAPABILITY_88_COLOR 10 #define TERMPAINT_CAPABILITY_CLEARED_COLORING 11 #define TERMPAINT_CAPABILITY_7BIT_ST 12 #define TERMPAINT_CAPABILITY_MAY_TRY_CURSOR_SHAPE 13 #define TERMPAINT_CAPABILITY_MAY_TRY_TAGGED_PASTE 14 #define TERMPAINT_CAPABILITY_CLEARED_COLORING_DEFCOLOR 15 _tERMPAINT_PUBLIC _Bool termpaint_terminal_capable(const termpaint_terminal *terminal, int capability); _tERMPAINT_PUBLIC void termpaint_terminal_promise_capability(termpaint_terminal *terminal, int capability); _tERMPAINT_PUBLIC void termpaint_terminal_disable_capability(termpaint_terminal *terminal, int capability); _tERMPAINT_PUBLIC _Bool termpaint_terminal_should_use_truecolor(termpaint_terminal *terminal); _tERMPAINT_PUBLIC void termpaint_terminal_pause(termpaint_terminal *term); _tERMPAINT_PUBLIC void termpaint_terminal_pause_and_persistent(termpaint_terminal *term, termpaint_surface *surface); _tERMPAINT_PUBLIC void termpaint_terminal_unpause(termpaint_terminal *term); #define TERMPAINT_LOG_AUTO_DETECT_TRACE (1 << 0) #define TERMPAINT_LOG_TRACE_RAW_INPUT (1 << 1) _tERMPAINT_PUBLIC void termpaint_terminal_set_log_mask(termpaint_terminal *term, unsigned mask); _tERMPAINT_PUBLIC void termpaint_terminal_glitch_on_out_of_memory(termpaint_terminal *term); _tERMPAINT_PUBLIC termpaint_attr* termpaint_attr_new(unsigned fg, unsigned bg); _tERMPAINT_PUBLIC termpaint_attr* termpaint_attr_new_or_nullptr(unsigned fg, unsigned bg); _tERMPAINT_PUBLIC termpaint_attr* termpaint_attr_clone(const termpaint_attr* attr); _tERMPAINT_PUBLIC termpaint_attr* termpaint_attr_clone_or_nullptr(const termpaint_attr* attr); _tERMPAINT_PUBLIC void termpaint_attr_free(termpaint_attr* attr); _tERMPAINT_PUBLIC void termpaint_attr_set_fg(termpaint_attr* attr, unsigned fg); _tERMPAINT_PUBLIC void termpaint_attr_set_bg(termpaint_attr* attr, unsigned bg); _tERMPAINT_PUBLIC void termpaint_attr_set_deco(termpaint_attr* attr, unsigned deco_color); _tERMPAINT_PUBLIC void termpaint_attr_set_patch(termpaint_attr* attr, _Bool optimize, const char *setup, const char * cleanup); _tERMPAINT_PUBLIC _Bool termpaint_attr_set_patch_mustcheck(termpaint_attr* attr, _Bool optimize, const char *setup, const char * cleanup); #define TERMPAINT_DEFAULT_COLOR 0x0000000 #define TERMPAINT_NAMED_COLOR 0x2100000 #define TERMPAINT_COLOR_BLACK TERMPAINT_NAMED_COLOR + 0 #define TERMPAINT_COLOR_RED TERMPAINT_NAMED_COLOR + 1 #define TERMPAINT_COLOR_GREEN TERMPAINT_NAMED_COLOR + 2 #define TERMPAINT_COLOR_YELLOW TERMPAINT_NAMED_COLOR + 3 #define TERMPAINT_COLOR_BLUE TERMPAINT_NAMED_COLOR + 4 #define TERMPAINT_COLOR_MAGENTA TERMPAINT_NAMED_COLOR + 5 #define TERMPAINT_COLOR_CYAN TERMPAINT_NAMED_COLOR + 6 #define TERMPAINT_COLOR_LIGHT_GREY TERMPAINT_NAMED_COLOR + 7 #define TERMPAINT_COLOR_DARK_GREY TERMPAINT_NAMED_COLOR + 8 #define TERMPAINT_COLOR_BRIGHT_RED TERMPAINT_NAMED_COLOR + 9 #define TERMPAINT_COLOR_BRIGHT_GREEN TERMPAINT_NAMED_COLOR + 10 #define TERMPAINT_COLOR_BRIGHT_YELLOW TERMPAINT_NAMED_COLOR + 11 #define TERMPAINT_COLOR_BRIGHT_BLUE TERMPAINT_NAMED_COLOR + 12 #define TERMPAINT_COLOR_BRIGHT_MAGENTA TERMPAINT_NAMED_COLOR + 13 #define TERMPAINT_COLOR_BRIGHT_CYAN TERMPAINT_NAMED_COLOR + 14 #define TERMPAINT_COLOR_WHITE TERMPAINT_NAMED_COLOR + 15 #define TERMPAINT_INDEXED_COLOR 0x2200000 #define TERMPAINT_RGB_COLOR_OFFSET 0x1000000 #define TERMPAINT_RGB_COLOR(r, g, b) (TERMPAINT_RGB_COLOR_OFFSET | (TERMPAINTP_CAST(unsigned,r) << 16) | (TERMPAINTP_CAST(unsigned, g) << 8) | TERMPAINTP_CAST(unsigned, b)) #define TERMPAINT_STYLE_BOLD (1<<0) #define TERMPAINT_STYLE_ITALIC (1<<1) #define TERMPAINT_STYLE_BLINK (1<<4) #define TERMPAINT_STYLE_OVERLINE (1<<5) #define TERMPAINT_STYLE_INVERSE (1<<6) #define TERMPAINT_STYLE_STRIKE (1<<7) #define TERMPAINT_STYLE_UNDERLINE (1<<16) #define TERMPAINT_STYLE_UNDERLINE_DBL (1<<17) #define TERMPAINT_STYLE_UNDERLINE_CURLY (1<<18) _tERMPAINT_PUBLIC void termpaint_attr_set_style(termpaint_attr* attr, int bits); _tERMPAINT_PUBLIC void termpaint_attr_unset_style(termpaint_attr* attr, int bits); _tERMPAINT_PUBLIC void termpaint_attr_reset_style(termpaint_attr* attr); #define TERMPAINT_ERASED "\x7f" _tERMPAINT_PUBLIC termpaint_surface *termpaint_terminal_new_surface(termpaint_terminal *term, int width, int height); _tERMPAINT_PUBLIC termpaint_surface *termpaint_terminal_new_surface_or_nullptr(termpaint_terminal *term, int width, int height); _tERMPAINT_PUBLIC termpaint_surface *termpaint_surface_new_surface(termpaint_surface *surface, int width, int height); _tERMPAINT_PUBLIC termpaint_surface *termpaint_surface_new_surface_or_nullptr(termpaint_surface *surface, int width, int height); _tERMPAINT_PUBLIC termpaint_surface *termpaint_surface_duplicate(termpaint_surface *surface); _tERMPAINT_PUBLIC void termpaint_surface_free(termpaint_surface *surface); _tERMPAINT_PUBLIC void termpaint_surface_resize(termpaint_surface *surface, int width, int height); _tERMPAINT_PUBLIC _Bool termpaint_surface_resize_mustcheck(termpaint_surface *surface, int width, int height); _tERMPAINT_PUBLIC int termpaint_surface_width(const termpaint_surface *surface); _tERMPAINT_PUBLIC int termpaint_surface_height(const termpaint_surface *surface); _tERMPAINT_PUBLIC int termpaint_surface_char_width(const termpaint_surface *surface, int codepoint); _tERMPAINT_PUBLIC void termpaint_surface_write_with_colors(termpaint_surface *surface, int x, int y, const char *string, int fg, int bg); _tERMPAINT_PUBLIC void termpaint_surface_write_with_len_colors(termpaint_surface *surface, int x, int y, const char *string, int len, int fg, int bg); _tERMPAINT_PUBLIC void termpaint_surface_write_with_colors_clipped(termpaint_surface *surface, int x, int y, const char *string, int fg, int bg, int clip_x0, int clip_x1); _tERMPAINT_PUBLIC void termpaint_surface_write_with_len_colors_clipped(termpaint_surface *surface, int x, int y, const char *string, int len, int fg, int bg, int clip_x0, int clip_x1); _tERMPAINT_PUBLIC void termpaint_surface_write_with_attr(termpaint_surface *surface, int x, int y, const char *string, const termpaint_attr *attr); _tERMPAINT_PUBLIC void termpaint_surface_write_with_len_attr(termpaint_surface *surface, int x, int y, const char *string, int len, const termpaint_attr *attr); _tERMPAINT_PUBLIC void termpaint_surface_write_with_attr_clipped(termpaint_surface *surface, int x, int y, const char *string, const termpaint_attr *attr, int clip_x0, int clip_x1); _tERMPAINT_PUBLIC void termpaint_surface_write_with_len_attr_clipped(termpaint_surface *surface, int x, int y, const char *string, int len, const termpaint_attr *attr, int clip_x0, int clip_x1); _tERMPAINT_PUBLIC void termpaint_surface_clear(termpaint_surface *surface, int fg, int bg); _tERMPAINT_PUBLIC void termpaint_surface_clear_with_char(termpaint_surface *surface, int fg, int bg, int codepoint); _tERMPAINT_PUBLIC void termpaint_surface_clear_with_attr(termpaint_surface *surface, const termpaint_attr *attr); _tERMPAINT_PUBLIC void termpaint_surface_clear_with_attr_char(termpaint_surface *surface, const termpaint_attr *attr, int codepoint); _tERMPAINT_PUBLIC void termpaint_surface_clear_rect(termpaint_surface *surface, int x, int y, int width, int height, int fg, int bg); _tERMPAINT_PUBLIC void termpaint_surface_clear_rect_with_char(termpaint_surface *surface, int x, int y, int width, int height, int fg, int bg, int codepoint); _tERMPAINT_PUBLIC void termpaint_surface_clear_rect_with_attr(termpaint_surface *surface, int x, int y, int width, int height, const termpaint_attr *attr); _tERMPAINT_PUBLIC void termpaint_surface_clear_rect_with_attr_char(termpaint_surface *surface, int x, int y, int width, int height, const termpaint_attr *attr, int codepoint); _tERMPAINT_PUBLIC void termpaint_surface_set_fg_color(const termpaint_surface *surface, int x, int y, unsigned fg); _tERMPAINT_PUBLIC void termpaint_surface_set_bg_color(const termpaint_surface *surface, int x, int y, unsigned bg); _tERMPAINT_PUBLIC void termpaint_surface_set_deco_color(const termpaint_surface *surface, int x, int y, unsigned deco_color); _tERMPAINT_PUBLIC void termpaint_surface_set_softwrap_marker(termpaint_surface *surface, int x, int y, _Bool state); #define TERMPAINT_COPY_NO_TILE 0 #define TERMPAINT_COPY_TILE_PRESERVE -1 #define TERMPAINT_COPY_TILE_PUT 1 _tERMPAINT_PUBLIC void termpaint_surface_copy_rect(termpaint_surface *src_surface, int x, int y, int width, int height, termpaint_surface *dst_surface, int dst_x, int dst_y, int tile_left, int tile_right); _tERMPAINT_PUBLIC void termpaint_surface_tint(termpaint_surface *surface, void (*recolor)(void *user_data, unsigned *fg, unsigned *bg, unsigned *deco), void *user_data); _tERMPAINT_PUBLIC unsigned termpaint_surface_peek_fg_color(const termpaint_surface *surface, int x, int y); _tERMPAINT_PUBLIC unsigned termpaint_surface_peek_bg_color(const termpaint_surface *surface, int x, int y); _tERMPAINT_PUBLIC unsigned termpaint_surface_peek_deco_color(const termpaint_surface *surface, int x, int y); _tERMPAINT_PUBLIC int termpaint_surface_peek_style(const termpaint_surface *surface, int x, int y); _tERMPAINT_PUBLIC void termpaint_surface_peek_patch(const termpaint_surface *surface, int x, int y, const char **setup, const char **cleanup, _Bool *optimize); _tERMPAINT_PUBLIC const char *termpaint_surface_peek_text(const termpaint_surface *surface, int x, int y, int *len, int *left, int *right); _tERMPAINT_PUBLIC _Bool termpaint_surface_peek_softwrap_marker(const termpaint_surface *surface, int x, int y); _tERMPAINT_PUBLIC _Bool termpaint_surface_same_contents(const termpaint_surface *surface1, const termpaint_surface *surface2); _tERMPAINT_PUBLIC termpaint_text_measurement* termpaint_text_measurement_new(const termpaint_surface *surface); _tERMPAINT_PUBLIC termpaint_text_measurement* termpaint_text_measurement_new_or_nullptr(const termpaint_surface *surface); _tERMPAINT_PUBLIC void termpaint_text_measurement_free(termpaint_text_measurement *m); _tERMPAINT_PUBLIC void termpaint_text_measurement_reset(termpaint_text_measurement *m); _tERMPAINT_PUBLIC int termpaint_text_measurement_pending_ref(const termpaint_text_measurement *m); _tERMPAINT_PUBLIC int termpaint_text_measurement_last_codepoints(const termpaint_text_measurement *m); _tERMPAINT_PUBLIC int termpaint_text_measurement_last_clusters(const termpaint_text_measurement *m); _tERMPAINT_PUBLIC int termpaint_text_measurement_last_width(const termpaint_text_measurement *m); _tERMPAINT_PUBLIC int termpaint_text_measurement_last_ref(const termpaint_text_measurement *m); _tERMPAINT_PUBLIC int termpaint_text_measurement_limit_codepoints(const termpaint_text_measurement *m); _tERMPAINT_PUBLIC void termpaint_text_measurement_set_limit_codepoints(termpaint_text_measurement *m, int new_value); _tERMPAINT_PUBLIC int termpaint_text_measurement_limit_clusters(const termpaint_text_measurement *m); _tERMPAINT_PUBLIC void termpaint_text_measurement_set_limit_clusters(termpaint_text_measurement *m, int new_value); _tERMPAINT_PUBLIC int termpaint_text_measurement_limit_width(const termpaint_text_measurement *m); _tERMPAINT_PUBLIC void termpaint_text_measurement_set_limit_width(termpaint_text_measurement *m, int new_value); _tERMPAINT_PUBLIC int termpaint_text_measurement_limit_ref(const termpaint_text_measurement *m); _tERMPAINT_PUBLIC void termpaint_text_measurement_set_limit_ref(termpaint_text_measurement *m, int new_value); #define TERMPAINT_MEASURE_LIMIT_REACHED 1 #define TERMPAINT_MEASURE_NEW_CLUSTER 2 _tERMPAINT_PUBLIC int termpaint_text_measurement_feed_codepoint(termpaint_text_measurement *m, int ch, int ref_adjust); _tERMPAINT_PUBLIC _Bool /* reached limit */ termpaint_text_measurement_feed_utf32(termpaint_text_measurement *m, const uint32_t *chars, int length, _Bool final); _tERMPAINT_PUBLIC _Bool /* reached limit */ termpaint_text_measurement_feed_utf16(termpaint_text_measurement *m, const uint16_t *code_units, int length, _Bool final); _tERMPAINT_PUBLIC _Bool /* reached limit */ termpaint_text_measurement_feed_utf8(termpaint_text_measurement *m, const char *code_units, int length, _Bool final); #ifdef __cplusplus } #endif #endif termpaint-0.3.1/termpaint.symver000066400000000000000000000207031477303547200170060ustar00rootroot00000000000000TERMPAINT_0.3 { global: termpaint_attr_clone; termpaint_attr_clone_or_nullptr; termpaint_attr_free; termpaint_attr_new; termpaint_attr_new_or_nullptr; termpaint_attr_reset_style; termpaint_attr_set_bg; termpaint_attr_set_deco; termpaint_attr_set_fg; termpaint_attr_set_patch; termpaint_attr_set_patch_mustcheck; termpaint_attr_set_style; termpaint_attr_unset_style; termpaint_input_activate_quirk; termpaint_input_add_data; termpaint_input_arrow_down; termpaint_input_arrow_left; termpaint_input_arrow_right; termpaint_input_arrow_up; termpaint_input_backspace; termpaint_input_context_menu; termpaint_input_delete; termpaint_input_end; termpaint_input_enter; termpaint_input_escape; termpaint_input_expect_apc_sequences; termpaint_input_expect_cursor_position_report; termpaint_input_expect_legacy_mouse_reports; termpaint_input_f10; termpaint_input_f11; termpaint_input_f12; termpaint_input_f1; termpaint_input_f2; termpaint_input_f3; termpaint_input_f4; termpaint_input_f5; termpaint_input_f6; termpaint_input_f7; termpaint_input_f8; termpaint_input_f9; termpaint_input_focus_in; termpaint_input_focus_out; termpaint_input_free; termpaint_input_handle_paste; termpaint_input_home; termpaint_input_i_resync; termpaint_input_insert; termpaint_input_new; termpaint_input_new_or_nullptr; termpaint_input_numpad0; termpaint_input_numpad1; termpaint_input_numpad2; termpaint_input_numpad3; termpaint_input_numpad4; termpaint_input_numpad5; termpaint_input_numpad6; termpaint_input_numpad7; termpaint_input_numpad8; termpaint_input_numpad9; termpaint_input_numpad_add; termpaint_input_numpad_decimal; termpaint_input_numpad_divide; termpaint_input_numpad_enter; termpaint_input_numpad_multiply; termpaint_input_numpad_subtract; termpaint_input_page_down; termpaint_input_page_up; termpaint_input_paste_begin; termpaint_input_paste_end; termpaint_input_peek_buffer; termpaint_input_peek_buffer_length; termpaint_input_set_event_cb; termpaint_input_set_raw_filter_cb; termpaint_input_space; termpaint_input_tab; termpaint_integration_deinit; termpaint_integration_init; termpaint_integration_init_mustcheck; termpaint_integration_set_awaiting_response; termpaint_integration_set_is_bad; termpaint_integration_set_logging_func; termpaint_integration_set_request_callback; termpaint_integration_set_restore_sequence_updated; termpaint_surface_char_width; termpaint_surface_clear; termpaint_surface_clear_rect; termpaint_surface_clear_rect_with_attr; termpaint_surface_clear_rect_with_attr_char; termpaint_surface_clear_rect_with_char; termpaint_surface_clear_with_attr; termpaint_surface_clear_with_attr_char; termpaint_surface_clear_with_char; termpaint_surface_copy_rect; termpaint_surface_duplicate; termpaint_surface_free; termpaint_surface_height; termpaint_surface_new_surface; termpaint_surface_new_surface_or_nullptr; termpaint_surface_peek_bg_color; termpaint_surface_peek_deco_color; termpaint_surface_peek_fg_color; termpaint_surface_peek_patch; termpaint_surface_peek_softwrap_marker; termpaint_surface_peek_style; termpaint_surface_peek_text; termpaint_surface_resize; termpaint_surface_resize_mustcheck; termpaint_surface_same_contents; termpaint_surface_set_bg_color; termpaint_surface_set_deco_color; termpaint_surface_set_fg_color; termpaint_surface_set_softwrap_marker; termpaint_surface_tint; termpaint_surface_width; termpaint_surface_write_with_attr; termpaint_surface_write_with_attr_clipped; termpaint_surface_write_with_colors; termpaint_surface_write_with_colors_clipped; termpaint_surface_write_with_len_attr; termpaint_surface_write_with_len_attr_clipped; termpaint_surface_write_with_len_colors; termpaint_surface_write_with_len_colors_clipped; termpaint_terminal_activate_input_quirk; termpaint_terminal_add_input_data; termpaint_terminal_auto_detect; termpaint_terminal_auto_detect_apply_input_quirks; termpaint_terminal_auto_detect_result_text; termpaint_terminal_auto_detect_state; termpaint_terminal_bell; termpaint_terminal_callback; termpaint_terminal_capable; termpaint_terminal_disable_capability; termpaint_terminal_expect_apc_input_sequences; termpaint_terminal_expect_cursor_position_report; termpaint_terminal_expect_legacy_mouse_reports; termpaint_terminal_flush; termpaint_terminal_free; termpaint_terminal_free_with_restore; termpaint_terminal_get_surface; termpaint_terminal_glitch_on_out_of_memory; termpaint_terminal_handle_paste; termpaint_terminal_might_be_supported; termpaint_terminal_new; termpaint_terminal_new_or_nullptr; termpaint_terminal_new_surface; termpaint_terminal_new_surface_or_nullptr; termpaint_terminal_pause; termpaint_terminal_peek_input_buffer; termpaint_terminal_peek_input_buffer_length; termpaint_terminal_promise_capability; termpaint_terminal_request_focus_change_reports; termpaint_terminal_request_focus_change_reports_mustcheck; termpaint_terminal_request_tagged_paste; termpaint_terminal_request_tagged_paste_mustcheck; termpaint_terminal_reset_color; termpaint_terminal_restore_sequence; termpaint_terminal_self_reported_name_and_version; termpaint_terminal_set_color; termpaint_terminal_set_color_mustcheck; termpaint_terminal_set_cursor_position; termpaint_terminal_set_cursor_style; termpaint_terminal_set_cursor_visible; termpaint_terminal_set_event_cb; termpaint_terminal_set_icon_title; termpaint_terminal_set_icon_title_mustcheck; termpaint_terminal_set_log_mask; termpaint_terminal_set_mouse_mode; termpaint_terminal_set_mouse_mode_mustcheck; termpaint_terminal_set_raw_input_filter_cb; termpaint_terminal_set_title; termpaint_terminal_set_title_mustcheck; termpaint_terminal_setup_fullscreen; termpaint_terminal_should_use_truecolor; termpaint_terminal_unpause; termpaint_text_measurement_feed_codepoint; termpaint_text_measurement_feed_utf16; termpaint_text_measurement_feed_utf32; termpaint_text_measurement_feed_utf8; termpaint_text_measurement_free; termpaint_text_measurement_last_clusters; termpaint_text_measurement_last_codepoints; termpaint_text_measurement_last_ref; termpaint_text_measurement_last_width; termpaint_text_measurement_limit_clusters; termpaint_text_measurement_limit_codepoints; termpaint_text_measurement_limit_ref; termpaint_text_measurement_limit_width; termpaint_text_measurement_new; termpaint_text_measurement_new_or_nullptr; termpaint_text_measurement_pending_ref; termpaint_text_measurement_reset; termpaint_text_measurement_set_limit_clusters; termpaint_text_measurement_set_limit_codepoints; termpaint_text_measurement_set_limit_ref; termpaint_text_measurement_set_limit_width; termpaintx_enable_tk_logging; termpaintx_fd_set_termios; termpaintx_fd_terminal_size; termpaintx_full_integration; termpaintx_full_integration_apply_input_quirks; termpaintx_full_integration_available; termpaintx_full_integration_do_iteration; termpaintx_full_integration_do_iteration_with_timeout; termpaintx_full_integration_from_controlling_terminal; termpaintx_full_integration_from_fd; termpaintx_full_integration_original_terminal_attributes; termpaintx_full_integration_set_terminal; termpaintx_full_integration_setup_terminal_fullscreen; termpaintx_full_integration_terminal_size; termpaintx_full_integration_ttyrescue_start; termpaintx_full_integration_wait_for_ready; termpaintx_full_integration_wait_for_ready_with_message; termpaintx_ttyrescue_set_restore_termios; termpaintx_ttyrescue_start_or_nullptr; termpaintx_ttyrescue_stop; termpaintx_ttyrescue_update; }; TERMPAINT_0.3.1 { global: termpaint_terminal_free_with_restore_and_persistent; termpaint_terminal_pause_and_persistent; termpaint_terminal_set_inline; termpaint_terminal_setup_inline; termpaintx_full_integration_from_fds; termpaintx_full_integration_set_inline; termpaintx_full_integration_setup_terminal_inline; }; TERMPAINT_PRIVATE { global: termpaintp_test; local: *; }; termpaint-0.3.1/termpaint_char_width.h000066400000000000000000000045601477303547200201070ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #define NEW_WIDTH(num, width) ((num << 2) | (((unsigned)width) & 3)) #include "charclassification.inc" #include "charclassification_konsole_2018.inc" #include "charclassification_konsole_2022.inc" #undef NEW_WIDTH typedef struct termpaintp_width_ { const uint16_t* termpaint_char_width_offsets; const uint16_t* termpaint_char_width_data; } termpaintp_width; static const termpaintp_width termpaintp_char_width_default = { .termpaint_char_width_offsets = termpaint_char_width_offsets_default, .termpaint_char_width_data = termpaint_char_width_data_default }; static const termpaintp_width termpaintp_char_width_konsole2018 = { .termpaint_char_width_offsets = termpaint_char_width_offsets_konsole_2018, .termpaint_char_width_data = termpaint_char_width_data_konsole_2018 }; static const termpaintp_width termpaintp_char_width_konsole2022 = { .termpaint_char_width_offsets = termpaint_char_width_offsets_konsole_2022, .termpaint_char_width_data = termpaint_char_width_data_konsole_2022 }; static int termpaintp_char_width(const termpaintp_width *table, int ch) { if (ch >= 0x10ffff) { // outside of unicode, assume narrow return 1; } const uint16_t* termpaint_char_width_offsets = table->termpaint_char_width_offsets; const uint16_t* termpaint_char_width_data = table->termpaint_char_width_data; int low = termpaint_char_width_offsets[ch >> 14]; int high = termpaint_char_width_offsets[(ch >> 14) + 1] - 1; int search = (ch & 0x3fff) << 2; while (1) { if (low == high) { if ((termpaint_char_width_data[low] & 0xfffc) > search) { // this is save as each section always starts with data for 0 --low; } int val = termpaint_char_width_data[low] & 3; if (val == 3) { return -1; } return val; } int mid = low + (high - low) / 2; if (termpaint_char_width_data[mid] < search) { low = mid + 1; } else { if ((termpaint_char_width_data[mid] & 0xfffc) == search) { int val = termpaint_char_width_data[mid] & 3; if (val == 3) { return -1; } return val; } else { high = mid; } } } } termpaint-0.3.1/termpaint_compiler.h000066400000000000000000000027011477303547200176000ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef TERMPAINT_TERMPAINT_COMPILER_INCLUDED #define TERMPAINT_TERMPAINT_COMPILER_INCLUDED #include #if __GNUC__ >= 5 #define BUILTIN_CHECKED_ARITHMETIC_SUPPORTED(x) 1 #else #ifdef __has_builtin #define BUILTIN_CHECKED_ARITHMETIC_SUPPORTED(x) __has_builtin(x) #endif #endif #ifndef BUILTIN_CHECKED_ARITHMETIC_SUPPORTED #define BUILTIN_CHECKED_ARITHMETIC_SUPPORTED(x) 0 #endif static inline _Bool termpaint_smul_overflow(int a, int b, int* res) { #if BUILTIN_CHECKED_ARITHMETIC_SUPPORTED(__builtin_smul_overflow) return __builtin_smul_overflow(a, b, res); #else _Static_assert(sizeof(int) < sizeof(unsigned long long), "overflow protection not supported"); *res = (unsigned int)a * (unsigned int)b; unsigned long long ores = (unsigned long long)a * (unsigned long long)b; return ores != (unsigned long long)*res; #endif } static inline _Bool termpaint_sadd_overflow(int a, int b, int* res) { #if BUILTIN_CHECKED_ARITHMETIC_SUPPORTED(__builtin_sadd_overflow) return __builtin_sadd_overflow(a, b, res); #else _Static_assert(sizeof(int) < sizeof(long long), "overflow protection not supported"); long long tmp = (long long)a + (long long)b; *res = (int)tmp; return tmp > INT_MAX || tmp < INT_MIN; #endif } #define UNUSED(x) (void)x #ifdef __GNUC__ #define ATTRIBUTE_PRINTF __attribute__ ((format (printf, 2, 3))) #else #define ATTRIBUTE_PRINTF #endif #endif termpaint-0.3.1/termpaint_event.c000066400000000000000000000001011477303547200170720ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "termpaint_event.h" termpaint-0.3.1/termpaint_event.h000066400000000000000000000137601477303547200171160ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef TERMPAINT_TERMPAINT_EVENT_INCLUDED #define TERMPAINT_TERMPAINT_EVENT_INCLUDED #ifdef __cplusplus #ifndef _Bool #define _Bool bool #endif #endif #ifdef __cplusplus extern "C" { #endif #if defined(__GNUC__) && defined(TERMPAINT_EXPORT_SYMBOLS) #define _tERMPAINT_PUBLIC __attribute__((visibility("default"))) #else #define _tERMPAINT_PUBLIC #endif _tERMPAINT_PUBLIC const char *termpaint_input_i_resync(void); // Keys _tERMPAINT_PUBLIC const char *termpaint_input_enter(void); _tERMPAINT_PUBLIC const char *termpaint_input_space(void); _tERMPAINT_PUBLIC const char *termpaint_input_tab(void); _tERMPAINT_PUBLIC const char *termpaint_input_backspace(void); _tERMPAINT_PUBLIC const char *termpaint_input_context_menu(void); _tERMPAINT_PUBLIC const char *termpaint_input_delete(void); _tERMPAINT_PUBLIC const char *termpaint_input_end(void); _tERMPAINT_PUBLIC const char *termpaint_input_home(void); _tERMPAINT_PUBLIC const char *termpaint_input_insert(void); _tERMPAINT_PUBLIC const char *termpaint_input_page_down(void); _tERMPAINT_PUBLIC const char *termpaint_input_page_up(void); _tERMPAINT_PUBLIC const char *termpaint_input_arrow_down(void); _tERMPAINT_PUBLIC const char *termpaint_input_arrow_left(void); _tERMPAINT_PUBLIC const char *termpaint_input_arrow_right(void); _tERMPAINT_PUBLIC const char *termpaint_input_arrow_up(void); _tERMPAINT_PUBLIC const char *termpaint_input_numpad_divide(void); _tERMPAINT_PUBLIC const char *termpaint_input_numpad_multiply(void); _tERMPAINT_PUBLIC const char *termpaint_input_numpad_subtract(void); _tERMPAINT_PUBLIC const char *termpaint_input_numpad_add(void); _tERMPAINT_PUBLIC const char *termpaint_input_numpad_enter(void); _tERMPAINT_PUBLIC const char *termpaint_input_numpad_decimal(void); _tERMPAINT_PUBLIC const char *termpaint_input_numpad0(void); _tERMPAINT_PUBLIC const char *termpaint_input_numpad1(void); _tERMPAINT_PUBLIC const char *termpaint_input_numpad2(void); _tERMPAINT_PUBLIC const char *termpaint_input_numpad3(void); _tERMPAINT_PUBLIC const char *termpaint_input_numpad4(void); _tERMPAINT_PUBLIC const char *termpaint_input_numpad5(void); _tERMPAINT_PUBLIC const char *termpaint_input_numpad6(void); _tERMPAINT_PUBLIC const char *termpaint_input_numpad7(void); _tERMPAINT_PUBLIC const char *termpaint_input_numpad8(void); _tERMPAINT_PUBLIC const char *termpaint_input_numpad9(void); _tERMPAINT_PUBLIC const char *termpaint_input_escape(void); _tERMPAINT_PUBLIC const char *termpaint_input_f1(void); _tERMPAINT_PUBLIC const char *termpaint_input_f2(void); _tERMPAINT_PUBLIC const char *termpaint_input_f3(void); _tERMPAINT_PUBLIC const char *termpaint_input_f4(void); _tERMPAINT_PUBLIC const char *termpaint_input_f5(void); _tERMPAINT_PUBLIC const char *termpaint_input_f6(void); _tERMPAINT_PUBLIC const char *termpaint_input_f7(void); _tERMPAINT_PUBLIC const char *termpaint_input_f8(void); _tERMPAINT_PUBLIC const char *termpaint_input_f9(void); _tERMPAINT_PUBLIC const char *termpaint_input_f10(void); _tERMPAINT_PUBLIC const char *termpaint_input_f11(void); _tERMPAINT_PUBLIC const char *termpaint_input_f12(void); // Misc Events _tERMPAINT_PUBLIC const char *termpaint_input_focus_in(void); _tERMPAINT_PUBLIC const char *termpaint_input_focus_out(void); _tERMPAINT_PUBLIC const char *termpaint_input_paste_begin(void); _tERMPAINT_PUBLIC const char *termpaint_input_paste_end(void); #define TERMPAINT_EV_UNKNOWN 0 #define TERMPAINT_EV_CHAR 1 #define TERMPAINT_EV_KEY 2 #define TERMPAINT_EV_AUTO_DETECT_FINISHED 3 #define TERMPAINT_EV_OVERFLOW 4 #define TERMPAINT_EV_INVALID_UTF8 5 #define TERMPAINT_EV_CURSOR_POSITION 6 #define TERMPAINT_EV_MODE_REPORT 7 #define TERMPAINT_EV_COLOR_SLOT_REPORT 8 #define TERMPAINT_EV_REPAINT_REQUESTED 9 #define TERMPAINT_EV_MOUSE 10 #define TERMPAINT_EV_MISC 11 #define TERMPAINT_EV_PALETTE_COLOR_REPORT 12 #define TERMPAINT_EV_PASTE 13 #define TERMPAINT_EV_RAW_PRI_DEV_ATTRIB 100 #define TERMPAINT_EV_RAW_SEC_DEV_ATTRIB 101 #define TERMPAINT_EV_RAW_3RD_DEV_ATTRIB 102 #define TERMPAINT_EV_RAW_DECREQTPARM 103 #define TERMPAINT_EV_RAW_TERM_NAME 104 #define TERMPAINT_EV_RAW_TERMINFO_QUERY_REPLY 105 #define TERMPAINT_MOD_SHIFT 1 #define TERMPAINT_MOD_CTRL 2 #define TERMPAINT_MOD_ALT 4 #define TERMPAINT_MOD_ALTGR 8 #define TERMPAINT_MOUSE_PRESS 1 #define TERMPAINT_MOUSE_RELEASE 2 #define TERMPAINT_MOUSE_MOVE 3 struct termpaint_event_ { int type; union { // EV_CHAR and INVALID_UTF8 struct { unsigned length; const char *string; int modifier; } c; // EV_KEY struct { unsigned length; const char *atom; int modifier; } key; // EV_PASTE struct { unsigned length; const char *string; _Bool initial; _Bool final; } paste; // EV_MOUSE struct { int x; int y; int raw_btn_and_flags; int action; // TERMPAINT_MOUSE_* int button; // button == 3 means release with unknown button int modifier; } mouse; // EV_MISC struct { unsigned length; const char *atom; } misc; // EV_CURSOR_POSITION struct { int x; int y; _Bool safe; } cursor_position; // EV_MODE_REPORT struct { int number; int kind; int status; } mode; // TERMPAINT_EV_RAW_SEC_DEV_ATTRIB, etc struct { unsigned length; const char *string; } raw; // TERMPAINT_EV_COLOR_SLOT_REPORT struct { int slot; const char *color; unsigned length; } color_slot_report; // TERMPAINT_EV_PALETTE_COLOR_REPORT struct { int color_index; const char *color_desc; unsigned length; } palette_color_report; }; }; typedef struct termpaint_event_ termpaint_event; #ifdef __cplusplus } #endif #endif termpaint-0.3.1/termpaint_hash.h000066400000000000000000000135221477303547200167140ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef TERMPAINT_TERMPAINT_HASH_INCLUDED #define TERMPAINT_TERMPAINT_HASH_INCLUDED #include #include #include // internal header, not api or abi stable // pointers to this are stable as long as the item is kept in the hash typedef struct termpaint_hash_item_ { unsigned char* text; bool unused; struct termpaint_hash_item_ *next; } termpaint_hash_item; typedef struct termpaint_hash_ { int count; int allocated; termpaint_hash_item** buckets; int item_size; void (*gc_mark_cb)(struct termpaint_hash_*); void (*destroy_cb)(struct termpaint_hash_item_*); } termpaint_hash; static uint32_t termpaintp_hash_fnv1a(const unsigned char* text) { uint32_t hash = 2166136261; for (; *text; ++text) { hash = hash ^ *text; hash = hash * 16777619; } return hash; } static bool termpaintp_hash_grow(termpaint_hash* p) { int old_allocated = p->allocated; termpaint_hash_item** old_buckets = p->buckets; p->allocated *= 2; p->buckets = (termpaint_hash_item**)calloc(p->allocated, sizeof(*p->buckets)); if (!p->buckets) { p->allocated = old_allocated; p->buckets = old_buckets; return false; } for (int i = 0; i < old_allocated; i++) { termpaint_hash_item* item_it = old_buckets[i]; while (item_it) { termpaint_hash_item* item = item_it; item_it = item->next; uint32_t bucket = termpaintp_hash_fnv1a(item->text) % p->allocated; item->next = p->buckets[bucket]; p->buckets[bucket] = item; } } free(old_buckets); return true; } static int termpaintp_hash_gc(termpaint_hash* p) { if (!p->gc_mark_cb) { return 0; } int items_removed = 0; for (int i = 0; i < p->allocated; i++) { termpaint_hash_item* item_it = p->buckets[i]; while (item_it) { item_it->unused = true; item_it = item_it->next; } } p->gc_mark_cb(p); for (int i = 0; i < p->allocated; i++) { termpaint_hash_item** prev_ptr = &p->buckets[i]; termpaint_hash_item* item = *prev_ptr; while (item) { termpaint_hash_item* old = item; if (item->unused) { *prev_ptr = item->next; } else { prev_ptr = &item->next; } item = item->next; if (old->unused) { --p->count; if (p->destroy_cb) { p->destroy_cb(old); } free(old->text); free(old); ++items_removed; } } } return items_removed; } static void* termpaintp_hash_ensure(termpaint_hash* p, const unsigned char* text) { if (!p->allocated) { p->allocated = 32; p->buckets = (termpaint_hash_item**)calloc(p->allocated, sizeof(termpaint_hash_item*)); if (!p->buckets) { p->allocated = 0; return NULL; } } uint32_t bucket = termpaintp_hash_fnv1a(text) % p->allocated; if (p->buckets[bucket]) { termpaint_hash_item* item = p->buckets[bucket]; termpaint_hash_item* prev = item; while (item) { prev = item; if (strcmp((const char*)text, (char*)item->text) == 0) { return item; } item = item->next; } if (p->allocated / 2 <= p->count) { if (termpaintp_hash_gc(p) == 0) { if (!termpaintp_hash_grow(p)) { return NULL; } } // either termpaintp_hash_gc or termpaintp_hash_grow have invalidated `prev` but now capacity is free return termpaintp_hash_ensure(p, text); } else { item = (termpaint_hash_item*)calloc(1, p->item_size); if (!item) { return NULL; } item->text = (unsigned char*)strdup((const char*)text); if (!item->text) { free(item); return NULL; } prev->next = item; p->count++; return item; } } else { if (p->allocated / 2 <= p->count && termpaintp_hash_gc(p) == 0) { if (!termpaintp_hash_grow(p)) { return NULL; } return termpaintp_hash_ensure(p, text); } else { termpaint_hash_item* item = (termpaint_hash_item*)calloc(1, p->item_size); if (!item) { return NULL; } item->text = (unsigned char*)strdup((const char*)text); if (!item->text) { free(item); return NULL; } p->count++; p->buckets[bucket] = item; return item; } } } static void* termpaintp_hash_get(termpaint_hash* p, const unsigned char* text) { if (!p->allocated) { return NULL; } uint32_t bucket = termpaintp_hash_fnv1a(text) % p->allocated; if (p->buckets[bucket]) { termpaint_hash_item* item = p->buckets[bucket]; while (item) { if (strcmp((const char*)text, (char*)item->text) == 0) { return item; } item = item->next; } } return NULL; } static void termpaintp_hash_destroy(termpaint_hash* p) { for (int i = 0; i < p->allocated; i++) { termpaint_hash_item* item = p->buckets[i]; while (item) { termpaint_hash_item* old = item; item = item->next; if (p->destroy_cb) { p->destroy_cb(old); } free(old->text); free(old); } } free(p->buckets); p->buckets = (termpaint_hash_item**)0; p->allocated = 0; p->count = 0; } #endif termpaint-0.3.1/termpaint_image.cpp000066400000000000000000000421741477303547200174130ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "termpaint_image.h" #include #include #include #ifndef BUNDLED_PICOJSON #include "picojson.h" #else #include "third-party/picojson.h" #endif #include static const char *const names[16] = { "black", "red", "green", "yellow", "blue", "magenta", "cyan", "white", "bright black", "bright red", "bright green", "bright yellow", "bright blue", "bright magenta", "bright cyan", "bright white", }; namespace { struct FileWriter { FILE *file; }; void write_string(FileWriter *w, const char *s) { fputs(s, w->file); } void write_char(FileWriter *w, int c) { putc(c, w->file); } struct StringWriter { std::string data; }; void write_string(StringWriter *w, const char *s) { w->data += s; } void write_char(StringWriter *w, int c) { w->data += c; } template void write_printf(WRITER *w, const char *format, ...) { char buff[100]; va_list aptr; va_start(aptr, format); int len = vsnprintf(buff, sizeof(buff), format, aptr); va_end(aptr); if (len < 0) { abort(); } if (len < static_cast(sizeof(buff)) - 1) { write_string(w, buff); } else { char *p = (char*)malloc(len + 1); if (!p) { abort(); } va_start(aptr, format); int res = vsnprintf(p, len + 1, format, aptr); if (res < 0) { abort(); } va_end(aptr); write_string(w, p); free(p); } } template static void print_color(WRITER* f, const char* name, unsigned color) { if (color != TERMPAINT_DEFAULT_COLOR) { write_printf(f, ", \"%s\": \"", name); if ((color & 0xff000000) == TERMPAINT_RGB_COLOR_OFFSET) { write_printf(f, "#%02x%02x%02x\"", (color >> 16) & 0xff, (color >> 8) & 0xff, color & 0xff); } else if (TERMPAINT_NAMED_COLOR <= color && TERMPAINT_NAMED_COLOR + 15 >= color) { write_printf(f, "%s\"", names[color & 0xf]); } else if (TERMPAINT_INDEXED_COLOR <= color && TERMPAINT_INDEXED_COLOR + 255 >= color) { write_printf(f, "%i\"", color & 0xff); } } } template static int print_style(WRITER* f, int style, const char* name, int flag) { if (style & flag) { style &= ~flag; write_printf(f, ", \"%s\": true", name); } return style; } template static void print_string(WRITER* f, const char* s_signed, size_t len) { const unsigned char* s = (const unsigned char*)s_signed; for (size_t i = 0; i < len;) { int l = termpaintp_utf8_len(s[i]); if (i + l > len) { break; } int ch = termpaintp_utf8_decode_from_utf8(s + i, l); if (s[i] >= 32 && s[i] <= 126 && s[i] != '"' && s[i] != '\\') { write_char(f, s[i]); } else { unsigned both = termpaintp_utf16_split(ch); write_printf(f, "\\u%04x", both & 0xffff); if (both > 0xffff) { write_printf(f, "\\u%04x", both >> 16); } } i += l; } } } template static bool termpaintp_image_save_impl(termpaint_surface *surface, WRITER &writer) { bool ok = true; const int width = termpaint_surface_width(surface); const int height = termpaint_surface_height(surface); write_string(&writer, "{\"termpaint_image\": true,\n"); write_printf(&writer, " \"width\": %d, \"height\": %d, \"version\": 0, \"cells\":[\n", width, height); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { write_printf(&writer, " {\"x\": %d, \"y\": %d,\n", x, y); write_printf(&writer, " \"t\": \""); int len, left, right; const char *text = termpaint_surface_peek_text(surface, x, y, &len, &left, &right); if (left != x) { ok = false; } if (len != 1 || text[0] != '\x7f') { print_string(&writer, text, len); write_string(&writer, "\""); } else { write_string(&writer, " \", \"cleared\": true"); } if (right - left) { write_printf(&writer, ", \"width\": %i", right - left + 1); } print_color(&writer, "fg", termpaint_surface_peek_fg_color(surface, x, y)); print_color(&writer, "bg", termpaint_surface_peek_bg_color(surface, x, y)); print_color(&writer, "deco", termpaint_surface_peek_deco_color(surface, x, y)); int style = termpaint_surface_peek_style(surface, x, y); style = print_style(&writer, style, "bold", TERMPAINT_STYLE_BOLD); style = print_style(&writer, style, "italic", TERMPAINT_STYLE_ITALIC); style = print_style(&writer, style, "blink", TERMPAINT_STYLE_BLINK); style = print_style(&writer, style, "overline", TERMPAINT_STYLE_OVERLINE); style = print_style(&writer, style, "inverse", TERMPAINT_STYLE_INVERSE); style = print_style(&writer, style, "strike", TERMPAINT_STYLE_STRIKE); style = print_style(&writer, style, "underline", TERMPAINT_STYLE_UNDERLINE); style = print_style(&writer, style, "double underline", TERMPAINT_STYLE_UNDERLINE_DBL); style = print_style(&writer, style, "curly underline", TERMPAINT_STYLE_UNDERLINE_CURLY); if (style != 0) { ok = false; } if (termpaint_surface_peek_softwrap_marker(surface, x, y)) { write_string(&writer, ", \"x-termpaint-softwrap\": true"); } const char* setup; const char* cleanup; bool optimize; termpaint_surface_peek_patch(surface, x, y, &setup, &cleanup, &optimize); if (setup || cleanup) { write_string(&writer, ", \"patch\": { \"setup\": "); if (setup) { write_string(&writer, "\""); print_string(&writer, setup, strlen(setup)); write_string(&writer, "\""); } else { write_string(&writer, "null"); } write_string(&writer, ", \"cleanup\": "); if (cleanup) { write_string(&writer, "\""); print_string(&writer, cleanup, strlen(cleanup)); write_string(&writer, "\""); } else { write_string(&writer, "null"); } write_printf(&writer, ", \"optimize\": %s}", optimize ? "true" : "false"); } x = right; if (x == width-1 && y == height - 1) { write_string(&writer, "}\n"); } else { write_string(&writer, "},\n"); } } write_string(&writer, "\n"); } write_string(&writer, "]}\n"); return ok; } char *termpaint_image_save_alloc_buffer(termpaint_surface *surface) { StringWriter writer; bool res = termpaintp_image_save_impl(surface, writer); if (!res) { return nullptr; } else { return strdup(writer.data.data()); } } void termpaint_image_save_dealloc_buffer(char *buffer) { free(buffer); } bool termpaint_image_save_to_file(termpaint_surface *surface, FILE *file) { FileWriter writer{file}; return termpaintp_image_save_impl(surface, writer); } bool termpaint_image_save(termpaint_surface *surface, const char *name) { FILE* f = fopen(name, "w"); if (!f) { return false; } bool ret = termpaint_image_save_to_file(surface, f); if (fclose(f) != 0) { ret = false; } return ret; } namespace { class fread_iterator { public: fread_iterator() = default; fread_iterator(FILE* f) : _f(f), _at_end(false) { ++*this; } bool operator==(const fread_iterator& other) const { return _at_end == other._at_end; } void operator++() { size_t ret; do { ret = fread(&_current, 1, 1, _f); } while (ret == 0 && errno == EINTR); if (ret < 1) { _at_end = true; } } char operator*() { return _current; } private: FILE* _f = nullptr; bool _at_end = true; char _current = 0; }; template static bool has(const picojson::object& obj, const char* name) { return obj.count(name) && obj.at(name).is(); } template static bool has(const picojson::object& obj, const char* name) { return obj.count(name) && (obj.at(name).is() || obj.at(name).is()); } template static T get(const picojson::object& obj, const char* name) { return obj.at(name).get(); } } static int read_flag(const picojson::object& obj, const char* name, int flag) { if (has(obj, name) && get(obj, name)) { return flag; } return 0; } static unsigned parse_color(const std::string& s) { if (s.size() == 7 && s[0] == '#') { int r = 0, g = 0, b = 0; sscanf(s.data() + 1, "%02x%02x%02x", &r, &g, &b); return TERMPAINT_RGB_COLOR(r, g, b); } for (unsigned i = 0; i < std::extent(); i++) { if (s == names[i]) { return TERMPAINT_NAMED_COLOR + i; } } unsigned indexed = 0; for (unsigned i = 0; i < s.length(); i++) { if (s[i] < '0' || s[i] > '9') { break; } indexed *= 10; indexed += (unsigned)s[i] - '0'; if (indexed > 0xff) { break; } if (i + 1 == s.length()) { return TERMPAINT_INDEXED_COLOR + indexed; } } return TERMPAINT_DEFAULT_COLOR; } static bool streq_nullsafe(const char* a, const char* b) { if (!a && !b) { return true; } if (!a || !b) { return false; } return strcmp(a, b) == 0; } static termpaint_surface *termpaintp_image_load_from_value(termpaint_terminal *term, picojson::value& rootValue) { termpaint_surface *surface = nullptr; if (!rootValue.is()) { return nullptr; } picojson::object root = rootValue.get(); if (!has(root, "termpaint_image") || !has(root, "version") || !has(root, "width") || !has(root, "height") || !has(root, "cells")) { return nullptr; } int width = static_cast(get(root, "width")); int height = static_cast(get(root, "height")); surface = termpaint_terminal_new_surface(term, width, height); termpaint_attr* attr = termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); picojson::array cells = get(root, "cells"); for (const auto& cellValue: cells) { if (!cellValue.is()) { termpaint_surface_free(surface); termpaint_attr_free(attr); return nullptr; } picojson::object cell = cellValue.get(); if (!has(cell, "x") || !has(cell, "y") || !has(cell, "t")) { termpaint_surface_free(surface); termpaint_attr_free(attr); return nullptr; } unsigned fg = TERMPAINT_DEFAULT_COLOR; if (has(cell, "fg")) { fg = parse_color(get(cell, "fg")); } termpaint_attr_set_fg(attr, fg); unsigned bg = TERMPAINT_DEFAULT_COLOR; if (has(cell, "bg")) { bg = parse_color(get(cell, "bg")); } termpaint_attr_set_bg(attr, bg); unsigned deco = TERMPAINT_DEFAULT_COLOR; if (has(cell, "deco")) { deco = parse_color(get(cell, "deco")); } termpaint_attr_set_deco(attr, deco); const char* setup = nullptr; std::string setup_str; const char* cleanup = nullptr; std::string cleanup_str; bool optimize = true; if (has(cell, "patch")) { picojson::object patch = get(cell, "patch"); if (has(patch, "setup") && has(patch, "cleanup") && has(patch, "optimize")) { if (has(patch, "setup")) { setup_str = get(patch, "setup"); setup = setup_str.data(); } if (has(patch, "cleanup")) { cleanup_str = get(patch, "cleanup"); cleanup = cleanup_str.data(); } optimize = get(patch, "optimize"); termpaint_attr_set_patch(attr, optimize, setup, cleanup); } else { termpaint_attr_set_patch(attr, false, nullptr, nullptr); } } else { termpaint_attr_set_patch(attr, false, nullptr, nullptr); } termpaint_attr_reset_style(attr); int style = 0; style |= read_flag(cell, "bold", TERMPAINT_STYLE_BOLD); style |= read_flag(cell, "italic", TERMPAINT_STYLE_ITALIC); style |= read_flag(cell, "blink", TERMPAINT_STYLE_BLINK); style |= read_flag(cell, "overline", TERMPAINT_STYLE_OVERLINE); style |= read_flag(cell, "inverse", TERMPAINT_STYLE_INVERSE); style |= read_flag(cell, "strike", TERMPAINT_STYLE_STRIKE); style |= read_flag(cell, "underline", TERMPAINT_STYLE_UNDERLINE); style |= read_flag(cell, "double underline", TERMPAINT_STYLE_UNDERLINE_DBL); style |= read_flag(cell, "curly underline", TERMPAINT_STYLE_UNDERLINE_CURLY); termpaint_attr_set_style(attr, style); int x = static_cast(get(cell, "x")); int y = static_cast(get(cell, "y")); int width = 1; if (has(cell, "width")) { width = static_cast(get(cell, "width")); } std::string text = get(cell, "t"); bool erased = has(cell, "cleared") ? get(cell, "cleared") : false; if (erased) { text = "\x7f"; } termpaint_surface_write_with_attr(surface, x, y, text.data(), attr); if (has(cell, "x-termpaint-softwrap") && get(cell, "x-termpaint-softwrap")) { termpaint_surface_set_softwrap_marker(surface, x, y, true); } { bool ok = true; int actual_len; int actual_left, actual_right; const char *actual_text = termpaint_surface_peek_text(surface, x, y, &actual_len, &actual_left, &actual_right); if (actual_text != text || actual_len != static_cast(text.size()) || actual_left != x || actual_right != x + width - 1) { ok = false; } if (fg != termpaint_surface_peek_fg_color(surface, x, y) || bg != termpaint_surface_peek_bg_color(surface, x, y) || deco != termpaint_surface_peek_deco_color(surface, x, y) || style != termpaint_surface_peek_style(surface, x, y)) { ok = false; } const char* actual_setup; const char* actual_cleanup; bool actual_optimize; termpaint_surface_peek_patch(surface, x, y, &actual_setup, &actual_cleanup, &actual_optimize); if (!streq_nullsafe(actual_setup, setup) || !streq_nullsafe(actual_cleanup, cleanup) || actual_optimize != optimize) { ok = false; } if (!ok) { termpaint_surface_free(surface); termpaint_attr_free(attr); return nullptr; } } } termpaint_attr_free(attr); return surface; } termpaint_surface *termpaint_image_load_from_file(termpaint_terminal *term, FILE *file) { picojson::value rootValue; std::string err; picojson::parse(rootValue, fread_iterator(file), fread_iterator(), &err); if (err.size()) { return nullptr; } return termpaintp_image_load_from_value(term, rootValue); } termpaint_surface *termpaint_image_load(termpaint_terminal *term, const char *name) { FILE* f = fopen(name, "r"); if (!f) { return nullptr; } picojson::value rootValue; std::string err; picojson::parse(rootValue, fread_iterator(f), fread_iterator(), &err); fclose(f); if (err.size()) { return nullptr; } return termpaintp_image_load_from_value(term, rootValue); } termpaint_surface *termpaint_image_load_from_buffer(termpaint_terminal *term, char *buffer, int length) { picojson::value rootValue; std::string err; picojson::parse(rootValue, buffer, buffer + length, &err); if (err.size()) { return nullptr; } return termpaintp_image_load_from_value(term, rootValue); } termpaint-0.3.1/termpaint_image.h000066400000000000000000000021011477303547200170420ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef TERMPAINT_TERMPAINT_IMAGE_INCLUDED #define TERMPAINT_TERMPAINT_IMAGE_INCLUDED #include #include #ifdef __cplusplus extern "C" { #endif #if defined(__GNUC__) && defined(TERMPAINT_EXPORT_SYMBOLS) #define _tERMPAINT_PUBLIC __attribute__((visibility("default"))) #else #define _tERMPAINT_PUBLIC #endif _tERMPAINT_PUBLIC _Bool termpaint_image_save(termpaint_surface *surface, const char* name); _tERMPAINT_PUBLIC _Bool termpaint_image_save_to_file(termpaint_surface *surface, FILE *file); _tERMPAINT_PUBLIC char* termpaint_image_save_alloc_buffer(termpaint_surface *surface); _tERMPAINT_PUBLIC void termpaint_image_save_dealloc_buffer(char *buffer); _tERMPAINT_PUBLIC termpaint_surface *termpaint_image_load(termpaint_terminal *term, const char *name); _tERMPAINT_PUBLIC termpaint_surface *termpaint_image_load_from_file(termpaint_terminal *term, FILE *file); _tERMPAINT_PUBLIC termpaint_surface *termpaint_image_load_from_buffer(termpaint_terminal *term, char *buffer, int length); #ifdef __cplusplus } #endif #endif termpaint-0.3.1/termpaint_image.symver000066400000000000000000000004251477303547200201470ustar00rootroot00000000000000TERMPAINT_0.3 { global: termpaint_image_load; termpaint_image_load_from_buffer; termpaint_image_load_from_file; termpaint_image_save; termpaint_image_save_alloc_buffer; termpaint_image_save_dealloc_buffer; termpaint_image_save_to_file; local: *; }; termpaint-0.3.1/termpaint_input.c000066400000000000000000002251341477303547200171270ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "termpaint_input.h" #include #include #include #include #include // for exit #include // for debugging prints and debugging data export #include "termpaint_compiler.h" #include "termpaint_utf8.h" /* Known problems: * * Massivly depends on resync trick. Non resync mode currently no longer supported * * in modOther ctrl-? strange (utf 8 converter?) * * needs to detect utf-8 encoded C1 chars? Or maybe that not used in the wild at all? */ #ifndef nullptr #define nullptr ((void*)0) #endif #define DEF_ATOM(name, value) \ static const char ATOM_ ## name[] = value; \ const char *termpaint_input_ ## name (void) { return ATOM_ ## name; } // Naming based on W3C uievents-code spec DEF_ATOM(enter, "Enter") DEF_ATOM(space, "Space") DEF_ATOM(tab, "Tab") DEF_ATOM(backspace, "Backspace") DEF_ATOM(context_menu, "ContextMenu") DEF_ATOM(delete, "Delete") DEF_ATOM(end, "End") // help omitted DEF_ATOM(home, "Home") DEF_ATOM(insert, "Insert") DEF_ATOM(page_down, "PageDown") DEF_ATOM(page_up, "PageUp") DEF_ATOM(arrow_down, "ArrowDown") DEF_ATOM(arrow_left, "ArrowLeft") DEF_ATOM(arrow_right, "ArrowRight") DEF_ATOM(arrow_up, "ArrowUp") DEF_ATOM(numpad_divide, "NumpadDivide") DEF_ATOM(numpad_multiply, "NumpadMultiply") DEF_ATOM(numpad_subtract, "NumpadSubtract") DEF_ATOM(numpad_add, "NumpadAdd") DEF_ATOM(numpad_enter, "NumpadEnter") DEF_ATOM(numpad_decimal, "NumpadDecimal") DEF_ATOM(numpad0, "Numpad0") DEF_ATOM(numpad1, "Numpad1") DEF_ATOM(numpad2, "Numpad2") DEF_ATOM(numpad3, "Numpad3") DEF_ATOM(numpad4, "Numpad4") DEF_ATOM(numpad5, "Numpad5") DEF_ATOM(numpad6, "Numpad6") DEF_ATOM(numpad7, "Numpad7") DEF_ATOM(numpad8, "Numpad8") DEF_ATOM(numpad9, "Numpad9") DEF_ATOM(escape, "Escape") DEF_ATOM(f1, "F1") DEF_ATOM(f2, "F2") DEF_ATOM(f3, "F3") DEF_ATOM(f4, "F4") DEF_ATOM(f5, "F5") DEF_ATOM(f6, "F6") DEF_ATOM(f7, "F7") DEF_ATOM(f8, "F8") DEF_ATOM(f9, "F9") DEF_ATOM(f10, "F10") DEF_ATOM(f11, "F11") DEF_ATOM(f12, "F12") // Misc Events DEF_ATOM(focus_in, "FocusIn") DEF_ATOM(focus_out, "FocusOut") DEF_ATOM(paste_begin, "PasteBegin") DEF_ATOM(paste_end, "PasteEnd") DEF_ATOM(i_resync, "i_resync") #define MOD_CTRL TERMPAINT_MOD_CTRL #define MOD_ALT TERMPAINT_MOD_ALT #define MOD_SHIFT TERMPAINT_MOD_SHIFT #define MOD_ALTGR TERMPAINT_MOD_ALTGR #define MOD_PRINT (1u << 31) #define MOD_ENTER (1 << 31 + 1 << 30) //#define MOD_PRINT (1 << 31) struct key_mapping_entry_ { const char *sequence; const char *atom; unsigned int modifiers; }; typedef struct key_mapping_entry_ key_mapping_entry; #define XTERM_MODS(PREFIX, POSTFIX, ATOM) \ { PREFIX "2" POSTFIX, ATOM, MOD_SHIFT }, \ { PREFIX "3" POSTFIX, ATOM, MOD_ALT }, \ { PREFIX "4" POSTFIX, ATOM, MOD_ALT | MOD_SHIFT }, \ { PREFIX "5" POSTFIX, ATOM, MOD_CTRL }, \ { PREFIX "6" POSTFIX, ATOM, MOD_CTRL | MOD_SHIFT }, \ { PREFIX "7" POSTFIX, ATOM, MOD_CTRL | MOD_ALT }, \ { PREFIX "8" POSTFIX, ATOM, MOD_CTRL | MOD_ALT | MOD_SHIFT } // xterm has 2 settings where a '>' is added to the CSI sequences added, support that too // ESC[>2;3m and ESC[>1;3m #define XTERM_MODS_GT(STR, POSTFIX, ATOM) \ { "\033[" STR "2" POSTFIX, ATOM, MOD_SHIFT }, \ { "\033[>" STR "2" POSTFIX, ATOM, MOD_SHIFT }, \ { "\033[" STR "3" POSTFIX, ATOM, MOD_ALT }, \ { "\033[>" STR "3" POSTFIX, ATOM, MOD_ALT }, \ { "\033[" STR "4" POSTFIX, ATOM, MOD_ALT | MOD_SHIFT }, \ { "\033[>" STR "4" POSTFIX, ATOM, MOD_ALT | MOD_SHIFT }, \ { "\033[" STR "5" POSTFIX, ATOM, MOD_CTRL }, \ { "\033[>" STR "5" POSTFIX, ATOM, MOD_CTRL }, \ { "\033[" STR "6" POSTFIX, ATOM, MOD_CTRL | MOD_SHIFT }, \ { "\033[>" STR "6" POSTFIX, ATOM, MOD_CTRL | MOD_SHIFT }, \ { "\033[" STR "7" POSTFIX, ATOM, MOD_CTRL | MOD_ALT }, \ { "\033[>" STR "7" POSTFIX, ATOM, MOD_CTRL | MOD_ALT }, \ { "\033[" STR "8" POSTFIX, ATOM, MOD_CTRL | MOD_ALT | MOD_SHIFT }, \ { "\033[>" STR "8" POSTFIX, ATOM, MOD_CTRL | MOD_ALT | MOD_SHIFT } // keyboard settings to consider: // xterm: // xterm.vt100.translations: : insert() --> remove all xterm side keybindings // xterm.vt100.modifyCursorKeys \in (-1, 0, 1, 2, 3) // xterm.vt100.modifyFunctionKeys \in (-1, 0, 1, 2, 3) // xterm.vt100.modifyKeyboard ?? // xterm.vt100.modifyOtherKeys ?? // xterm.vt100.oldXtermFKeys ?? // // urxvt: urxvt --perl-ext-common "" --perl-ext "" ++iso14755 -keysym.Insert "builtin-string:" -keysym.Prior "builtin-string:" -keysym.Next "builtin-string:" -keysym.C-M-v "builtin-string:" -keysym.C-M-c "builtin-string:" // Modes: // ?1 // ?66 keypad mapping changes // ?67 // ?1035 // ?1036 // ?1039 // ?1050 ??? // ?1051 ??? // ?1052 ??? // ?1053 ??? // ?1060 ??? // ?1061 ??? static const key_mapping_entry key_mapping_table[] = { { "\x0d", ATOM_enter, 0 }, // also ctrl-m in traditional mode { "\033\x0d", ATOM_enter, MOD_ALT }, XTERM_MODS("\033[27;", ";13~", ATOM_enter), // modifiy other keys mode XTERM_MODS("\033[13;", "u", ATOM_enter), // modifiy other keys mode { "\x09", ATOM_tab, 0 }, //also ctrl_i { "\033\x09", ATOM_tab, MOD_ALT }, //also ctrl-alt-i { "\033[Z", ATOM_tab, MOD_SHIFT }, // xterm, normal mode XTERM_MODS("\033[27;", ";9~", ATOM_tab), // modifiy other keys mode XTERM_MODS("\033[9;", "u", ATOM_tab), // modifiy other keys mode { " ", ATOM_space, 0 }, { "\033 ", ATOM_space, MOD_ALT }, // { "\x00", ATOM_SPACE, MOD_CTRL } via special case in code // { "\033\x00", ATOM_space, MOD_CTRL | MOD_ALT} via special case in code XTERM_MODS("\033[27;", ";32~", ATOM_space), // modifiy other keys mode XTERM_MODS("\033[32;", "u", ATOM_space), // modifiy other keys mode //+ also ctrl-2 { "\033[29~", ATOM_context_menu, 0 }, // + also shift F4 in linux vt XTERM_MODS("\033[29;", "~", ATOM_context_menu), { "\033[3~", ATOM_delete, 0 }, XTERM_MODS("\033[3;", "~", ATOM_delete), { "\033[3$", ATOM_delete, MOD_SHIFT}, { "\033[3^", ATOM_delete, MOD_CTRL}, { "\033[3@", ATOM_delete, MOD_CTRL | MOD_SHIFT}, { "\033\033[3~", ATOM_delete, MOD_ALT }, { "\033\033[3$", ATOM_delete, MOD_ALT | MOD_SHIFT}, { "\033\033[3^", ATOM_delete, MOD_CTRL | MOD_ALT}, { "\033\033[3@", ATOM_delete, MOD_CTRL | MOD_ALT | MOD_SHIFT}, { "\033[3;1~", ATOM_delete, MOD_ALTGR }, { "\033[F", ATOM_end, 0}, XTERM_MODS_GT("1;", "F", ATOM_end), { "\033OF", ATOM_end, 0}, { "\033[4~", ATOM_end, 0}, { "\033[8~", ATOM_end, 0}, { "\033[8$", ATOM_end, MOD_SHIFT}, { "\033[8^", ATOM_end, MOD_CTRL}, { "\033[8@", ATOM_end, MOD_CTRL | MOD_SHIFT}, { "\033\033[8~", ATOM_end, MOD_ALT}, { "\033\033[8$", ATOM_end, MOD_ALT | MOD_SHIFT}, { "\033\033[8^", ATOM_end, MOD_CTRL | MOD_ALT}, { "\033\033[8@", ATOM_end, MOD_CTRL | MOD_ALT | MOD_SHIFT}, { "\033[1;1F", ATOM_end, MOD_ALTGR}, { "\033[H", ATOM_home, 0}, XTERM_MODS_GT("1;", "H", ATOM_home), { "\033OH", ATOM_home, 0}, { "\033[1~", ATOM_home, 0}, { "\033[7~", ATOM_home, 0}, { "\033[7$", ATOM_home, MOD_SHIFT}, { "\033[7^", ATOM_home, MOD_CTRL}, { "\033[7@", ATOM_home, MOD_CTRL | MOD_SHIFT}, { "\033\033[7~", ATOM_home, MOD_ALT}, { "\033\033[7$", ATOM_home, MOD_ALT | MOD_SHIFT}, { "\033\033[7^", ATOM_home, MOD_CTRL | MOD_ALT}, { "\033\033[7@", ATOM_home, MOD_CTRL | MOD_ALT | MOD_SHIFT}, { "\033[1;1H", ATOM_home, MOD_ALTGR}, { "\033[2~", ATOM_insert, 0}, XTERM_MODS("\033[2;", "~", ATOM_insert), { "\033[2$", ATOM_insert, MOD_SHIFT}, { "\033[2^", ATOM_insert, MOD_CTRL}, { "\033[2@", ATOM_insert, MOD_CTRL | MOD_SHIFT}, { "\033\033[2~", ATOM_insert, MOD_ALT}, { "\033\033[2$", ATOM_insert, MOD_ALT | MOD_SHIFT}, { "\033\033[2^", ATOM_insert, MOD_CTRL | MOD_ALT}, { "\033\033[2@", ATOM_insert, MOD_CTRL | MOD_ALT | MOD_SHIFT}, { "\033[2;1~", ATOM_insert, MOD_ALTGR}, { "\033[6~", ATOM_page_down, 0}, XTERM_MODS("\033[6;", "~", ATOM_page_down), { "\033[6$", ATOM_page_down, MOD_SHIFT}, { "\033[6^", ATOM_page_down, MOD_CTRL}, { "\033[6@", ATOM_page_down, MOD_CTRL | MOD_SHIFT}, { "\033\033[6~", ATOM_page_down, MOD_ALT}, { "\033\033[6$", ATOM_page_down, MOD_ALT | MOD_SHIFT}, { "\033\033[6^", ATOM_page_down, MOD_CTRL | MOD_ALT}, { "\033\033[6@", ATOM_page_down, MOD_CTRL | MOD_ALT | MOD_SHIFT}, { "\033[6;1~", ATOM_page_down, MOD_ALTGR}, { "\033[5~", ATOM_page_up, 0}, XTERM_MODS("\033[5;", "~", ATOM_page_up), // shift combinations only available when scroll bindings are removed in xterm { "\033[5$", ATOM_page_up, MOD_SHIFT}, { "\033[5^", ATOM_page_up, MOD_CTRL}, { "\033[5@", ATOM_page_up, MOD_CTRL | MOD_SHIFT}, { "\033\033[5~", ATOM_page_up, MOD_ALT}, { "\033\033[5$", ATOM_page_up, MOD_ALT | MOD_SHIFT}, { "\033\033[5^", ATOM_page_up, MOD_CTRL | MOD_ALT}, { "\033\033[5@", ATOM_page_up, MOD_CTRL | MOD_ALT | MOD_SHIFT}, { "\033[5;1~", ATOM_page_up, MOD_ALTGR}, { "\033[B", ATOM_arrow_down, 0 }, XTERM_MODS_GT("1;", "B", ATOM_arrow_down), { "\033OB", ATOM_arrow_down, 0 }, { "\033[b", ATOM_arrow_down, MOD_SHIFT }, { "\033Ob", ATOM_arrow_down, MOD_CTRL }, { "\033\033[B", ATOM_arrow_down, MOD_ALT }, { "\033\033[b", ATOM_arrow_down, MOD_ALT | MOD_SHIFT }, { "\033\033Ob", ATOM_arrow_down, MOD_CTRL | MOD_ALT }, { "\033[1;1B", ATOM_arrow_down, MOD_ALTGR }, { "\033[D", ATOM_arrow_left, 0 }, XTERM_MODS_GT("1;", "D", ATOM_arrow_left), { "\033OD", ATOM_arrow_left, 0 }, { "\033[d", ATOM_arrow_left, MOD_SHIFT }, { "\033Od", ATOM_arrow_left, MOD_CTRL }, { "\033\033[D", ATOM_arrow_left, MOD_ALT }, { "\033\033[d", ATOM_arrow_left, MOD_ALT | MOD_SHIFT }, { "\033\033Od", ATOM_arrow_left, MOD_CTRL | MOD_ALT }, { "\033[1;1D", ATOM_arrow_left, MOD_ALTGR }, { "\033[C", ATOM_arrow_right, 0 }, XTERM_MODS_GT("1;", "C", ATOM_arrow_right), { "\033OC", ATOM_arrow_right, 0 }, { "\033[c", ATOM_arrow_right, MOD_SHIFT }, { "\033Oc", ATOM_arrow_right, MOD_CTRL }, { "\033\033[C", ATOM_arrow_right, MOD_ALT }, { "\033\033[c", ATOM_arrow_right, MOD_ALT | MOD_SHIFT }, { "\033\033Oc", ATOM_arrow_right, MOD_CTRL | MOD_ALT }, { "\033[1;1C", ATOM_arrow_right, MOD_ALTGR }, { "\033[A", ATOM_arrow_up, 0 }, XTERM_MODS_GT("1;", "A", ATOM_arrow_up), { "\033OA", ATOM_arrow_up, 0 }, { "\033[a", ATOM_arrow_up, MOD_SHIFT }, { "\033Oa", ATOM_arrow_up, MOD_CTRL }, { "\033\033[A", ATOM_arrow_up, MOD_ALT }, { "\033\033[a", ATOM_arrow_up, MOD_ALT | MOD_SHIFT }, { "\033\033Oa", ATOM_arrow_up, MOD_CTRL | MOD_ALT }, { "\033[1;1A", ATOM_arrow_up, MOD_ALTGR }, { "\033Oo", ATOM_numpad_divide, 0 }, { "\033\033Oo", ATOM_numpad_divide, MOD_ALT }, XTERM_MODS("\033O", "o", ATOM_numpad_divide), // ctrl-alt (not shifted) not reachable in xterm { "\033Oj", ATOM_numpad_multiply, 0 }, { "\033\033Oj", ATOM_numpad_multiply, MOD_ALT }, XTERM_MODS("\033O", "j", ATOM_numpad_multiply), // ctrl-alt (not shifted) not reachable in xterm { "\033Om", ATOM_numpad_subtract, 0 }, { "\033\033Om", ATOM_numpad_subtract, MOD_ALT }, XTERM_MODS("\033O", "m", ATOM_numpad_subtract), // ctrl-alt (not shifted) not reachable in xterm { "\033Ok", ATOM_numpad_add, 0 }, { "\033\033Ok", ATOM_numpad_add, MOD_ALT }, XTERM_MODS("\033O", "k", ATOM_numpad_add), // ctrl-alt (not shifted) not reachable in xterm { "\033OM", ATOM_numpad_enter, 0 }, { "\033\033OM", ATOM_numpad_enter, MOD_ALT }, XTERM_MODS("\033O", "M", ATOM_numpad_enter), //{ "\033[3~", ATOM_numpad_decimal, 0 }, //XTERM_MODS("\033[3;", "~", ATOM_numpad_decimal), // shifted combinations produce other codes in xterm { "\033O2l", ATOM_numpad_decimal, MOD_SHIFT }, { "\033O3l", ATOM_numpad_decimal, MOD_ALT }, { "\033O5l", ATOM_numpad_decimal, MOD_CTRL }, { "\033O6l", ATOM_numpad_decimal, MOD_CTRL | MOD_SHIFT }, { "\033O4l", ATOM_numpad_decimal, MOD_ALT | MOD_SHIFT }, { "\033O7l", ATOM_numpad_decimal, MOD_CTRL | MOD_ALT }, { "\033O8l", ATOM_numpad_decimal, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033Ol", ATOM_numpad_decimal, MOD_ALT }, { "\033On", ATOM_numpad_decimal, 0 }, { "\033\033On",ATOM_numpad_decimal, MOD_ALT }, { "\033O2p", ATOM_numpad0, MOD_SHIFT }, { "\033O3p", ATOM_numpad0, MOD_ALT }, { "\033O5p", ATOM_numpad0, MOD_CTRL }, { "\033O6p", ATOM_numpad0, MOD_CTRL | MOD_SHIFT }, { "\033O4p", ATOM_numpad0, MOD_ALT | MOD_SHIFT }, { "\033O7p", ATOM_numpad0, MOD_CTRL | MOD_ALT }, { "\033O8p", ATOM_numpad0, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033Op", ATOM_numpad0, 0 }, { "\033\033Op",ATOM_numpad0, MOD_ALT }, { "\033O2q", ATOM_numpad1, MOD_SHIFT }, { "\033O3q", ATOM_numpad1, MOD_ALT }, { "\033O5q", ATOM_numpad1, MOD_CTRL }, { "\033O6q", ATOM_numpad1, MOD_CTRL | MOD_SHIFT }, { "\033O4q", ATOM_numpad1, MOD_ALT | MOD_SHIFT }, { "\033O7q", ATOM_numpad1, MOD_CTRL | MOD_ALT }, { "\033O8q", ATOM_numpad1, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033Oq", ATOM_numpad1, 0 }, { "\033\033Oq",ATOM_numpad1, MOD_ALT }, { "\033O2r", ATOM_numpad2, MOD_SHIFT }, { "\033O3r", ATOM_numpad2, MOD_ALT }, { "\033O5r", ATOM_numpad2, MOD_CTRL }, { "\033O6r", ATOM_numpad2, MOD_CTRL | MOD_SHIFT }, { "\033O4r", ATOM_numpad2, MOD_ALT | MOD_SHIFT }, { "\033O7r", ATOM_numpad2, MOD_CTRL | MOD_ALT }, { "\033O8r", ATOM_numpad2, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033Or", ATOM_numpad2, 0 }, { "\033\033Or",ATOM_numpad2, MOD_ALT }, { "\033O2s", ATOM_numpad3, MOD_SHIFT }, { "\033O3s", ATOM_numpad3, MOD_ALT }, { "\033O5s", ATOM_numpad3, MOD_CTRL }, { "\033O6s", ATOM_numpad3, MOD_CTRL | MOD_SHIFT }, { "\033O4s", ATOM_numpad3, MOD_ALT | MOD_SHIFT }, { "\033O7s", ATOM_numpad3, MOD_CTRL | MOD_ALT }, { "\033O8s", ATOM_numpad3, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033Os", ATOM_numpad3, 0 }, { "\033\033Os",ATOM_numpad3, MOD_ALT }, { "\033O2t", ATOM_numpad4, MOD_SHIFT }, { "\033O3t", ATOM_numpad4, MOD_ALT }, { "\033O5t", ATOM_numpad4, MOD_CTRL }, { "\033O6t", ATOM_numpad4, MOD_CTRL | MOD_SHIFT }, { "\033O4t", ATOM_numpad4, MOD_ALT | MOD_SHIFT }, { "\033O7t", ATOM_numpad4, MOD_CTRL | MOD_ALT }, { "\033O8t", ATOM_numpad4, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033Ot", ATOM_numpad4, 0 }, { "\033\033Ot",ATOM_numpad4, MOD_ALT }, { "\033O2u", ATOM_numpad5, MOD_SHIFT }, { "\033O3u", ATOM_numpad5, MOD_ALT }, { "\033O5u", ATOM_numpad5, MOD_CTRL }, { "\033O6u", ATOM_numpad5, MOD_CTRL | MOD_SHIFT }, { "\033O4u", ATOM_numpad5, MOD_ALT | MOD_SHIFT }, { "\033O7u", ATOM_numpad5, MOD_CTRL | MOD_ALT }, { "\033O8u", ATOM_numpad5, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033Ou", ATOM_numpad5, 0 }, { "\033\033Ou",ATOM_numpad5, MOD_ALT }, { "\033[E", ATOM_numpad5, 0 }, XTERM_MODS_GT("1;", "E", ATOM_numpad5), { "\033OE", ATOM_numpad5, 0 }, { "\033[G", ATOM_numpad5, 0 }, { "\033O2v", ATOM_numpad6, MOD_SHIFT }, { "\033O3v", ATOM_numpad6, MOD_ALT }, { "\033O5v", ATOM_numpad6, MOD_CTRL }, { "\033O6v", ATOM_numpad6, MOD_CTRL | MOD_SHIFT }, { "\033O4v", ATOM_numpad6, MOD_ALT | MOD_SHIFT }, { "\033O7v", ATOM_numpad6, MOD_CTRL | MOD_ALT }, { "\033O8v", ATOM_numpad6, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033Ov", ATOM_numpad6, 0 }, { "\033\033Ov",ATOM_numpad6, MOD_ALT }, { "\033O2w", ATOM_numpad7, MOD_SHIFT }, { "\033O3w", ATOM_numpad7, MOD_ALT }, { "\033O5w", ATOM_numpad7, MOD_CTRL }, { "\033O6w", ATOM_numpad7, MOD_CTRL | MOD_SHIFT }, { "\033O4w", ATOM_numpad7, MOD_ALT | MOD_SHIFT }, { "\033O7w", ATOM_numpad7, MOD_CTRL | MOD_ALT }, { "\033O8w", ATOM_numpad7, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033Ow", ATOM_numpad7, 0 }, { "\033\033Ow",ATOM_numpad7, MOD_ALT }, { "\033O2x", ATOM_numpad8, MOD_SHIFT }, { "\033O3x", ATOM_numpad8, MOD_ALT }, { "\033O5x", ATOM_numpad8, MOD_CTRL }, { "\033O6x", ATOM_numpad8, MOD_CTRL | MOD_SHIFT }, { "\033O4x", ATOM_numpad8, MOD_ALT | MOD_SHIFT }, { "\033O7x", ATOM_numpad8, MOD_CTRL | MOD_ALT }, { "\033O8x", ATOM_numpad8, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033Ox", ATOM_numpad8, 0 }, { "\033\033Ox",ATOM_numpad8, MOD_ALT }, { "\033O2y", ATOM_numpad9, MOD_SHIFT }, { "\033O3y", ATOM_numpad9, MOD_ALT }, { "\033O5y", ATOM_numpad9, MOD_CTRL }, { "\033O6y", ATOM_numpad9, MOD_CTRL | MOD_SHIFT }, { "\033O4y", ATOM_numpad9, MOD_ALT | MOD_SHIFT }, { "\033O7y", ATOM_numpad9, MOD_CTRL | MOD_ALT }, { "\033O8y", ATOM_numpad9, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033Oy", ATOM_numpad9, 0 }, { "\033\033Oy",ATOM_numpad9, MOD_ALT }, // { "\033", ATOM_escape, }, via special case in code (also Ctrl-[ in traditional mode) XTERM_MODS("\033[27;", ";27~", ATOM_escape), // modifiy other keys mode XTERM_MODS("\033[27;", "u", ATOM_escape), // modifiy other keys mode { "\033\033", ATOM_escape, MOD_ALT }, { "\033OP", ATOM_f1, 0 }, XTERM_MODS_GT("1;", "P", ATOM_f1), XTERM_MODS("\033O", "P", ATOM_f1), { "\033[[A", ATOM_f1, 0 }, { "\033[25~", ATOM_f1, MOD_SHIFT }, { "\033[25^", ATOM_f1, MOD_CTRL | MOD_SHIFT }, { "\033\033[25~", ATOM_f1, MOD_ALT | MOD_SHIFT }, { "\033\033[25^", ATOM_f1, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033O1P", ATOM_f1, MOD_ALTGR }, { "\033[11~", ATOM_f1, 0 }, { "\033[11^", ATOM_f1, MOD_CTRL }, { "\033\033[11~", ATOM_f1, MOD_ALT }, { "\033\033[11^", ATOM_f1, MOD_CTRL | MOD_ALT }, { "\033OQ", ATOM_f2, 0 }, XTERM_MODS_GT("1;", "Q", ATOM_f2), XTERM_MODS("\033O", "Q", ATOM_f2), { "\033[[B", ATOM_f2, 0 }, { "\033[26~", ATOM_f2, MOD_SHIFT }, { "\033[26^", ATOM_f2, MOD_CTRL | MOD_SHIFT }, { "\033\033[26~", ATOM_f2, MOD_ALT | MOD_SHIFT }, { "\033\033[26^", ATOM_f2, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033O1Q", ATOM_f2, MOD_ALTGR }, { "\033[12~", ATOM_f2, 0 }, { "\033[12^", ATOM_f2, MOD_CTRL }, { "\033\033[12~", ATOM_f2, MOD_ALT }, { "\033\033[12^", ATOM_f2, MOD_CTRL | MOD_ALT }, { "\033OR", ATOM_f3, 0 }, XTERM_MODS_GT("1;", "R", ATOM_f3), XTERM_MODS("\033O", "R", ATOM_f3), { "\033[[C", ATOM_f3, 0 }, { "\033[28~", ATOM_f3, MOD_SHIFT }, { "\033[28^", ATOM_f3, MOD_CTRL | MOD_SHIFT }, { "\033\033[28~", ATOM_f3, MOD_ALT | MOD_SHIFT }, { "\033\033[28^", ATOM_f3, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033O1R", ATOM_f3, MOD_ALTGR }, { "\033[13~", ATOM_f3, 0 }, { "\033[13^", ATOM_f3, MOD_CTRL }, { "\033\033[13~", ATOM_f3, MOD_ALT }, { "\033\033[13^", ATOM_f3, MOD_CTRL | MOD_ALT }, { "\033OS", ATOM_f4, 0 }, XTERM_MODS_GT("1;", "S", ATOM_f4), XTERM_MODS("\033O", "S", ATOM_f4), { "\033[[D", ATOM_f4, 0 }, { "\033O1S", ATOM_f4, MOD_ALTGR }, { "\033[14~", ATOM_f4, 0 }, { "\033[14^", ATOM_f4, MOD_CTRL }, { "\033\033[14~", ATOM_f4, MOD_ALT }, { "\033\033[14^", ATOM_f4, MOD_CTRL | MOD_ALT }, { "\033[29^", ATOM_f4, MOD_CTRL | MOD_SHIFT }, { "\033\033[29~", ATOM_f4, MOD_ALT | MOD_SHIFT }, { "\033\033[29^", ATOM_f4, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033[15~", ATOM_f5, 0 }, { "\033[15^", ATOM_f5, MOD_CTRL }, { "\033\033[15~", ATOM_f5, MOD_ALT }, { "\033\033[15^", ATOM_f5, MOD_CTRL | MOD_ALT }, XTERM_MODS_GT("15;", "~", ATOM_f5), { "\033[[E", ATOM_f5, 0 }, { "\033[31~", ATOM_f5, MOD_SHIFT }, { "\033[31^", ATOM_f5, MOD_CTRL | MOD_SHIFT }, { "\033\033[31~", ATOM_f5, MOD_ALT | MOD_SHIFT }, { "\033\033[31^", ATOM_f5, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033[15;1~", ATOM_f5, MOD_ALTGR }, { "\033[17~", ATOM_f6, 0 }, { "\033[17^", ATOM_f6, MOD_CTRL }, { "\033\033[17~", ATOM_f6, MOD_ALT }, { "\033\033[17^", ATOM_f6, MOD_CTRL | MOD_ALT }, XTERM_MODS_GT("17;", "~", ATOM_f6), { "\033[32~", ATOM_f6, MOD_SHIFT }, { "\033[32^", ATOM_f6, MOD_CTRL | MOD_SHIFT }, { "\033\033[32~", ATOM_f6, MOD_ALT | MOD_SHIFT }, { "\033\033[32^", ATOM_f6, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033[17;1~", ATOM_f6, MOD_ALTGR }, { "\033[18~", ATOM_f7, 0 }, { "\033[18^", ATOM_f7, MOD_CTRL }, { "\033\033[18~", ATOM_f7, MOD_ALT }, { "\033\033[18^", ATOM_f7, MOD_CTRL | MOD_ALT }, XTERM_MODS_GT("18;", "~", ATOM_f7), { "\033[33~", ATOM_f7, MOD_SHIFT }, { "\033[33^", ATOM_f7, MOD_CTRL | MOD_SHIFT }, { "\033\033[33~", ATOM_f7, MOD_ALT | MOD_SHIFT }, { "\033\033[33^", ATOM_f7, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033[18;1~", ATOM_f7, MOD_ALTGR }, { "\033[19~", ATOM_f8, 0 }, { "\033[19^", ATOM_f8, MOD_CTRL }, { "\033\033[19~", ATOM_f8, MOD_ALT }, { "\033\033[19^", ATOM_f8, MOD_CTRL | MOD_ALT }, XTERM_MODS_GT("19;", "~", ATOM_f8), { "\033[34~", ATOM_f8, MOD_SHIFT }, { "\033[34^", ATOM_f8, MOD_CTRL | MOD_SHIFT }, { "\033\033[34~", ATOM_f8, MOD_ALT | MOD_SHIFT }, { "\033\033[34^", ATOM_f8, MOD_CTRL | MOD_ALT | MOD_SHIFT }, { "\033[19;1~", ATOM_f8, MOD_ALTGR }, { "\033[20~", ATOM_f9, 0 }, { "\033[20^", ATOM_f9, MOD_CTRL }, { "\033\033[20~", ATOM_f9, MOD_ALT }, { "\033\033[20^", ATOM_f9, MOD_CTRL | MOD_ALT }, XTERM_MODS_GT("20;", "~", ATOM_f9), { "\033[20;1~", ATOM_f9, MOD_ALTGR }, { "\033[21~", ATOM_f10, 0 }, { "\033[21^", ATOM_f10, MOD_CTRL }, { "\033\033[21~", ATOM_f10, MOD_ALT }, { "\033\033[21^", ATOM_f10, MOD_CTRL | MOD_ALT }, XTERM_MODS_GT("21;", "~", ATOM_f10), { "\033[21;1~", ATOM_f10, MOD_ALTGR }, { "\033[23~", ATOM_f11, 0 }, { "\033[23$", ATOM_f11, MOD_SHIFT }, { "\033[23^", ATOM_f11, MOD_CTRL }, { "\033[23@", ATOM_f11, MOD_CTRL | MOD_SHIFT }, { "\033\033[23~", ATOM_f11, MOD_ALT }, { "\033\033[23$", ATOM_f11, MOD_ALT | MOD_SHIFT }, { "\033\033[23^", ATOM_f11, MOD_CTRL | MOD_ALT }, { "\033\033[23@", ATOM_f11, MOD_CTRL | MOD_ALT | MOD_SHIFT }, XTERM_MODS_GT("23;", "~", ATOM_f11), { "\033[23;1~", ATOM_f11, MOD_ALTGR }, { "\033[24~", ATOM_f12, 0 }, { "\033[24$", ATOM_f12, MOD_SHIFT }, { "\033[24^", ATOM_f12, MOD_CTRL }, { "\033[24@", ATOM_f12, MOD_CTRL | MOD_SHIFT }, { "\033\033[24~", ATOM_f12, MOD_ALT }, { "\033\033[24$", ATOM_f12, MOD_ALT | MOD_SHIFT }, { "\033\033[24^", ATOM_f12, MOD_CTRL | MOD_ALT }, { "\033\033[24@", ATOM_f12, MOD_CTRL | MOD_ALT | MOD_SHIFT }, XTERM_MODS_GT("24;", "~", ATOM_f12), { "\033[24;1~", ATOM_f12, MOD_ALTGR }, //{ "", ATOM_, 0 }, //{ "", ATOM_, 0 }, //{ "", ATOM_, 0 }, { "\x01", "a", MOD_CTRL | MOD_PRINT }, { "\033\x01", "a", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x02", "b", MOD_CTRL | MOD_PRINT }, { "\033\x02", "b", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x03", "c", MOD_CTRL | MOD_PRINT }, { "\033\x03", "c", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x04", "d", MOD_CTRL | MOD_PRINT }, { "\033\x04", "d", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x05", "e", MOD_CTRL | MOD_PRINT }, { "\033\x05", "e", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x06", "f", MOD_CTRL | MOD_PRINT }, { "\033\x06", "f", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x07", "g", MOD_CTRL | MOD_PRINT }, { "\033\x07", "g", MOD_CTRL | MOD_ALT | MOD_PRINT }, //{ "\x08", "h", MOD_CTRL | MOD_PRINT }, //+ also ctrl-Backspace //{ "\033\x08", "h", MOD_CTRL | MOD_ALT | MOD_PRINT }, //+ also ctrl-alt-Backspace (which might not be usable as xorg binds zap to it) //{ "\x09", "i", MOD_PRINT }, //+ also Tab, Ctrl-Tab //{ "\033\x09", "i", MOD_ALT | MOD_PRINT }, //+ also Alt-Tab { "\x0a", "j", MOD_CTRL | MOD_PRINT }, { "\033\x0a", "j", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x0b", "k", MOD_CTRL | MOD_PRINT }, { "\033\x0b", "k", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x0c", "l", MOD_CTRL | MOD_PRINT }, { "\033\x0c", "l", MOD_CTRL | MOD_ALT | MOD_PRINT }, //{ "\x0d", "m", MOD_CTRL | MOD_PRINT }, //+ also Return, Ctrl-Return //{ "\033\x0d", "m", MOD_CTRL | MOD_ALT | MOD_PRINT }, //+ also alt-Return, alt-Ctrl-Return { "\x0e", "n", MOD_CTRL | MOD_PRINT }, { "\033\x0e", "n", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x0f", "o", MOD_CTRL | MOD_PRINT }, { "\033\x0f", "o", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x10", "p", MOD_CTRL | MOD_PRINT }, { "\033\x10", "p", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x11", "q", MOD_CTRL | MOD_PRINT }, { "\033\x11", "q", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x12", "r", MOD_CTRL | MOD_PRINT }, { "\033\x12", "r", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x13", "s", MOD_CTRL | MOD_PRINT }, { "\033\x13", "s", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x14", "t", MOD_CTRL | MOD_PRINT }, { "\033\x14", "t", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x15", "u", MOD_CTRL | MOD_PRINT }, { "\033\x15", "u", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x16", "v", MOD_CTRL | MOD_PRINT }, { "\033\x16", "v", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x17", "w", MOD_CTRL | MOD_PRINT }, { "\033\x17", "w", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x18", "x", MOD_CTRL | MOD_PRINT }, { "\033\x18", "x", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x19", "y", MOD_CTRL | MOD_PRINT }, { "\033\x19", "y", MOD_CTRL | MOD_ALT | MOD_PRINT }, { "\x1a", "z", MOD_CTRL | MOD_PRINT }, { "\033\x1a", "z", MOD_CTRL | MOD_ALT | MOD_PRINT }, //{ "\x1b", "[", MOD_CTRL | MOD_PRINT }, //+ also ESC //+ also ctrl-3 { "\x1c", "\\", MOD_CTRL | MOD_PRINT }, { "\033\x1c", "\\", MOD_CTRL | MOD_ALT | MOD_PRINT }, //+ also ctrl-4 { "\x1d", "]", MOD_CTRL | MOD_PRINT }, //+ also ctrl-5 { "\033\x1d", "]", MOD_CTRL | MOD_ALT | MOD_PRINT }, //+ also alt-ctrl-5 { "\x1e", "~", MOD_CTRL | MOD_PRINT }, //+ also ctrl-6 { "\033\x1e", "~", MOD_CTRL | MOD_ALT | MOD_PRINT }, //+ also alt-ctrl-6 { "\x1f", "?", MOD_CTRL | MOD_PRINT }, { "\033\x1f", "?", MOD_CTRL | MOD_ALT | MOD_PRINT }, //+ also ctrl-7 { "\x7f", ATOM_backspace, 0 }, { "\x08", ATOM_backspace, MOD_CTRL }, { "\033\x08", ATOM_backspace, MOD_CTRL | MOD_ALT }, { "\033\x7f", ATOM_backspace, MOD_ALT }, XTERM_MODS("\033[27;", ";127~", ATOM_backspace), // modifiy other keys mode XTERM_MODS("\033[127;", "u", ATOM_backspace), // modifiy other keys mode XTERM_MODS("\033[27;", ";8~", ATOM_backspace), // modifiy other keys mode XTERM_MODS("\033[8;", "u", ATOM_backspace), // modifiy other keys mode { 0, 0, 0 } }; void termpaintp_input_selfcheck(void) { static bool finished; if (finished) return; bool ok = true; for (const key_mapping_entry* entry_a = key_mapping_table; entry_a->sequence != nullptr; entry_a++) { for (const key_mapping_entry* entry_b = entry_a; entry_b->sequence != nullptr; entry_b++) { if (entry_a != entry_b && strcmp(entry_a->sequence, entry_b->sequence) == 0) { printf("Duplicate key mapping: %s == %s\n", entry_a->atom, entry_b->atom); ok = false; } } } if (!ok) { exit(55); } finished = true; } void termpaintp_input_dump_table(void) { FILE * f = fopen("input.dump", "w"); if (!f) { abort(); } for (const key_mapping_entry* entry_a = key_mapping_table; entry_a->sequence != nullptr; entry_a++) { fputs(entry_a->sequence, f); fputs("\n", f); } fclose(f); } #define MAX_SEQ_LENGTH 1024 enum termpaint_input_state { tpis_base, tpis_esc, tpis_ss3, tpis_csi, tpis_cmd_str, tpis_cmd_str_c1, tpis_str_terminator_esc, tpid_utf8_5, tpid_utf8_4, tpid_utf8_3, tpid_utf8_2, tpid_utf8_1, tpis_mouse_btn, tpis_mouse_col, tpis_mouse_row }; struct termpaint_input_ { unsigned char buff[MAX_SEQ_LENGTH]; int used; enum termpaint_input_state state; _Bool overflow; _Bool esc_pending; int expect_cursor_position_report; _Bool expect_mouse_char_mode; _Bool expect_mouse_multibyte_mode; _Bool expect_apc; _Bool in_paste; _Bool handle_paste; int quirks_len; key_mapping_entry *quirks; _Bool extended_unicode; _Bool (*raw_filter_cb)(void *user_data, const char *data, unsigned length, _Bool overflow); void *raw_filter_user_data; void (*event_cb)(void *, termpaint_event *); void *event_user_data; }; static void termpaintp_input_reset(termpaint_input *ctx) { ctx->used = 0; ctx->overflow = 0; ctx->state = tpis_base; } static bool termpaintp_input_checked_append_digit(int *to_update, int base, int value) { int tmp; if (termpaint_smul_overflow(*to_update, base, &tmp)) { return false; } if (termpaint_sadd_overflow(tmp, value, to_update)) { return false; } return true; } static bool termpaintp_input_parse_mb_3(const unsigned char *data, size_t length, int *a, int *b, int *c) { if (length < 3) { // three values -> at least 3 bytes return false; } const size_t len_a = termpaintp_utf8_len(data[0]); if (len_a >= length // including first byte of b || !termpaintp_check_valid_sequence(data, len_a)) { return false; } *a = termpaintp_utf8_decode_from_utf8(data, len_a); const size_t len_b = termpaintp_utf8_len(data[len_a]); if (len_a + len_b >= length // including first byte of c || !termpaintp_check_valid_sequence(data + len_a, len_b)) { return false; } *b = termpaintp_utf8_decode_from_utf8(data + len_a, len_b); const size_t len_c = termpaintp_utf8_len(data[len_a + len_b]); if (len_a + len_b + len_c != length // don't allow trailing garbage || !termpaintp_check_valid_sequence(data + len_a + len_b, len_c)) { return false; } *c = termpaintp_utf8_decode_from_utf8(data + len_a + len_b, len_c); return true; } static void termpaintp_input_translate_mouse_flags(termpaint_event* event, int mode) { // mode = 0 -> release if button is 3 (all modes except 1006) // mode = 1 -> release from final (mode 1006 with 'm' as final) // mode = 2 -> press from final (mode 1006 with 'M' as final) // shuffle the bits from the raw button and flags event->mouse.button = event->mouse.raw_btn_and_flags & 0x3; if (event->mouse.raw_btn_and_flags & 0x40) { event->mouse.button |= 4; } if (event->mouse.raw_btn_and_flags & 0x80) { event->mouse.button |= 8; } event->mouse.modifier = 0; if (event->mouse.raw_btn_and_flags & 0x4) { event->mouse.modifier |= TERMPAINT_MOD_SHIFT; } if (event->mouse.raw_btn_and_flags & 0x8) { event->mouse.modifier |= TERMPAINT_MOD_ALT; } if (event->mouse.raw_btn_and_flags & 0x10) { event->mouse.modifier |= TERMPAINT_MOD_CTRL; } if (event->mouse.raw_btn_and_flags & 0x20) { event->mouse.action = TERMPAINT_MOUSE_MOVE; } else if (mode == 0) { event->mouse.action = event->mouse.button != 3 ? TERMPAINT_MOUSE_PRESS : TERMPAINT_MOUSE_RELEASE; } else if (mode == 1) { event->mouse.action = TERMPAINT_MOUSE_RELEASE; } else { event->mouse.action = TERMPAINT_MOUSE_PRESS; } } static void termpaintp_input_raw(termpaint_input *ctx, const unsigned char *data, size_t length, _Bool overflow) { unsigned char dbl_esc_tmp[21]; // First handle double escape for alt-ESC if (overflow) { // overflow just reset to base state. ctx->esc_pending = false; } else { if (!ctx->esc_pending) { if (length == 1 && data[0] == '\033') { // skip processing this, either next key or resync will trigger real handling ctx->esc_pending = true; return; } } else { ctx->esc_pending = false; bool found = false; if (length + 1 < sizeof (dbl_esc_tmp)) { dbl_esc_tmp[0] = '\033'; memcpy(dbl_esc_tmp + 1, data, length); for (const key_mapping_entry* entry = key_mapping_table; entry->sequence != nullptr; entry++) { if (strlen(entry->sequence) == length + 1 && memcmp(entry->sequence, dbl_esc_tmp, length + 1) == 0) { found = true; } } } if (found) { // alt-, this is just one event length += 1; data = dbl_esc_tmp; } else { // something else, two events if (ctx->raw_filter_cb && ctx->raw_filter_cb(ctx->raw_filter_user_data, (const char *)"\033", 1, false)) { ; // skipped by raw filter } else if (ctx->event_cb) { termpaint_event event; event.type = TERMPAINT_EV_KEY; event.key.length = strlen(ATOM_escape); event.key.atom = ATOM_escape; event.key.modifier = 0; ctx->event_cb(ctx->event_user_data, &event); } } } } if (ctx->raw_filter_cb) { if (ctx->raw_filter_cb(ctx->raw_filter_user_data, (const char *)data, length, overflow)) { return; } } if (!ctx->event_cb) { return; } unsigned char buffer[6]; termpaint_event event; event.type = 0; if (overflow) { event.type = TERMPAINT_EV_OVERFLOW; /*event.length = 0; event.atom_or_string = 0; event.modifier = 0;*/ } else if (length == 0) { // length == 0 should only be possible with overflow. Bailing here removes some conditions later. return; } else if (length == 1 && data[0] == 0) { event.type = TERMPAINT_EV_KEY; event.key.length = strlen(ATOM_space); event.key.atom = ATOM_space; event.key.modifier = MOD_CTRL; } else if (length == 2 && data[0] == '\033' && data[1] == 0) { event.type = TERMPAINT_EV_KEY; event.key.length = strlen(ATOM_space); event.key.atom = ATOM_space; event.key.modifier = MOD_CTRL | MOD_ALT; } else { const key_mapping_entry* matched_entry = nullptr; for (int i = 0; i < ctx->quirks_len; i++) { const key_mapping_entry* entry = &ctx->quirks[i]; if (strlen(entry->sequence) == length && memcmp(entry->sequence, data, length) == 0) { matched_entry = entry; break; } } // TODO optimize if (!matched_entry) { for (const key_mapping_entry* entry = key_mapping_table; entry->sequence != nullptr; entry++) { if (strlen(entry->sequence) == length && memcmp(entry->sequence, data, length) == 0) { matched_entry = entry; break; } } } if (matched_entry) { if (matched_entry->modifiers & MOD_PRINT) { // special case for ctrl-X which is in the table but a modified printable event.type = TERMPAINT_EV_CHAR; event.c.length = strlen(matched_entry->atom); event.c.string = matched_entry->atom; event.c.modifier = matched_entry->modifiers & ~MOD_PRINT; } else { event.type = TERMPAINT_EV_KEY; event.key.length = strlen(matched_entry->atom); event.key.atom = matched_entry->atom; event.key.modifier = matched_entry->modifiers; } } if (length == 4 && data[0] == '\033' && data[1] == '[' && data[2] == '0' && data[3] == 'n') { event.type = TERMPAINT_EV_MISC; event.misc.atom = ATOM_i_resync; event.misc.length = strlen(ATOM_i_resync); } if (!event.type && length >= 2 && data[0] == '\033' && (0xc0 == (0xc0 & data[1]))) { // tokenizer can only abort on invalid utf-8 sequences, so now recheck and issue a distinct event type event.type = termpaintp_check_valid_sequence(data+1, length - 1) ? TERMPAINT_EV_CHAR : TERMPAINT_EV_INVALID_UTF8; if (length - 1 > 4 && !ctx->extended_unicode) { event.type = TERMPAINT_EV_INVALID_UTF8; } event.c.length = length-1; event.c.string = (const char*)data+1; event.c.modifier = MOD_ALT; } if (!event.type && length == 2 && data[0] == '\033' && data[1] > 32 && data[1] < 127) { event.type = TERMPAINT_EV_CHAR; event.c.length = length-1; event.c.string = (const char*)data+1; event.c.modifier = MOD_ALT; } if (!event.type && (0xc0 == (0xc0 & data[0]))) { // tokenizer can only abort on invalid utf-8 sequences, so now recheck and issue a distinct event type event.type = termpaintp_check_valid_sequence(data, length) ? TERMPAINT_EV_CHAR : TERMPAINT_EV_INVALID_UTF8; if (length > 4 && !ctx->extended_unicode) { event.type = TERMPAINT_EV_INVALID_UTF8; } event.c.length = length; event.c.string = (const char*)data; event.c.modifier = 0; } if (!event.type && length == 1 && data[0] > 32 && data[0] < 127) { event.type = TERMPAINT_EV_CHAR; event.c.length = length; event.c.string = (const char*)data; event.c.modifier = 0; } if (length > 2 && data[0] == '\033' && data[1] == '[') { int params_start = 2; int params_len = length - 3; // scan for shape char prefix_modifier = 0; char postfix_modifier = 0; char final = 0; const int default_arg = -1; int args[10] = { 0 }; bool has_sub_args = false; const int max_args = sizeof(args) / sizeof(*args); int arg_count = 0; enum state_t { S_initial, S_main_param, S_sub_param, S_ignore } state = S_initial; bool ok = true; for (size_t j = 2; j < length; j++) { if ('0' <= data[j] && data[j] <= '9') { if (state == S_initial) { if (arg_count >= max_args) { state = S_ignore; } else { state = S_main_param; ++arg_count; } } if (state == S_main_param) { if (!termpaintp_input_checked_append_digit(&args[arg_count - 1], 10, data[j] - '0')) { // parameter out of range state = S_ignore; ok = false; } } } else if (':' == data[j]) { has_sub_args = true; if (state == S_initial) { if (arg_count >= max_args) { state = S_ignore; } else { ++arg_count; args[arg_count - 1] = default_arg; state = S_sub_param; } } else if (state == S_main_param) { state = S_sub_param; } } else if (data[j] == ';') { if (state == S_initial) { if (arg_count >= max_args) { state = S_ignore; } else { ++arg_count; args[arg_count - 1] = default_arg; state = S_initial; } } else if (state == S_main_param) { state = S_initial; } else if (state == S_sub_param) { state = S_initial; } } else if ('<' <= data[j] && data[j] <= '?') { // prefix modifier if (j == 2) { // at the very beginning prefix_modifier = data[j]; ++params_start; --params_len; } else { // at an unexpected place ok = false; break; } } else if (' ' <= data[j] && data[j] <= '/') { // postfix modifier if (j == length - 2) { // just before final character postfix_modifier = data[j]; --params_len; } else { // at an unexpected place ok = false; break; } } else if ('@' <= data[j] && data[j] <= 127) { // final character if (j == length - 1) { // and actually in the final byte final = data[j]; } else { // at an unexpected place ok = false; break; } } else { ok = false; break; } } if (state == S_initial && arg_count > 0 && arg_count < max_args) { ++arg_count; args[arg_count - 1] = default_arg; } #define SEQ(f, pre, post) (((pre) << 16) | ((post) << 8) | (f)) int32_t sequence_id = ok ? SEQ(final, prefix_modifier, postfix_modifier) : 0; // the CSI sequence is just a prefix in legacy mouse modes. if (!event.type && length >= 6 && data[2] == 'M') { if (length == 6) { if (data[3] >= 32 && data[4] > 32 && data[5] > 32) { // only translate non overflow mouse reports (some terminals overflow into the C0 range, // ignore those too) event.type = TERMPAINT_EV_MOUSE; event.mouse.raw_btn_and_flags = data[3] - ' '; event.mouse.x = data[4] - '!'; event.mouse.y = data[5] - '!'; termpaintp_input_translate_mouse_flags(&event, 0); } } else { int x, y, btn; if (termpaintp_input_parse_mb_3(data + 3, length - 3, &btn, &x, &y)) { if (btn >= 32 && x > 32 && y > 32) { // here no overflow should be possible. But the substractions would yield negative values // otherwise event.mouse.raw_btn_and_flags = btn - ' '; event.mouse.x = x - '!'; event.mouse.y = y - '!'; termpaintp_input_translate_mouse_flags(&event, 0); event.type = TERMPAINT_EV_MOUSE; } } } } if (!event.type && sequence_id == SEQ('M', 0, 0) && length > 7) { // urxvt mouse mode 1015 if (arg_count == 3 && !has_sub_args) { int btn = args[0]; int x = args[1]; int y = args[2]; if (btn >= ' ' && x > 0 && y > 0) { event.type = TERMPAINT_EV_MOUSE; event.mouse.raw_btn_and_flags = btn - ' '; event.mouse.x = x - 1; event.mouse.y = y - 1; termpaintp_input_translate_mouse_flags(&event, 0); } } } if (!event.type && length > 8 && (sequence_id == SEQ('M', '<', 0) || sequence_id == SEQ('m', '<', 0))) { // mouse mode 1006 if (arg_count == 3 && !has_sub_args) { int btn = (args[0] != default_arg ? args[0] : 0); int x = args[1]; int y = args[2]; if (x > 0 && y > 0) { event.type = TERMPAINT_EV_MOUSE; event.mouse.raw_btn_and_flags = btn; event.mouse.x = x - 1; event.mouse.y = y - 1; termpaintp_input_translate_mouse_flags(&event, data[length - 1] == 'm' ? 1 : 2); } } } // the nice key modifier extensions: // \033[27;;~ // \033[;u if (!event.type && ((sequence_id == SEQ('~', 0, 0) && arg_count >= 3 && args[0] == 27) || (sequence_id == SEQ('u', 0, 0) && arg_count >= 2 && !has_sub_args))) { // see further down for other CSI ~ sequences int mod, codepoint; if (sequence_id == SEQ('u', 0, 0)) { codepoint = args[0]; mod = args[1]; } else { // ~ variant mod = args[1]; codepoint = args[2]; } if (mod == 0) { // This does not happen in tested terminals but // https://www.leonerd.org.uk/hacks/fixterms/ // implies we should just default to 1 mod = 1; } if (codepoint >= 32 && codepoint <= 0x7FFFFFFF && !(codepoint >= 0x80 && codepoint <= 0xa0) && codepoint != 0x7f) { event.type = TERMPAINT_EV_CHAR; event.c.length = termpaintp_encode_to_utf8(codepoint, buffer); event.c.string = (char*)buffer; event.c.modifier = 0; mod = mod - 1; if (mod & 1) { event.c.modifier |= MOD_SHIFT; } if (mod & 2) { event.c.modifier |= MOD_ALT; } if (mod & 4) { event.c.modifier |= MOD_CTRL; } } } if ((!event.type || ctx->expect_cursor_position_report > 0) && length > 5 && (sequence_id == SEQ('R', 0, 0) || sequence_id == SEQ('R', '?', 0))) { if (arg_count >= 2 && !has_sub_args) { int y = args[0]; int x = args[1]; if (x > 0 && y > 0) { event.type = TERMPAINT_EV_CURSOR_POSITION; event.cursor_position.x = x - 1; event.cursor_position.y = y - 1; if (prefix_modifier == 0) { ctx->expect_cursor_position_report -= 1; } event.cursor_position.safe = prefix_modifier == '?'; } } } if (!event.type && params_len == 0 && (sequence_id == SEQ('O', 0, 0) || sequence_id == SEQ('I', 0, 0))) { event.type = TERMPAINT_EV_MISC; event.misc.atom = sequence_id == SEQ('I', 0, 0) ? termpaint_input_focus_in() : termpaint_input_focus_out(); event.misc.length = strlen(event.misc.atom); } if (!event.type && sequence_id == SEQ('~', 0, 0)) { // see above for CSI 27;;~ if (arg_count >= 1 && !has_sub_args) { int num = args[0]; if (num == 200) { if (ctx->handle_paste) { ctx->in_paste = true; termpaint_event event2; event2.type = TERMPAINT_EV_PASTE; event2.paste.string = ""; event2.paste.length = 0; event2.paste.initial = true; event2.paste.final = false; ctx->event_cb(ctx->event_user_data, &event2); } else { event.type = TERMPAINT_EV_MISC; event.misc.atom = termpaint_input_paste_begin(); event.misc.length = strlen(event.misc.atom); } } else if (num == 201) { if (ctx->handle_paste) { ctx->in_paste = false; event.type = TERMPAINT_EV_PASTE; event.paste.string = ""; event.paste.length = 0; event.paste.initial = false; event.paste.final = true; } else { event.type = TERMPAINT_EV_MISC; event.misc.atom = termpaint_input_paste_end(); event.misc.length = strlen(event.misc.atom); } } } } if (!event.type) { if (length > 5 && (sequence_id == SEQ('y', 0, '$') || sequence_id == SEQ('y', '?', '$'))) { if (arg_count >= 2 && !has_sub_args) { int mode = (args[0] != default_arg ? args[0] : 0); int status = (args[1] != default_arg ? args[1] : 0); event.type = TERMPAINT_EV_MODE_REPORT; event.mode.number = mode; event.mode.kind = (prefix_modifier == '?') ? 1 : 0; event.mode.status = status; } } if (sequence_id == SEQ('c', '>', 0)) { event.type = TERMPAINT_EV_RAW_SEC_DEV_ATTRIB; event.raw.string = (const char*)data; event.raw.length = length; } if (sequence_id == SEQ('c', '?', 0)) { event.type = TERMPAINT_EV_RAW_PRI_DEV_ATTRIB; event.raw.string = (const char*)data; event.raw.length = length; } // prefix_modifier == '?' is possible here, VTE < 0.54 answers this to CSI 1x if (sequence_id == SEQ('x', 0, 0) || sequence_id == SEQ('x', '?', 0)) { event.type = TERMPAINT_EV_RAW_DECREQTPARM; event.raw.string = (const char*)data; event.raw.length = length; } } #undef SEQ } if (!event.type && length > 5 && data[0] == '\033' && data[1] == ']' && ((data[length-1] == '\\' && data[length-2] == '\033') || (data[length-1] == '\x07') || (data[length-1] == 0x9c))) { // OSC sequences size_t st_offset; if (data[length-1] == '\\') { st_offset = length - 2; } else { st_offset = length - 1; } int num; // -1 -> not a numerical OSC size_t num_end = 0; if ('0' <= data[2] && data[2] <= '9') { num = 0; for (size_t i = 2; i < st_offset; i++) { if (data[i] == ';') { num_end = i; // finished break; } else if ('0' <= data[i] && data[i] <= '9') { if (!termpaintp_input_checked_append_digit(&num, 10, data[i] - '0')) { num = -1; break; } } else { // bail num = -1; break; } } } else { num = -1; } if (num_end && num == 4) { event.palette_color_report.color_index = 0; // the normal report has the form OSC 4 ; color_index ; color_desc ST // but at least urxvt does send an different form of OSC 4 ; color_desc ST // (set color_index == -1 in the event in that case) bool color_index_ok = true; size_t end_idx1 = num_end + 1; while (end_idx1 < st_offset && data[end_idx1] != ';') { if ('0' <= data[end_idx1] && data[end_idx1] <= '9') { if (!termpaintp_input_checked_append_digit(&event.palette_color_report.color_index, 10, data[end_idx1] - '0')) { color_index_ok = false; } } else { color_index_ok = false; } end_idx1++; } if (end_idx1 + 1 < st_offset) { size_t end_idx2 = end_idx1 + 1; while (end_idx2 < st_offset && data[end_idx2] != ';') { end_idx2++; } if (color_index_ok) { event.type = TERMPAINT_EV_PALETTE_COLOR_REPORT; event.palette_color_report.color_desc = (const char*)data + end_idx1 + 1; event.palette_color_report.length = end_idx2 - end_idx1 - 1; } } else { event.palette_color_report.color_index = -1; event.type = TERMPAINT_EV_PALETTE_COLOR_REPORT; event.palette_color_report.color_desc = (const char*)data + num_end + 1; event.palette_color_report.length = end_idx1 - num_end - 1; } } if (num_end && ((num >= 10 && num <= 14) || num == 17 || num == 19 || (num >= 705 && num <= 708))) { event.type = TERMPAINT_EV_COLOR_SLOT_REPORT; event.color_slot_report.slot = num; event.color_slot_report.color = (const char*)data + num_end + 1; size_t end_idx = num_end + 1; while (end_idx < st_offset && data[end_idx] != ';') { end_idx++; } event.color_slot_report.length = end_idx - num_end - 1; } } if (!event.type && length > 5 && data[0] == '\033' && data[1] == 'P' && data[length-1] == '\\' && data[length-2] == '\033') { // DCS sequences if (data[2] == '!' && data[3] == '|') { event.type = TERMPAINT_EV_RAW_3RD_DEV_ATTRIB; event.raw.string = (const char*)data + 4; event.raw.length = length - 6; } if (data[2] == '>' && data[3] == '|') { event.type = TERMPAINT_EV_RAW_TERM_NAME; event.raw.string = (const char*)data + 4; event.raw.length = length - 6; } if ((data[2] == '1' || data[2] == '0') && data[3] == '+' && data[4] == 'r') { event.type = TERMPAINT_EV_RAW_TERMINFO_QUERY_REPLY; event.raw.string = (const char*)data + 2; event.raw.length = length - 4; } } if (!event.type && length > 3 && data[0] == 0x90 && data[length-1] == 0x9c) { // DCS sequences if (data[1] == '!' && data[2] == '|') { event.type = TERMPAINT_EV_RAW_3RD_DEV_ATTRIB; event.raw.string = (const char*)data + 3; event.raw.length = length - 4; } } } if (!ctx->in_paste) { ctx->event_cb(ctx->event_user_data, &event); } else { // while in paste state ignore anything that is not a plain character. // in a paste there shouldn't be any escape sequences, but don't depend on // all terminals applying strict filtering. if (event.type == TERMPAINT_EV_CHAR && event.c.modifier == 0) { termpaint_event event2; event2.type = TERMPAINT_EV_PASTE; event2.paste.string = event.c.string; event2.paste.length = event.c.length; event2.paste.initial = false; event2.paste.final = false; ctx->event_cb(ctx->event_user_data, &event2); } // some terminals send line breaks as \x0a if (event.type == TERMPAINT_EV_CHAR && event.c.modifier == TERMPAINT_MOD_CTRL && event.c.length == 1 && event.c.string[0] == 'j') { termpaint_event event2; event2.type = TERMPAINT_EV_PASTE; event2.paste.string = "\n"; event2.paste.length = 1; event2.paste.initial = false; event2.paste.final = false; ctx->event_cb(ctx->event_user_data, &event2); } // But some plain strings are handled as keys, so process those as well if (event.type == TERMPAINT_EV_KEY && event.key.modifier == 0) { termpaint_event event2; event2.type = TERMPAINT_EV_PASTE; event2.paste.initial = false; event2.paste.final = false; if (event.key.atom == termpaint_input_space()) { event2.paste.string = " "; event2.paste.length = 1; ctx->event_cb(ctx->event_user_data, &event2); } if (event.key.atom == termpaint_input_tab()) { event2.paste.string = "\t"; event2.paste.length = 1; ctx->event_cb(ctx->event_user_data, &event2); } if (event.key.atom == termpaint_input_enter()) { event2.paste.string = "\r"; event2.paste.length = 1; ctx->event_cb(ctx->event_user_data, &event2); } } } } termpaint_input *termpaint_input_new_or_nullptr(void) { termpaintp_input_selfcheck(); termpaint_input *ctx = calloc(1, sizeof(termpaint_input)); if (!ctx) { return nullptr; } termpaintp_input_reset(ctx); ctx->esc_pending = false; ctx->raw_filter_cb = nullptr; ctx->event_cb = nullptr; ctx->expect_cursor_position_report = 0; ctx->handle_paste = true; return ctx; } termpaint_input *termpaint_input_new(void) { termpaint_input *ret = termpaint_input_new_or_nullptr(); if (!ret) { abort(); } return ret; } void termpaint_input_free(termpaint_input *ctx) { if (!ctx) { return; } for (int i = 0; i < ctx->quirks_len; i++) { key_mapping_entry* entry = &ctx->quirks[i]; free((void*)entry->sequence); // cast away const, quirks is always dynamically allocated. if (entry->modifiers & MOD_PRINT) { free((void*)entry->atom); // cast away const, quirks is always dynamically allocated. } } free(ctx->quirks); free(ctx); } void termpaint_input_set_raw_filter_cb(termpaint_input *ctx, _Bool (*cb)(void *user_data, const char *data, unsigned length, _Bool overflow), void *user_data) { ctx->raw_filter_cb = cb; ctx->raw_filter_user_data = user_data; } void termpaint_input_set_event_cb(termpaint_input *ctx, void (*cb)(void *, termpaint_event *), void *user_data) { ctx->event_cb = cb; ctx->event_user_data = user_data; } static bool termpaintp_input_legacy_mouse_bytes_finished(termpaint_input *ctx) { const unsigned char cur_ch = ctx->buff[ctx->used - 1]; if (0xc0 == (0xc0 & cur_ch)) { // start of multi code unit sequence. if (ctx->used >= 2 && (0xc0 == (0xc0 & ctx->buff[ctx->used - 2]))) { // multiple start characters without continuation -> bogus return true; } else { return false; } } else if (0x80 == (0x80 & cur_ch)) { // continuation for (int j = ctx->used - 2; j > 0; j--) { if (j <= ctx->used - 5 || !(0x80 & ctx->buff[j])) { return true; } if ((ctx->buff[j] & 0xc0) == 0xc0) { int len = termpaintp_utf8_len(ctx->buff[j]); if (len <= ctx->used - j) { return true; } else { return false; } } } return true; } else { return true; } } void termpaint_input_add_data(termpaint_input *ctx, const char *data_s, unsigned length) { const unsigned char *data = (const unsigned char*)data_s; for (unsigned i = 0; i < length; i++) { // Protect against overlong sequences if (ctx->used == MAX_SEQ_LENGTH) { // go to error recovery ctx->buff[0] = 0; ctx->used = 0; ctx->overflow = 1; } const unsigned char cur_ch = data[i]; ctx->buff[ctx->used] = cur_ch; ++ctx->used; // Don't use data or i after here! bool finished = false; bool retrigger = false; bool retrigger2 = false; // used in tpis_cmd_str to reprocess "\033[" (last 2 chars) switch (ctx->state) { case tpis_base: // expected: ctx->used == 0 // detect possible utf-8 multi char start bytes if (0xfc == (0xfe & cur_ch)) { ctx->state = tpid_utf8_5; } else if (0xf8 == (0xfc & cur_ch)) { ctx->state = tpid_utf8_4; } else if (0xf0 == (0xf8 & cur_ch)) { ctx->state = tpid_utf8_3; } else if (0xe0 == (0xf0 & cur_ch)) { ctx->state = tpid_utf8_2; } else if (0xc0 == (0xe0 & cur_ch)) { ctx->state = tpid_utf8_1; // escape sequence starts } else if (cur_ch == '\033') { ctx->state = tpis_esc; } else if (cur_ch == 0x8f) { // SS3 ctx->state = tpis_ss3; } else if (cur_ch == 0x90) { // DCS ctx->state = tpis_cmd_str_c1; } else if (cur_ch == 0x9b) { // CSI ctx->state = tpis_csi; } else if (cur_ch == 0x9d) { // OSC ctx->state = tpis_cmd_str_c1; } else { finished = true; } break; case tpis_esc: if (cur_ch == 'O') { ctx->state = tpis_ss3; } else if (cur_ch == 'P') { ctx->state = tpis_cmd_str; } else if (cur_ch == '[') { ctx->state = tpis_csi; } else if (cur_ch == ']') { ctx->state = tpis_cmd_str; } else if (ctx->expect_apc && cur_ch == '_') { // APC ctx->state = tpis_cmd_str; } else if (0xfc == (0xfe & cur_ch)) { // meta -> ESC can produce utf-8 sequences preceeded by an ESC ctx->state = tpid_utf8_5; } else if (0xf8 == (0xfc & cur_ch)) { ctx->state = tpid_utf8_4; } else if (0xf0 == (0xf8 & cur_ch)) { ctx->state = tpid_utf8_3; } else if (0xe0 == (0xf0 & cur_ch)) { ctx->state = tpid_utf8_2; } else if (0xc0 == (0xe0 & cur_ch)) { ctx->state = tpid_utf8_1; } else if (cur_ch == '\033') { retrigger = true; } else { finished = true; } break; case tpis_ss3: // this ss3 stuff is totally undocumented. But various codes // are seen in the wild that extend these codes by embedding // parameters if ((cur_ch >= '0' && cur_ch <= '9') || cur_ch == ';') { ; } else if (cur_ch == '\033') { retrigger = true; } else { finished = true; } break; case tpis_csi: if (ctx->used == 3 && cur_ch == 'M' && ctx->buff[ctx->used - 2] == '[' && (ctx->expect_mouse_char_mode || ctx->expect_mouse_multibyte_mode)) { ctx->state = tpis_mouse_btn; } else if (cur_ch >= '@' && cur_ch <= '~' && (cur_ch != '[' || ctx->used != 3 /* linux vt*/)) { finished = true; } else if (cur_ch == '\033') { retrigger = true; } break; case tpis_cmd_str: if (cur_ch == '\033') { ctx->state = tpis_str_terminator_esc; } else if (cur_ch == 0x9c || cur_ch == 0x07) { finished = true; } break; case tpis_cmd_str_c1: if (cur_ch == 0x9c) { finished = true; } break; case tpis_str_terminator_esc: // we expect a '\\' here. But every other char also aborts parsing if (cur_ch == '[') { // as a workaround for retriggering: retrigger2 = true; } else { finished = true; } break; case tpid_utf8_5: if ((cur_ch & 0xc0) != 0x80) { // encoding error, abort sequence retrigger = true; } else { ctx->state = tpid_utf8_4; } break; case tpid_utf8_4: if ((cur_ch & 0xc0) != 0x80) { // encoding error, abort sequence retrigger = true; } else { ctx->state = tpid_utf8_3; } break; case tpid_utf8_3: if ((cur_ch & 0xc0) != 0x80) { // encoding error, abort sequence retrigger = true; } else { ctx->state = tpid_utf8_2; } break; case tpid_utf8_2: if ((cur_ch & 0xc0) != 0x80) { // encoding error, abort sequence retrigger = true; } else { ctx->state = tpid_utf8_1; } break; case tpid_utf8_1: if ((cur_ch & 0xc0) != 0x80) { // encoding error, abort sequence retrigger = true; } else { finished = true; } break; case tpis_mouse_btn: if (!ctx->expect_mouse_multibyte_mode) { ctx->state = tpis_mouse_col; } else { if (termpaintp_input_legacy_mouse_bytes_finished(ctx)) { ctx->state = tpis_mouse_col; } } break; case tpis_mouse_col: if (!ctx->expect_mouse_multibyte_mode) { ctx->state = tpis_mouse_row; } else { if (termpaintp_input_legacy_mouse_bytes_finished(ctx)) { ctx->state = tpis_mouse_row; } } break; case tpis_mouse_row: if (!ctx->expect_mouse_multibyte_mode) { finished = true; } else { if (termpaintp_input_legacy_mouse_bytes_finished(ctx)) { finished = true; } } break; } if (finished) { termpaintp_input_raw(ctx, ctx->buff, ctx->used, ctx->overflow); termpaintp_input_reset(ctx); } else if (retrigger2) { // current and previous char is not part of sequence if (ctx->used > 2) { termpaintp_input_raw(ctx, ctx->buff, ctx->used - 2, ctx->overflow); } else { termpaintp_input_raw(ctx, ctx->buff, 0, ctx->overflow); } termpaintp_input_reset(ctx); ctx->buff[ctx->used] = '\033'; ++ctx->used; ctx->buff[ctx->used] = '['; ++ctx->used; ctx->state = tpis_csi; } else if (retrigger) { // current char is not part of sequence termpaintp_input_raw(ctx, ctx->buff, ctx->used - 1, ctx->overflow); termpaintp_input_reset(ctx); --i; // process this char again } } } const char *termpaint_input_peek_buffer(const termpaint_input *ctx) { return (const char*)ctx->buff; } int termpaint_input_peek_buffer_length(const termpaint_input *ctx) { return ctx->used; } void termpaint_input_expect_cursor_position_report(termpaint_input *ctx) { ctx->expect_cursor_position_report += 1; } void termpaint_input_expect_legacy_mouse_reports(termpaint_input *ctx, int s) { if (s == TERMPAINT_INPUT_EXPECT_LEGACY_MOUSE) { ctx->expect_mouse_char_mode = true; ctx->expect_mouse_multibyte_mode = false; } else if (s == TERMPAINT_INPUT_EXPECT_LEGACY_MOUSE_MODE_1005) { ctx->expect_mouse_char_mode = false; ctx->expect_mouse_multibyte_mode = true; } else { ctx->expect_mouse_char_mode = false; ctx->expect_mouse_multibyte_mode = false; } } void termpaint_input_expect_apc_sequences(termpaint_input *ctx, bool enable) { ctx->expect_apc = enable; } static void termpaintp_input_prepend_quirk(termpaint_input *ctx, const key_mapping_entry *e) { // takes ownership of e.sequence; key_mapping_entry* new_quirks = calloc(ctx->quirks_len + 1, sizeof(key_mapping_entry)); if (!new_quirks) { abort(); } new_quirks[0] = *e; if (ctx->quirks_len) { memcpy(new_quirks + 1, ctx->quirks, ctx->quirks_len * sizeof(*ctx->quirks)); } free(ctx->quirks); ctx->quirks = new_quirks; ctx->quirks_len += 1; } void termpaint_input_activate_quirk(termpaint_input *ctx, int quirk) { if (quirk == TERMPAINT_INPUT_QUIRK_BACKSPACE_X08_AND_X7F_SWAPPED) { { key_mapping_entry e; e.atom = termpaint_input_backspace(); e.sequence = strdup("\x08"); if (!e.sequence) { abort(); } e.modifiers = 0; termpaintp_input_prepend_quirk(ctx, &e); } { key_mapping_entry e; e.atom = termpaint_input_backspace(); e.sequence = strdup("\x7f"); if (!e.sequence) { abort(); } e.modifiers = TERMPAINT_MOD_CTRL; termpaintp_input_prepend_quirk(ctx, &e); } } else if (quirk == TERMPAINT_INPUT_QUIRK_C1_FOR_CTRL_SHIFT) { { key_mapping_entry e; e.atom = termpaint_input_space(); e.sequence = strdup("\xc2\x80"); if (!e.sequence) { abort(); } e.modifiers = TERMPAINT_MOD_CTRL | TERMPAINT_MOD_SHIFT; termpaintp_input_prepend_quirk(ctx, &e); } for (int i = 0; i < 26; i++) { key_mapping_entry e; unsigned char *atom = malloc(2); if (!atom) { abort(); } unsigned char *sequence = malloc(3); if (!sequence) { abort(); } atom[0] = 'A' + i; atom[1] = 0; termpaintp_encode_to_utf8(0x81 + i, sequence); sequence[2] = 0; e.atom = (char*)atom; e.sequence = (char*)sequence; e.modifiers = TERMPAINT_MOD_CTRL | TERMPAINT_MOD_SHIFT | MOD_PRINT; termpaintp_input_prepend_quirk(ctx, &e); } } } void termpaint_input_handle_paste(termpaint_input *ctx, bool enable) { ctx->handle_paste = enable; if (!enable) { // TODO emit paste end event here too? ctx->in_paste = false; } } termpaint-0.3.1/termpaint_input.h000066400000000000000000000037261477303547200171350ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef TERMPAINT_TERMPAINT_INPUT_INCLUDED #define TERMPAINT_TERMPAINT_INPUT_INCLUDED #include #ifdef __cplusplus #ifndef _Bool #define _Bool bool #endif #endif #ifdef __cplusplus extern "C" { #endif struct termpaint_input_; typedef struct termpaint_input_ termpaint_input; // Usually termpaint_terminal's input integration should be used instead of raw termpaint_input _tERMPAINT_PUBLIC termpaint_input *termpaint_input_new(void); _tERMPAINT_PUBLIC termpaint_input *termpaint_input_new_or_nullptr(void); _tERMPAINT_PUBLIC void termpaint_input_free(termpaint_input *ctx); _tERMPAINT_PUBLIC void termpaint_input_set_raw_filter_cb(termpaint_input *ctx, _Bool (*cb)(void *user_data, const char *data, unsigned length, _Bool overflow), void *user_data); _tERMPAINT_PUBLIC void termpaint_input_set_event_cb(termpaint_input *ctx, void (*cb)(void *user_data, termpaint_event* event), void *user_data); _tERMPAINT_PUBLIC void termpaint_input_add_data(termpaint_input *ctx, const char *data, unsigned length); _tERMPAINT_PUBLIC void termpaint_input_expect_cursor_position_report(termpaint_input *ctx); #define TERMPAINT_INPUT_EXPECT_NO_LEGACY_MOUSE 0 #define TERMPAINT_INPUT_EXPECT_LEGACY_MOUSE 1 #define TERMPAINT_INPUT_EXPECT_LEGACY_MOUSE_MODE_1005 2 _tERMPAINT_PUBLIC void termpaint_input_expect_legacy_mouse_reports(termpaint_input *ctx, int s); _tERMPAINT_PUBLIC void termpaint_input_handle_paste(termpaint_input *ctx, _Bool enable); _tERMPAINT_PUBLIC void termpaint_input_expect_apc_sequences(termpaint_input *ctx, _Bool enable); _tERMPAINT_PUBLIC const char* termpaint_input_peek_buffer(const termpaint_input *ctx); _tERMPAINT_PUBLIC int termpaint_input_peek_buffer_length(const termpaint_input *ctx); #define TERMPAINT_INPUT_QUIRK_BACKSPACE_X08_AND_X7F_SWAPPED 1 #define TERMPAINT_INPUT_QUIRK_C1_FOR_CTRL_SHIFT 2 _tERMPAINT_PUBLIC void termpaint_input_activate_quirk(termpaint_input *ctx, int quirk); #ifdef __cplusplus } #endif #endif termpaint-0.3.1/termpaint_utf8.h000066400000000000000000000152271477303547200166630ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef TERMPAINT_TERMPAINT_UTF8_INCLUDED #define TERMPAINT_TERMPAINT_UTF8_INCLUDED // internal header, not api or abi stable /* x = any bit value y = at least one needs to be set (to detect overlong encodings which are invalid) 0x00000000 - 0x0000007F: 0xxxxxxx (7bit) 0x00000080 - 0x000007FF: 110yyyyx 10xxxxxx (11bit) 0x00000800 - 0x0000D7FF: 0x0000E000 - 0x0000FFFD: 1110yyyy 10yxxxxx 10xxxxxx (16 bit) 0x00010000 - 0x001FFFFF: 11110yyy 10yyxxxx 10xxxxxx 10xxxxxx (21bit) 0x00200000 - 0x03FFFFFF: 111110yy 10yyyxxx 10xxxxxx 10xxxxxx 10xxxxxx (26bit) 0x04000000 - 0x7FFFFFFF: 1111110y 10yyyyxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx (31bit) */ static inline int termpaintp_utf8_len(unsigned char first_byte) { int size; if (0xfc == (0xfe & first_byte)) { size = 6; } else if (0xf8 == (0xfc & first_byte)) { size = 5; } else if (0xf0 == (0xf8 & first_byte)) { size = 4; } else if (0xe0 == (0xf0 & first_byte)) { size = 3; } else if (0xc0 == (0xe0 & first_byte)) { size = 2; } else { size = 1; } return size; } // Decodes a utf8 encoded unicode codepoint. // input MUST point to at least length readable bytes. // length MUST be the return of termpaintp_utf8_len(*input) static inline int termpaintp_utf8_decode_from_utf8(const unsigned char *input, int length) { if (length == 1) { return *input; } else if (length == 2) { return ((input[0] & 0x1f) << 6) | (input[1] & 0x3f); } else if (length == 3) { return ((input[0] & 0x0f) << (6*2)) | ((input[1] & 0x3f) << 6) | (input[2] & 0x3f); } else if (length == 4) { return ((input[0] & 0x07) << (6*3)) | ((input[1] & 0x3f) << (6*2)) | ((input[2] & 0x3f) << 6) | (input[3] & 0x3f); } else if (length == 5) { return ((input[0] & 0x03) << (6*3)) | ((input[1] & 0x3f) << (6*3)) | ((input[2] & 0x3f) << (6*2)) | ((input[3] & 0x3f) << 6) | (input[4] & 0x3f); } else if (length == 6) { return ((input[0] & 0x01) << (6*5)) | ((input[1] & 0x3f) << (6*4)) | ((input[2] & 0x3f) << (6*3)) | ((input[3] & 0x3f) << (6*2)) | ((input[4] & 0x3f) << 6) | (input[5] & 0x3f); } else { return 0; } } static inline _Bool termpaintp_check_valid_sequence(const unsigned char *input, unsigned length) { #define CHECK_CONTINUATION_BYTE(index) if ((input[index] & 0xc0) != 0x80) return false; if (length == 0) { return 0; } if ((*input >= 0x80 && *input <= 0xc0) || *input == 0xfe || *input == 0xff) { return 0; } unsigned len = termpaintp_utf8_len(*input); if (len != length) { return 0; } if (len == 1) { return 1; } else if (len == 2) { CHECK_CONTINUATION_BYTE(1) return input[0] & 0x1e; } else if (len == 3) { CHECK_CONTINUATION_BYTE(1) CHECK_CONTINUATION_BYTE(2) //unsigned codepoint = ((input[0] & 0x0f) << 12) | ((input[1] & 0x7f) << 6) | (input[2] & 0x7f); //return (codepoint >= 0x800 && codepoint < 0xd800) || codepoint >= 0xe000; // based on table 3-7 in unicode standard (9.0) if (input[0] == 0xe0) { // all variable bits in input[0] are zero // most significant variable bit in input[0] must be set if this is not an overlong sequence return input[1] >= 0xa0; } else if (input[0] == 0xed) { // decoded value begins with 1101 // if code continues with 1xxxxx (thus 1101 1xxxxx ...) it is surrogate // thus not a valid unicode scalar value return input[1] < 0xa0; } else { return 1; } } else if (len == 4) { CHECK_CONTINUATION_BYTE(1) CHECK_CONTINUATION_BYTE(2) CHECK_CONTINUATION_BYTE(3) return (input[0] & 0x7) + (input[1] & 0x30); } else if (len == 5) { CHECK_CONTINUATION_BYTE(1) CHECK_CONTINUATION_BYTE(2) CHECK_CONTINUATION_BYTE(3) CHECK_CONTINUATION_BYTE(4) return (input[0] & 0x3) + (input[1] & 0x38); } else { // len == 6 CHECK_CONTINUATION_BYTE(1) CHECK_CONTINUATION_BYTE(2) CHECK_CONTINUATION_BYTE(3) CHECK_CONTINUATION_BYTE(4) CHECK_CONTINUATION_BYTE(5) return (input[0] & 0x1) + (input[1] & 0x3c); } return 0; // not reached #undef CHECK_CONTINUATION_BYTE } // return count of bytes written // buffer needs to be 6 bytes long // does not reject UTF-16 surrogates codepoints static inline int termpaintp_encode_to_utf8(int codepoint, unsigned char *buf) { #define STORE_AND_SHIFT(index) \ buf[index] = (codepoint & 0b00111111) | 0x80; \ codepoint = codepoint >> 6; if (codepoint > 0x7FFFFFFF) { // out of range return 0; } else if (codepoint >= 0x04000000) { STORE_AND_SHIFT(5) STORE_AND_SHIFT(4) STORE_AND_SHIFT(3) STORE_AND_SHIFT(2) STORE_AND_SHIFT(1) buf[0] = 0b11111100 | codepoint; return 6; } else if (codepoint >= 0x00200000) { STORE_AND_SHIFT(4) STORE_AND_SHIFT(3) STORE_AND_SHIFT(2) STORE_AND_SHIFT(1) buf[0] = 0b11111000 | codepoint; return 5; } else if (codepoint >= 0x00010000) { STORE_AND_SHIFT(3) STORE_AND_SHIFT(2) STORE_AND_SHIFT(1) buf[0] = 0b11110000 | codepoint; return 4; } else if (codepoint >= 0x00000800) { STORE_AND_SHIFT(2) STORE_AND_SHIFT(1) buf[0] = 0b11100000 | codepoint; return 3; } else if (codepoint >= 0x00000080) { STORE_AND_SHIFT(1) buf[0] = 0b11000000 | codepoint; return 2; } else { buf[0] = codepoint; return 1; } #undef STORE_AND_SHIFT } // UTF-16 sneaked in here too static inline _Bool termpaintp_utf16_is_high_surrogate(uint16_t codeunit) { return (codeunit >= 0xD800) && (codeunit <= 0xDBFF); } static inline _Bool termpaintp_utf16_is_low_surrogate(uint16_t codeunit) { return (codeunit >= 0xDC00) && (codeunit <= 0xDFFF); } static inline int termpaintp_utf16_combine(uint16_t high, uint16_t low) { return 0x10000 + (((high - 0xD800) << 10) | (low - 0xDC00)); } static inline unsigned termpaintp_utf16_split(int codepoint) { if (codepoint <= 0xffff) { return codepoint; } else { codepoint -= 0x10000; return (0xDC00 + (codepoint & 0x3ff)) << 16 | (0xD800 + (codepoint >> 10)); } } #endif termpaint-0.3.1/termpaintx.c000066400000000000000000000673521477303547200161060ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #define _GNU_SOURCE #include "termpaintx.h" #ifdef USE_TK_DEBUGLOG #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if __GNUC__ // Trying to avoid this warning with e.g. bit manipulations using defines from the standard headers is just too ugly #pragma GCC diagnostic ignored "-Wsign-conversion" #endif #ifndef nullptr #define nullptr ((void*)0) #endif #define FDPTR(var) ((termpaint_integration_fd*)var) typedef struct termpaint_integration_fd_ { termpaint_integration base; char *options; // In rare situations, read and write MUST use different fds to be able to communicate with the terminal int fd_read; int fd_write; bool auto_close; // if true fd_read == fd_write is assumed struct termios original_terminal_attributes; bool callback_requested; bool awaiting_response; bool poll_sigwinch; bool inline_active; int inline_height; termpaint_terminal *terminal; termpaintx_ttyrescue *rescue; } termpaint_integration_fd; static bool sigwinch_set; static int sigwinch_pipe[2]; static void termpaintx_sig_winch_pipe_handler(int sig, siginfo_t *info, void *ucontext) { (void)sig; (void)info; (void)ucontext; char dummy = ' '; (void)!write(sigwinch_pipe[1], &dummy, 1); // nothing much we can do, signal just gets lost then... } static void termpaintp_setup_winch(void) { if (!sigwinch_set) { bool ok = true; #ifdef __linux__ ok &= (pipe2(sigwinch_pipe, O_CLOEXEC | O_NONBLOCK) == 0); #else ok &= (pipe(sigwinch_pipe) == 0); ok &= (fcntl(sigwinch_pipe[0], F_SETFD, FD_CLOEXEC) == 0); ok &= (fcntl(sigwinch_pipe[1], F_SETFD, FD_CLOEXEC) == 0); ok &= (fcntl(sigwinch_pipe[0], F_SETFL, O_NONBLOCK) == 0); ok &= (fcntl(sigwinch_pipe[1], F_SETFL, O_NONBLOCK) == 0); #endif if (!ok) { return; } struct sigaction act; if (sigfillset(&act.sa_mask) != 0) { return; } act.sa_flags = SA_RESTART | SA_SIGINFO; act.sa_sigaction = &termpaintx_sig_winch_pipe_handler; if (sigaction(SIGWINCH, &act, NULL) != 0) { return; } sigwinch_set = true; } } static bool termpaintp_is_file_rw(int fd) { int ret = fcntl(fd, F_GETFL); return ret != -1 && (ret & O_ACCMODE) == O_RDWR; } static bool termpaintp_is_file_readable(int fd) { int ret = fcntl(fd, F_GETFL); return ret != -1 && ((ret & O_ACCMODE) == O_RDWR || (ret & O_ACCMODE) == O_RDONLY); } static bool termpaintp_is_file_writable(int fd) { int ret = fcntl(fd, F_GETFL); return ret != -1 && ((ret & O_ACCMODE) == O_RDWR || (ret & O_ACCMODE) == O_WRONLY); } static bool termpaintp_is_terminal_fd_pair(int fd_read, int fd_write) { if (isatty(fd_read) && isatty(fd_write) && termpaintp_is_file_readable(fd_read) && termpaintp_is_file_writable(fd_write)) { struct stat statbuf_r; struct stat statbuf_w; if (fstat(fd_read, &statbuf_r) == 0 && fstat(fd_write, &statbuf_w) == 0) { if (statbuf_r.st_rdev == statbuf_w.st_rdev && statbuf_r.st_dev == statbuf_w.st_dev && statbuf_r.st_ino == statbuf_w.st_ino) { return true; } } } return false; } _Bool termpaintx_full_integration_available(void) { _Bool from_std_fd = false; from_std_fd = (isatty(0) && termpaintp_is_file_rw(0)) || (isatty(1) && termpaintp_is_file_rw(1)) || (isatty(2) && termpaintp_is_file_rw(2)); if (from_std_fd) { return true; } // also try controlling terminal int fd = open("/dev/tty", O_RDONLY | O_NOCTTY | FD_CLOEXEC); if (fd != -1) { close(fd); return true; } // as last resort check if (0, 1) or (0, 2) are matching read/write pairs pointing to the same terminal if (termpaintp_is_terminal_fd_pair(0, 1)) { return true; } if (termpaintp_is_terminal_fd_pair(0, 2)) { return true; } return false; } termpaint_integration *termpaintx_full_integration(const char *options) { int fd = -1; int fd_write = -1; // only if differs _Bool auto_close = false; bool might_be_controlling_terminal = true; if (isatty(0) && termpaintp_is_file_rw(0)) { fd = 0; } else if (isatty(1) && termpaintp_is_file_rw(1)) { fd = 1; } else if (isatty(2) && termpaintp_is_file_rw(2)) { fd = 2; } else { fd = open("/dev/tty", O_RDWR | O_NOCTTY | FD_CLOEXEC); if (fd != -1) { auto_close = true; } else { might_be_controlling_terminal = false; // as last resort check if (0, 1) or (0, 2) are matching read/write pairs pointing to the same terminal if (termpaintp_is_terminal_fd_pair(0, 1)) { fd = 0; fd_write = 1; } else if (termpaintp_is_terminal_fd_pair(0, 2)) { fd = 0; fd_write = 2; } else { return nullptr; } } } termpaint_integration *ret = (fd_write == -1) ? termpaintx_full_integration_from_fd(fd, auto_close, options) : termpaintx_full_integration_from_fds(fd, fd_write, options); if (might_be_controlling_terminal) { termpaintp_setup_winch(); FDPTR(ret)->poll_sigwinch = true; } return ret; } termpaint_integration *termpaintx_full_integration_from_controlling_terminal(const char *options) { int fd = -1; fd = open("/dev/tty", O_RDWR | O_NOCTTY | FD_CLOEXEC); if (fd == -1) { return nullptr; } termpaint_integration *ret = termpaintx_full_integration_from_fd(fd, true, options); termpaintp_setup_winch(); FDPTR(ret)->poll_sigwinch = true; return ret; } static void fd_free(termpaint_integration* integration) { termpaint_integration_fd* fd_data = FDPTR(integration); // If terminal auto detection or another operation with response is cut short // by a close the reponse will leak out into the next application. // We can't reliably prevent that here, but this kludge can reduce the likelyhood // by just discarding input for a short amount of time. if (fd_data->awaiting_response) { struct timespec start_time; clock_gettime(CLOCK_REALTIME, &start_time); while (true) { struct timespec now; clock_gettime(CLOCK_REALTIME, &now); long time_waited_ms = (now.tv_sec - start_time.tv_sec) * 1000 + now.tv_nsec / 1000000 - start_time.tv_nsec / 1000000; if (time_waited_ms >= 100 || time_waited_ms < 0) { break; } int ret; struct pollfd info; info.fd = fd_data->fd_read; info.events = POLLIN; ret = poll(&info, 1, 100 - time_waited_ms); if (ret == 1) { char buff[1000]; int amount = (int)read(fd_data->fd_read, buff, 999); if (amount < 0) { break; } } } } if (fd_data->rescue) { termpaintx_ttyrescue_stop(fd_data->rescue); fd_data->rescue = nullptr; } tcsetattr (fd_data->fd_read, TCSAFLUSH, &fd_data->original_terminal_attributes); if (fd_data->auto_close && fd_data->fd_read != -1) { // assumnes that auto_close will only be true if fd_read == fd_write close(fd_data->fd_read); } free(fd_data->options); termpaint_integration_deinit(&fd_data->base); free(fd_data); } static void fd_flush(termpaint_integration* integration) { (void)integration; // no buffering yet } static void fd_mark_bad(termpaint_integration* integration) { FDPTR(integration)->fd_read = -1; FDPTR(integration)->fd_write = -1; } static _Bool fd_is_bad(termpaint_integration* integration) { return FDPTR(integration)->fd_read == -1; } static void fd_write_data(termpaint_integration* integration, const char *data, int length) { ssize_t written = 0; ssize_t ret; errno = 0; while (written != length) { ret = write(FDPTR(integration)->fd_write, data+written, length-written); if (ret > 0) { written += ret; } else { // error handling? if (errno == EAGAIN || errno == EWOULDBLOCK) { // fatal, non blocking is not supported by this integration fd_mark_bad(integration); return; } if (errno == EIO || errno == ENOSPC) { // fatal? fd_mark_bad(integration); return; } if (errno == EBADF || errno == EINVAL || errno == EPIPE) { // fatal, or fd is gone bad fd_mark_bad(integration); return; } if (errno == EINTR) { continue; } } } } static void fd_request_callback(struct termpaint_integration_ *integration) { FDPTR(integration)->callback_requested = true; } static void fd_awaiting_response(struct termpaint_integration_ *integration) { FDPTR(integration)->awaiting_response = true; } static bool termpaintp_has_option(const char *options, const char *name) { const char *p = options; int name_len = strlen(name); while (1) { const char *found = strstr(p, name); if (!found) { break; } if (found == options || found[-1] == ' ') { if (found[name_len] == 0 || found[name_len] == ' ') { return true; } } p = found + name_len; } return false; } static bool termpaintp_fd_set_termios(int fd, const char *options) { struct termios tattr; if (tcgetattr(fd, &tattr) < 0) { return false; } tattr.c_iflag |= IGNBRK|IGNPAR; tattr.c_iflag &= ~(BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF); tattr.c_oflag &= ~(OPOST|ONLCR|OCRNL|ONOCR|ONLRET); tattr.c_lflag &= ~(ICANON|IEXTEN|ECHO); tattr.c_cc[VMIN] = 1; tattr.c_cc[VTIME] = 0; bool allow_interrupt = termpaintp_has_option(options, "+kbdsigint"); bool allow_quit = termpaintp_has_option(options, "+kbdsigquit"); bool allow_suspend = termpaintp_has_option(options, "+kbdsigtstp"); if (!(allow_interrupt || allow_quit || allow_suspend)) { tattr.c_lflag &= ~ISIG; } else { if (!allow_interrupt) { tattr.c_cc[VINTR] = 0; } if (!allow_quit) { tattr.c_cc[VQUIT] = 0; } if (!allow_suspend) { tattr.c_cc[VSUSP] = 0; } } if (tcsetattr (fd, TCSAFLUSH, &tattr) < 0) { return false; } return true; } bool termpaintx_fd_set_termios(int fd, const char *options) { return termpaintp_fd_set_termios(fd, options); } static termpaint_integration *termpaintp_full_integration_from_fds(int fd_read, int fd_write, _Bool auto_close, const char *options) { // NOTE: If fd_read != fd_write then auto_close must be false. termpaint_integration_fd *ret = calloc(1, sizeof(termpaint_integration_fd)); if (!ret) { (void)!write(2, "Out of memory\n", 14); // ignore error, exiting soon anyway. abort(); } termpaint_integration_init(&ret->base, fd_free, fd_write_data, fd_flush); termpaint_integration_set_is_bad(&ret->base, fd_is_bad); termpaint_integration_set_request_callback(&ret->base, fd_request_callback); termpaint_integration_set_awaiting_response(&ret->base, fd_awaiting_response); ret->options = strdup(options); ret->fd_read = fd_read; ret->fd_write = fd_write; ret->auto_close = auto_close; ret->callback_requested = false; ret->awaiting_response = false; tcgetattr(ret->fd_read, &ret->original_terminal_attributes); termpaintp_fd_set_termios(ret->fd_read, options); return (termpaint_integration*)ret; } termpaint_integration *termpaintx_full_integration_from_fd(int fd, _Bool auto_close, const char *options) { return termpaintp_full_integration_from_fds(fd, fd, auto_close, options); } termpaint_integration *termpaintx_full_integration_from_fds(int fd_read, int fd_write, const char *options) { return termpaintp_full_integration_from_fds(fd_read, fd_write, false, options); } void termpaintx_full_integration_wait_for_ready(termpaint_integration *integration) { termpaint_integration_fd *t = FDPTR(integration); while (termpaint_terminal_auto_detect_state(t->terminal) == termpaint_auto_detect_running) { if (!termpaintx_full_integration_do_iteration(integration)) { // some kind of error break; } } } void termpaintx_full_integration_wait_for_ready_with_message(termpaint_integration *integration, int milliseconds, const char* message) { termpaint_integration_fd *t = FDPTR(integration); while (termpaint_terminal_auto_detect_state(t->terminal) == termpaint_auto_detect_running) { if (milliseconds > 0) { if (!termpaintx_full_integration_do_iteration_with_timeout(integration, &milliseconds)) { // some kind of error break; } if (milliseconds <= 0) { fd_write_data(integration, message, strlen(message)); } } else { if (!termpaintx_full_integration_do_iteration(integration)) { // some kind of error break; } } } } bool termpaintx_full_integration_terminal_size(termpaint_integration *integration, int *width, int *height) { if (fd_is_bad(integration) || !isatty(FDPTR(integration)->fd_read)) { return false; } return termpaintx_fd_terminal_size(FDPTR(integration)->fd_read, width, height); } void termpaintx_full_integration_set_terminal(termpaint_integration *integration, termpaint_terminal *terminal) { termpaint_integration_fd *t = FDPTR(integration); t->terminal = terminal; } static void termpaintp_handle_self_pipe(termpaint_integration_fd *t, struct pollfd *pfd) { if (pfd->revents == POLLIN) { // drain signaling pipe char buff[1000]; int ret = read(sigwinch_pipe[0], buff, 999); if (ret < 0 && ret != EINTR && ret != EAGAIN && ret != EWOULDBLOCK) { // something broken, don't try again sigwinch_set = false; } int width, height; if (termpaintx_full_integration_terminal_size(&t->base, &width, &height)) { if (t->inline_active && t->inline_height && height > t->inline_height) { height = t->inline_height; } termpaint_surface* surface = termpaint_terminal_get_surface(t->terminal); termpaint_surface_resize(surface, width, height); } } else { // something broken, don't try again sigwinch_set = false; } } bool termpaintx_full_integration_do_iteration(termpaint_integration *integration) { termpaint_integration_fd *t = FDPTR(integration); char buff[1000]; if (t->poll_sigwinch && sigwinch_set) { struct pollfd info[2]; info[0].fd = t->fd_read; info[0].events = POLLIN; info[1].fd = sigwinch_pipe[0]; info[1].events = POLLIN; int ret = poll(info, 2, -1); if (ret < 0 && errno == EINTR) { return true; } if (ret > 0 && info[1].revents != 0) { termpaintp_handle_self_pipe(t, &info[1]); return true; } } int amount = (int)read(t->fd_read, buff, 999); if (amount < 0) { if (errno != EINTR && errno != EWOULDBLOCK) { return false; } else { return true; } } t->awaiting_response = false; termpaint_terminal_add_input_data(t->terminal, buff, amount); if (t->callback_requested) { t->callback_requested = false; struct pollfd info; info.fd = t->fd_read; info.events = POLLIN; int ret = poll(&info, 1, 100); if (ret == 1) { int amount = (int)read(t->fd_read, buff, 999); if (amount < 0) { if (errno != EINTR && errno != EWOULDBLOCK) { return false; } else { return true; } } t->awaiting_response = false; termpaint_terminal_add_input_data(t->terminal, buff, amount); } termpaint_terminal_callback(t->terminal); } return true; } bool termpaintx_full_integration_do_iteration_with_timeout(termpaint_integration *integration, int *milliseconds) { termpaint_integration_fd *t = FDPTR(integration); char buff[1000]; struct timespec start_time; clock_gettime(CLOCK_REALTIME, &start_time); int ret; { int count = 1; struct pollfd info[2]; info[0].fd = t->fd_read; info[0].events = POLLIN; if (t->poll_sigwinch && sigwinch_set) { info[1].fd = sigwinch_pipe[0]; info[1].events = POLLIN; ++count; } ret = poll(info, count, *milliseconds); if (ret < 0 && errno == EINTR) { struct timespec now; clock_gettime(CLOCK_REALTIME, &now); *milliseconds -= ((now.tv_sec - start_time.tv_sec) * 1000 + now.tv_nsec / 1000000 - start_time.tv_nsec / 1000000); return true; } if (count >= 2 && ret > 0 && info[1].revents != 0) { termpaintp_handle_self_pipe(t, &info[1]); struct timespec now; clock_gettime(CLOCK_REALTIME, &now); *milliseconds -= ((now.tv_sec - start_time.tv_sec) * 1000 + now.tv_nsec / 1000000 - start_time.tv_nsec / 1000000); return true; } } if (ret == 1) { int amount = (int)read(t->fd_read, buff, 999); if (amount < 0) { if (errno != EINTR && errno != EWOULDBLOCK) { return false; } else { return true; } } t->awaiting_response = false; termpaint_terminal_add_input_data(t->terminal, buff, amount); if (t->callback_requested) { t->callback_requested = false; struct timespec now; clock_gettime(CLOCK_REALTIME, &now); long remaining = *milliseconds - ((now.tv_sec - start_time.tv_sec) * 1000 + now.tv_nsec / 1000000 - start_time.tv_nsec / 1000000); if (remaining > 0) { struct pollfd info; info.fd = t->fd_read; info.events = POLLIN; ret = poll(&info, 1, remaining < 100 ? remaining : 100); } if (ret == 1) { int amount = (int)read(t->fd_read, buff, 999); if (amount < 0) { if (errno != EINTR && errno != EWOULDBLOCK) { return false; } else { return true; } } t->awaiting_response = false; termpaint_terminal_add_input_data(t->terminal, buff, amount); } termpaint_terminal_callback(t->terminal); } struct timespec now; clock_gettime(CLOCK_REALTIME, &now); *milliseconds -= (int)((now.tv_sec - start_time.tv_sec) * 1000 + now.tv_nsec / 1000000 - start_time.tv_nsec / 1000000); } else { *milliseconds = 0; } return true; } bool termpaintx_fd_terminal_size(int fd, int *width, int *height) { struct winsize s; if (ioctl(fd, TIOCGWINSZ, &s) < 0) { return false; } *width = s.ws_col; *height = s.ws_row; return true; } const struct termios *termpaintx_full_integration_original_terminal_attributes(termpaint_integration *integration) { termpaint_integration_fd *t = FDPTR(integration); return &t->original_terminal_attributes; } void termpaintx_full_integration_apply_input_quirks(termpaint_integration *integration) { termpaint_integration_fd *t = FDPTR(integration); termpaint_terminal_auto_detect_apply_input_quirks(t->terminal, t->original_terminal_attributes.c_cc[VERASE] == 0x08); } static void fd_restore_sequence_updated(struct termpaint_integration_ *integration, const char *data, int length) { termpaint_integration_fd *t = FDPTR(integration); if (t->rescue) { termpaintx_ttyrescue_update(t->rescue, data, length); } } bool termpaintx_full_integration_ttyrescue_start(termpaint_integration *integration) { termpaint_integration_fd *t = FDPTR(integration); if (t->rescue || !t->terminal) return false; t->rescue = termpaintx_ttyrescue_start_or_nullptr(t->fd_write, termpaint_terminal_restore_sequence(t->terminal)); if (t->rescue) { termpaint_integration_set_restore_sequence_updated(integration, fd_restore_sequence_updated); termpaintx_ttyrescue_set_restore_termios(t->rescue, &t->original_terminal_attributes); return true; } return false; } static termpaint_integration *termpaintp_full_integration_setup_terminal_common(const char *options, void (*event_handler)(void *, termpaint_event *), void *event_handler_user_data, termpaint_terminal **terminal_out) { termpaint_integration *integration = termpaintx_full_integration(options); if (!integration) { const char* error = "Error: Terminal not available!"; (void)!write(1, error, strlen(error)); // already printing an error message return nullptr; } termpaint_terminal *terminal = termpaint_terminal_new(integration); termpaintx_full_integration_set_terminal(integration, terminal); termpaint_terminal_set_event_cb(terminal, event_handler, event_handler_user_data); termpaint_terminal_auto_detect(terminal); termpaintx_full_integration_wait_for_ready_with_message(integration, 10000, "Terminal auto detection is taking unusually long, press space to abort."); termpaintx_full_integration_apply_input_quirks(integration); *terminal_out = terminal; return integration; } termpaint_integration *termpaintx_full_integration_setup_terminal_fullscreen(const char *options, void (*event_handler)(void *, termpaint_event *), void *event_handler_user_data, termpaint_terminal **terminal_out) { termpaint_terminal *terminal; termpaint_integration *integration = termpaintp_full_integration_setup_terminal_common(options, event_handler, event_handler_user_data, &terminal); if (!integration) { return nullptr; } int width = 80, height = 25; termpaintx_full_integration_terminal_size(integration, &width, &height); termpaint_terminal_setup_fullscreen(terminal, width, height, options); termpaintx_full_integration_ttyrescue_start(integration); *terminal_out = terminal; return integration; } termpaint_integration *termpaintx_full_integration_setup_terminal_inline(const char *options, int lines, void (*event_handler)(void *, termpaint_event *), void *event_handler_user_data, termpaint_terminal **terminal_out) { termpaint_terminal *terminal; termpaint_integration *integration = termpaintp_full_integration_setup_terminal_common(options, event_handler, event_handler_user_data, &terminal); if (!integration) { return nullptr; } termpaint_integration_fd* fd_data = FDPTR(integration); fd_data->inline_height = lines; fd_data->inline_active = true; int width = 80, height = 25; termpaintx_full_integration_terminal_size(integration, &width, &height); if (height > lines) { height = lines; } termpaint_terminal_setup_inline(terminal, width, height, options); termpaintx_full_integration_ttyrescue_start(integration); *terminal_out = terminal; return integration; } void termpaintx_full_integration_set_inline(termpaint_integration *integration, _Bool enabled, int height) { termpaint_integration_fd* fd_data = FDPTR(integration); if (height > 0) { fd_data->inline_height = height; } termpaint_terminal_set_inline(fd_data->terminal, enabled); fd_data->inline_active = enabled; int term_width, term_height; if (termpaintx_full_integration_terminal_size(integration, &term_width, &term_height)) { if (fd_data->inline_active && fd_data->inline_height && term_height > fd_data->inline_height) { term_height = fd_data->inline_height; } termpaint_surface* surface = termpaint_terminal_get_surface(fd_data->terminal); termpaint_surface_resize(surface, term_width, term_height); } } static void termpaintx_dummy_log(struct termpaint_integration_ *integration, char *data, int length) { (void)integration; (void)data; (void)length; } #ifdef USE_TK_DEBUGLOG #include "debugwin.py.inc" static int logfd = -1; static void termpaintx_fd_log(struct termpaint_integration_ *integration, char *data, int length) { (void)integration; write(logfd, data, length); } extern char **environ; termpaint_logging_func termpaintx_enable_tk_logging(void) { if (logfd >= 0) { return termpaintx_fd_log; } int pipeends[2]; if (pipe2(pipeends, O_CLOEXEC) < 0) { return termpaintx_dummy_log; } logfd = pipeends[1]; int readend = pipeends[0]; posix_spawnattr_t attr; posix_spawn_file_actions_t file_actions; posix_spawn_file_actions_init(&file_actions); if (readend != 1) { posix_spawn_file_actions_addclose(&file_actions, 1); } if (readend != 2) { posix_spawn_file_actions_addclose(&file_actions, 2); } if (readend != 0) { posix_spawn_file_actions_addclose(&file_actions, 0); posix_spawn_file_actions_adddup2(&file_actions, readend, 0); } posix_spawnattr_init(&attr); posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGDEF); sigset_t mask; sigfillset(&mask); posix_spawnattr_getsigdefault(&attr, &mask); char * argv[] = { "python3", "-E", "-c", (char*)debugwin, nullptr }; posix_spawnp(nullptr, "python3", &file_actions, &attr, argv, environ); posix_spawn_file_actions_destroy(&file_actions); posix_spawnattr_destroy(&attr); return termpaintx_fd_log; } #else termpaint_logging_func termpaintx_enable_tk_logging(void) { return termpaintx_dummy_log; } #endif termpaint-0.3.1/termpaintx.h000066400000000000000000000053611477303547200161030ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef TERMPAINT_TERMPAINTX_INCLUDED #define TERMPAINT_TERMPAINTX_INCLUDED #include "termpaint.h" #ifdef __cplusplus extern "C" { #endif _tERMPAINT_PUBLIC _Bool termpaintx_full_integration_available(void); _tERMPAINT_PUBLIC termpaint_integration *termpaintx_full_integration(const char *options); _tERMPAINT_PUBLIC termpaint_integration *termpaintx_full_integration_from_controlling_terminal(const char *options); _tERMPAINT_PUBLIC termpaint_integration *termpaintx_full_integration_from_fd(int fd, _Bool auto_close, const char *options); _tERMPAINT_PUBLIC termpaint_integration *termpaintx_full_integration_from_fds(int fd_read, int fd_write, const char *options); _tERMPAINT_PUBLIC termpaint_integration *termpaintx_full_integration_setup_terminal_fullscreen(const char *options, void (*event_handler)(void *, termpaint_event *), void *event_handler_user_data, termpaint_terminal **terminal_out); _tERMPAINT_PUBLIC termpaint_integration *termpaintx_full_integration_setup_terminal_inline(const char *options, int lines, void (*event_handler)(void *, termpaint_event *), void *event_handler_user_data, termpaint_terminal **terminal_out); _tERMPAINT_PUBLIC void termpaintx_full_integration_set_inline(termpaint_integration *integration, _Bool enabled, int height); _tERMPAINT_PUBLIC void termpaintx_full_integration_wait_for_ready(termpaint_integration *integration); _tERMPAINT_PUBLIC void termpaintx_full_integration_wait_for_ready_with_message(termpaint_integration *integration, int milliseconds, const char* message); _tERMPAINT_PUBLIC void termpaintx_full_integration_apply_input_quirks(termpaint_integration *integration); _tERMPAINT_PUBLIC void termpaintx_full_integration_set_terminal(termpaint_integration *integration, termpaint_terminal *terminal); _tERMPAINT_PUBLIC _Bool termpaintx_full_integration_do_iteration(termpaint_integration *integration); _tERMPAINT_PUBLIC _Bool termpaintx_full_integration_do_iteration_with_timeout(termpaint_integration *integration, int *milliseconds); _tERMPAINT_PUBLIC _Bool termpaintx_full_integration_terminal_size(termpaint_integration *integration, int *width, int *height); _tERMPAINT_PUBLIC _Bool termpaintx_full_integration_ttyrescue_start(termpaint_integration *integration); _tERMPAINT_PUBLIC const struct termios *termpaintx_full_integration_original_terminal_attributes(termpaint_integration *integration); _tERMPAINT_PUBLIC _Bool termpaintx_fd_set_termios(int fd, const char *options); _tERMPAINT_PUBLIC _Bool termpaintx_fd_terminal_size(int fd, int *width, int *height); typedef void (*termpaint_logging_func)(struct termpaint_integration_ *integration, char *data, int length); _tERMPAINT_PUBLIC termpaint_logging_func termpaintx_enable_tk_logging(void); #ifdef __cplusplus } #endif #endif termpaint-0.3.1/termpaintx_ttyrescue.c000066400000000000000000000360021477303547200202010ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #define _GNU_SOURCE #include "termpaintx_ttyrescue.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __linux__ #include // memfd #include #ifdef __NR_memfd_create #include #endif #endif #ifndef nullptr #define nullptr ((void*)0) #endif _Static_assert(ATOMIC_INT_LOCK_FREE == 2, "lock free atomic_int needed"); #ifdef TERMPAINTP_VALGRIND #include #endif #ifdef TERMPAINT_RESCUE_FEXEC #include "ttyrescue_blob.inc" #endif #ifdef TERMPAINTP_VALGRIND static void exit_wrapper(long tid, void (*fn)(int)) { (void)tid; // lazy resolving _exit by ld.so here crashes, so take it as preresolved pointer fn(1); } #endif #if defined(__linux__) && defined(__NR_memfd_create) int termpaintp_memfd_create(const char *name, unsigned int flags, bool allow_exec) { #ifdef MFD_EXEC if (allow_exec) { flags |= MFD_EXEC; } else { flags |= MFD_NOEXEC_SEAL; } #endif int fd = (int)syscall(__NR_memfd_create, name, flags); #ifdef MFD_EXEC if (fd < 0 && errno == EINVAL) { // Need to retry if kernel does not yet support MFD_EXEC / MFD_NOEXEC_SEAL. flags &= ~(MFD_EXEC | MFD_NOEXEC_SEAL); fd = (int)syscall(__NR_memfd_create, name, flags); } #endif return fd; } #define HAVE_MEMFD 1 #endif #define SEGLEN 8192 #define TTYRESCUE_FLAG_ATTACHED (1 << 0) #define TTYRESCUE_FLAG_TERMIOS_SET (1 << 1) struct termpaint_ipcseg { atomic_int active; atomic_int flags; long termios_iflag; long termios_oflag; long termios_lflag; long termios_vintr; long termios_vmin; long termios_vquit; long termios_vstart; long termios_vstop; long termios_vsusp; long termios_vtime; char seq1[4000]; char seq2[4000]; }; _Static_assert(sizeof(struct termpaint_ipcseg) < SEGLEN, "termpaint_ipcseg does not fit IPC segment size"); int termpaintp_rescue_embedded(struct termpaint_ipcseg* ctlseg); struct termpaintx_ttyrescue_ { int fd; struct termpaint_ipcseg* seg; bool using_mmap; }; #ifdef __GNUC__ __attribute__((format(printf, 1, 2))) #endif static char* termpaintp_asprintf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); int len = vsnprintf(nullptr, 0, fmt, ap); va_end(ap); if (len < 0) { return nullptr; } char *ret = malloc(len + 1); if (!ret) { return nullptr; } va_start(ap, fmt); len = vsnprintf(ret, len + 1, fmt, ap); va_end(ap); if (len < 0) { free(ret); return nullptr; } return ret; } termpaintx_ttyrescue *termpaintx_ttyrescue_start_or_nullptr(int tty_fd, const char *restore_seq) { termpaintx_ttyrescue *ret = calloc(1, sizeof(termpaintx_ttyrescue)); if (!ret) { return nullptr; } ret->using_mmap = 0; int pipe[2]; #ifdef SOCK_CLOEXEC if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0, pipe) < 0) { free(ret); return nullptr; } #else // This is racy, but systems that don't offer non racy apis seem to prefer it that way. if (socketpair(AF_UNIX, SOCK_STREAM, 0, pipe) < 0) { free(ret); return nullptr; } fcntl(pipe[0], F_SETFD, FD_CLOEXEC); fcntl(pipe[1], F_SETFD, FD_CLOEXEC); fcntl(pipe[0], F_SETFL, O_NONBLOCK); fcntl(pipe[1], F_SETFL, O_NONBLOCK); #endif int shmfd = -1; #if HAVE_MEMFD shmfd = termpaintp_memfd_create("ttyrescue ctl", MFD_CLOEXEC | MFD_ALLOW_SEALING, false); if (shmfd < 0) { shmfd = -1; } #endif #if defined(__FreeBSD__) if (shmfd == -1) { shmfd = shm_open(SHM_ANON, O_RDWR | IPC_CREAT | IPC_EXCL, 0600); // FD_CLOEXEC is implied if (shmfd < 0) { shmfd = -1; } } #endif if (shmfd != -1) { if (ftruncate(shmfd, SEGLEN) < 0) { close(shmfd); shmfd = -1; } else { ret->seg = mmap(0, SEGLEN, PROT_READ | PROT_WRITE, MAP_SHARED, shmfd, 0); if (ret->seg == MAP_FAILED) { close(shmfd); shmfd = -1; ret->seg = nullptr; } else { ret->using_mmap = true; } } } int shmid = -1; if (shmfd == -1) { shmid = shmget(IPC_PRIVATE, SEGLEN, 0600 | IPC_CREAT | IPC_EXCL); ret->seg = shmat(shmid, 0, 0); if (ret->seg == (void*)-1) { ret->seg = nullptr; shmctl(shmid, IPC_RMID, nullptr); shmid = -1; } } char *envvar = termpaintp_asprintf("TTYRESCUE_RESTORE=%s", restore_seq); char *envvar2 = nullptr; bool alloc_error = envvar == nullptr; char *envp[3] = {envvar, nullptr, nullptr}; if (shmid != -1) { envvar2 = termpaintp_asprintf("TTYRESCUE_SYSVSHMID=%i", shmid); alloc_error |= envvar2 == nullptr; envp[1] = envvar2; } if (shmfd != -1) { envvar2 = strdup("TTYRESCUE_SHMFD=yes"); alloc_error |= envvar2 == nullptr; envp[1] = envvar2; } if (alloc_error) { close(pipe[0]); close(pipe[1]); free(envvar); free(envvar2); if (shmid != -1) { shmctl(shmid, IPC_RMID, nullptr); } if (shmfd != -1) { close(shmfd); } free(ret); return nullptr; } termpaintx_ttyrescue_update(ret, restore_seq, strlen(restore_seq)); pid_t pid = fork(); if (pid < 0) { close(pipe[0]); close(pipe[1]); free(envvar); free(envvar2); if (shmid != -1) { shmdt(ret->seg); shmctl(shmid, IPC_RMID, nullptr); } if (shmfd != -1) { close(shmfd); } free(ret); return nullptr; } else if (pid) { free(envvar); free(envvar2); close(pipe[0]); ret->fd = pipe[1]; if (shmid != -1) { struct pollfd info; info.fd = ret->fd; info.events = POLLIN; int err; do { err = poll(&info, 1, -1); } while (err == EINTR); char buff[1]; (void)!read(ret->fd, buff, 1); // failure doesn't matter, course of action keeps same shmctl(shmid, IPC_RMID, nullptr); } if (shmfd != -1) { close(shmfd); } return ret; } else { close(pipe[1]); // wanted file descriptors 0: control pipe(pipe[0]), 1: closed, 2: terminal(tty_fd), 3: control segment (shmfd) // first move file descriptors we intend to keep out of the way if (tty_fd == 0) { tty_fd = dup(tty_fd); } if (shmfd == 0) { shmfd = dup(shmfd); } // setup fd 0 if (pipe[0] == 0) { fcntl(0, F_SETFD, 0); // Unset O_CLOEXEC } else { dup2(pipe[0], 0); } if (shmfd == 2) { shmfd = dup(shmfd); } if (tty_fd == 2) { fcntl(pipe[0], F_SETFD, 0); // Unset O_CLOEXEC } else { dup2(tty_fd, 2); } if (shmfd != -1) { if (shmfd == 3) { fcntl(3, F_SETFD, 0); // Unset O_CLOEXEC } else { dup2(shmfd, 3); } } close(1); int from = ((shmfd != -1) ? 4 : 3); #if defined(__FreeBSD__) closefrom(from); #else int max_fd = sysconf(_SC_OPEN_MAX); for (int i = from; i < max_fd; i++) { close(i); } #endif char *argv[] = {"ttyrescue", NULL}; const char* path = TERMPAINT_RESCUE_PATH; char tmp[sizeof(TERMPAINT_RESCUE_PATH) + sizeof("ttyrescue") + 1]; for (const char *item = path; *item;) { char *end = strchr(item, ':'); ptrdiff_t len; if (end) { len = end - item; } else { len = strlen(item); } if (len > 0) { memcpy(tmp, item, len); tmp[len] = '/'; tmp[len+1] = '\0'; strcat(tmp, "ttyrescue"); execve(tmp, argv, envp); } if (end) { item = end + 1; } else { break; } } #ifdef TERMPAINT_RESCUE_FEXEC #ifdef __linux__ if (shmfd != -1) { int exefd = termpaintp_memfd_create("ttyrescue (embedded)", MFD_CLOEXEC | MFD_ALLOW_SEALING, true); if (write(exefd, ttyrescue_blob, sizeof(ttyrescue_blob)) == sizeof(ttyrescue_blob)) { argv[0] = "ttyrescue (embedded)"; fexecve(exefd, argv, envp); close(exefd); } } #else #error ttyrescue-fexec-blob option not available on this platform: not ported yet #endif #endif // if that does not work use internal fallback. extern char **environ; environ = envp; #ifdef __linux__ prctl(PR_SET_NAME, "ttyrescue embed", 0, 0, 0); int fd = open("/proc/self/stat", O_RDONLY, 0); if (fd) { char buffer[1000]; int idx = 0; int max = 0; int argn = 0; intptr_t argc_base = -1; intptr_t argc_end = -1; enum { S_A1, S_PAREN1, S_COMM, S_SPACEX, S_ARGX } state = S_A1; while (1) { if (idx+1 >= max) { max = read(fd, buffer, 1000); if (max <= 0) { break; } idx = 0; } else { ++idx; } if (state == S_A1) { if (buffer[idx] == ' ') { state = S_PAREN1; } } else if (state == S_PAREN1) { if (buffer[idx] == '(') { state = S_COMM; } } else if (state == S_COMM) { if (buffer[idx] == ')') { state = S_SPACEX; argn = 2; } } else if (state == S_SPACEX) { if (buffer[idx] != ' ') { ++argn; state = S_ARGX; if (argn == 48) { // arg_start argc_base = buffer[idx] - '0'; } if (argn == 49) { // arg_end argc_end = buffer[idx] - '0'; } } } else if (state == S_ARGX) { if (buffer[idx] == ' ') { state = S_SPACEX; if (argn == 49) { break; } } else { if (argn == 48) { // arg_start argc_base = argc_base * 10 + buffer[idx] - '0'; } if (argn == 49) { // arg_end argc_end = argc_end * 10 + buffer[idx] - '0'; } } } } close(fd); if (argc_base != -1 && argc_end != -1 && argc_end > argc_base) { #ifdef TERMPAINTP_VALGRIND // Valgrind does not grok that this is supposed to work, use a cluebat VALGRIND_MAKE_MEM_DEFINED(argc_base, argc_end - argc_base); #endif int datalen = 21; // if datalen does not fit into the arg space, intentionally overwrite into // the environment space to get the full name shown. memcpy((void*)argc_base, "ttyrescue (embedded)", datalen); // zero out the rest of the space, so ps doesn't show left over parts from old name if (datalen <= argc_end - argc_base) { memset((char*)argc_base + datalen, 0, argc_end - argc_base - datalen); } } } #endif #ifdef TERMPAINTP_VALGRIND VALGRIND_PRINTF("termpaint embedded ttyrescue running with valgrind. Maybe use --child-silent-after-fork=yes\n"); #endif termpaintp_rescue_embedded(ret->seg); #ifdef TERMPAINTP_VALGRIND if (RUNNING_ON_VALGRIND) { // There is no way to avoid leaking the main programs allocations // Valgrind's leak check would report these, so instead exit this process without valgrind noticeing. VALGRIND_PRINTF("termpaint embedded ttyrescue exited (suppressing valgrind reports in rescue process)\n"); (void)VALGRIND_NON_SIMD_CALL1(exit_wrapper, _exit); } #endif _exit(1); } } void termpaintx_ttyrescue_stop(termpaintx_ttyrescue *tpr) { if (!tpr) { return; } if (tpr->fd >= 0) { #ifdef MSG_NOSIGNAL send(tpr->fd, "~", 1, MSG_NOSIGNAL); #else send(tpr->fd, "~", 1, 0); #endif } if (tpr->seg) { if (tpr->using_mmap) { munmap(tpr->seg, SEGLEN); } else { shmdt(tpr->seg); } } free(tpr); } _Bool termpaintx_ttyrescue_update(termpaintx_ttyrescue *tpr, const char *data, int len) { if (tpr->seg && len < (int)sizeof(tpr->seg->seq1)) { // active is only written from this process. It's atomic to get memory_order_seq_cst and // to avoid tearing int offset = atomic_load(&tpr->seg->active); if (offset == 0 || offset == offsetof(struct termpaint_ipcseg, seq2)) { memcpy(tpr->seg->seq1, data, len); tpr->seg->seq1[len] = 0; atomic_store(&tpr->seg->active, offsetof(struct termpaint_ipcseg, seq1)); } else { memcpy(tpr->seg->seq2, data, len); tpr->seg->seq2[len] = 0; atomic_store(&tpr->seg->active, offsetof(struct termpaint_ipcseg, seq2)); } return 1; } return 0; } bool termpaintx_ttyrescue_set_restore_termios(termpaintx_ttyrescue *tpr, const struct termios *original_terminal_attributes) { if (tpr->seg) { tpr->seg->termios_iflag = original_terminal_attributes->c_iflag; tpr->seg->termios_oflag = original_terminal_attributes->c_oflag; tpr->seg->termios_lflag = original_terminal_attributes->c_lflag; tpr->seg->termios_vintr = original_terminal_attributes->c_cc[VINTR]; tpr->seg->termios_vmin = original_terminal_attributes->c_cc[VMIN]; tpr->seg->termios_vquit = original_terminal_attributes->c_cc[VQUIT]; tpr->seg->termios_vstart = original_terminal_attributes->c_cc[VSTART]; tpr->seg->termios_vstop = original_terminal_attributes->c_cc[VSTOP]; tpr->seg->termios_vsusp = original_terminal_attributes->c_cc[VSUSP]; tpr->seg->termios_vtime = original_terminal_attributes->c_cc[VTIME]; atomic_fetch_or(&tpr->seg->flags, TTYRESCUE_FLAG_TERMIOS_SET); } return 0; } termpaint-0.3.1/termpaintx_ttyrescue.h000066400000000000000000000017411477303547200202100ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef TERMPAINT_TERMPAINT_TTYRESCUE_INCLUDED #define TERMPAINT_TERMPAINT_TTYRESCUE_INCLUDED #include #ifdef __cplusplus #ifndef _Bool #define _Bool bool #endif #endif #ifdef __cplusplus extern "C" { #endif #if defined(__GNUC__) && defined(TERMPAINT_EXPORT_SYMBOLS) #define _tERMPAINT_PUBLIC __attribute__((visibility("default"))) #else #define _tERMPAINT_PUBLIC #endif struct termpaintx_ttyrescue_; typedef struct termpaintx_ttyrescue_ termpaintx_ttyrescue; _tERMPAINT_PUBLIC void termpaintx_ttyrescue_stop(termpaintx_ttyrescue *tpr); _tERMPAINT_PUBLIC _Bool termpaintx_ttyrescue_update(termpaintx_ttyrescue *tpr, const char* data, int len); _tERMPAINT_PUBLIC _Bool termpaintx_ttyrescue_set_restore_termios(termpaintx_ttyrescue *tpr, const struct termios *original_terminal_attributes); _tERMPAINT_PUBLIC termpaintx_ttyrescue *termpaintx_ttyrescue_start_or_nullptr(int fd, const char *restore_seq); #ifdef __cplusplus } #endif #endif termpaint-0.3.1/termquery.cpp000066400000000000000000000154731477303547200163050ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include #include #include #include #include #include #include #include #include typedef bool _Bool; #include "termpaint.h" #include "termpaintx.h" #include "termpaint_input.h" std::vector ring; std::vector ring2; std::string peek_buffer; termpaint_surface *surface; struct info { std::string name; std::string query; std::string user; std::function decoder; std::string raw; std::string pretty; }; std::vector info_vec; info *current_query = nullptr; bool finished = false; void kick() { for (auto &i : info_vec) { if (i.raw.size() == 0) { printf("%s", i.query.data()); fflush(stdout); current_query = &i; return; } } finished = true; } _Bool raw_filter(void *user_data, const char *data, unsigned length, _Bool overflow) { (void)user_data; (void) overflow; std::string event { data, length }; info &i = *current_query; i.raw = event; i.pretty = i.decoder(i); puts((i.name + ": " + i.pretty.data()).data()); kick(); return 0; } int main(int argc, char **argv) { (void)argc; (void)argv; termpaint_input *input = termpaint_input_new(); termpaint_input_set_raw_filter_cb(input, raw_filter, 0); struct termios tattr; struct termios otattr; tcgetattr (STDIN_FILENO, &otattr); tcgetattr (STDIN_FILENO, &tattr); tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */ tattr.c_cc[VMIN] = 1; tattr.c_cc[VTIME] = 0; tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr); auto addMode = [] (std::string mode, std::string name) { info i; i.name = name; i.query = "\033[" + mode + "$p"; i.user = "\033[" + mode + ";"; i.decoder = [] (info &i) { if (i.raw.find_first_of(i.user) == 0) { char result = i.raw[i.user.size()]; if (result == '0') { return "not recognized"; } else if (result == '1') { return "set"; } else if (result == '2') { return "reset"; } else if (result == '3') { return "perm set"; } else if (result == '4') { return "perm reset"; } } return "error"; }; info_vec.emplace_back(i); }; // Modenames mostly based on the fine documentation of xterm and some other sources // https://invisible-island.net/xterm/ctlseqs/ctlseqs.html addMode("2" , "Keyboard Action Mode "); addMode("3" , "Display control chars "); addMode("4" , "Insert Mode "); addMode("12" , "Send/receive "); addMode("20" , "Automatic Linefeed Mode "); addMode("34" , "Normal Cursor Visibility "); addMode("?1" , "Application Cursor Keys "); addMode("?2" , "ASCII (DECANM) "); addMode("?3" , "132 columns "); addMode("?4" , "Smooth Scroll "); addMode("?5" , "Reverse Video "); addMode("?6" , "scroll region relative (DECOM)"); addMode("?7" , "Wrap Mode "); addMode("?8" , "keyboard autorepeat "); addMode("?9" , "X10 mouse tracking "); addMode("?10" , "Show toolbar (rxvt) "); addMode("?12" , "Blinking Cursor "); addMode("?18" , "Print form feed "); addMode("?19" , "print extent is full screen "); addMode("?25" , "Visible Cursor "); addMode("?30" , "Show scrollbar (rxvt) "); addMode("?35" , "font-shifting functions (rxvt)"); addMode("?38" , "Tektronix "); addMode("?40" , "Allow 80 ←→ 132 Mode "); addMode("?41" , "more(1) fix "); addMode("?42" , "National Replacement Character set"); addMode("?44" , "Margin Bel "); addMode("?45" , "Reverse-wraparound Mode "); addMode("?46" , "Logging "); addMode("?47" , "Alternate Screen (47) "); addMode("?66" , "Application keypad "); addMode("?67" , "Backarrow key sends backspace "); addMode("?69" , "left and right margin "); addMode("?95" , "keep screen when DECCOLM is set/reset"); addMode("?1000", "VT200 mouse tracking "); addMode("?1001", "Use Hilite Mouse Tracking "); addMode("?1002", "Cell Motion Mouse Tracking "); addMode("?1003", "All Motion Mouse Tracking "); addMode("?1004", "FocusIn/FocusOut events "); addMode("?1005", "Enable UTF-8 Mouse "); addMode("?1006", "SGR Mouse "); addMode("?1007", "Alternate Scroll "); addMode("?1010", "Scroll to bottom on tty output"); addMode("?1011", "Scroll to bottom on key press "); addMode("?1015", "urxvt Mouse "); addMode("?1034", "meta key sets eighth bit "); addMode("?1035", "special modifiers for Alt and NumLock keys"); addMode("?1036", "Send ESC when Meta modifies a key"); addMode("?1037", "Send DEL from the editing-keypad Delete key"); addMode("?1039", "Send ESC when Alt modifies a key"); addMode("?1040", "Keep selection even if not highlighted"); addMode("?1041", "CLIPBOARD selection "); addMode("?1042", "Urgency window manager hint "); addMode("?1043", "popOnBell "); addMode("?1044", "Reuse the most recent data copied to CLIPBOARD"); addMode("?1047", "Alternate Screen (1047) "); addMode("?1048", "Save cursor as in DECSC "); addMode("?1049", "Alternate Screen (1049) "); addMode("?1050", "terminfo/termcap function-key "); addMode("?1051", "Sun function-key "); addMode("?1052", "HP function-key "); addMode("?1053", "SCO function-key "); addMode("?1060", "legacy keyboard emulation (X11R6)"); addMode("?1061", "VT220 keyboard emulation "); addMode("?2004", "bracketed paste "); addMode("?2001", "click1 emit Esc seq to move point"); addMode("?2002", "press2 emit Esc seq to move point"); addMode("?2003", "Double click-3 deletes "); addMode("?2005", "Quote each char during paste "); addMode("?2006", "Paste '\\n' as C-j "); kick(); while (!finished) { char buff[100]; int amount = read (STDIN_FILENO, buff, 99); termpaint_input_add_data(input, buff, amount); } tcsetattr (STDIN_FILENO, TCSAFLUSH, &otattr); return 0; } termpaint-0.3.1/tests/000077500000000000000000000000001477303547200146745ustar00rootroot00000000000000termpaint-0.3.1/tests/catch_main.cpp000066400000000000000000000004661477303547200174740ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #define CATCH_CONFIG_RUNNER #ifndef BUNDLED_CATCH2 #ifdef CATCH3 #include "catch2/catch_all.hpp" #else #include "catch2/catch.hpp" #endif #else #include "../third-party/catch.hpp" #endif int main (int argc, char * argv[]) { return Catch::Session().run( argc, argv ); } termpaint-0.3.1/tests/fingerprintingtests.cpp000066400000000000000000003452511477303547200215220ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include #include #include #include #include #include "testhelper.h" #ifndef BUNDLED_CATCH2 #ifdef CATCH3 #include "catch2/catch_all.hpp" #else #include "catch2/catch.hpp" #endif #else #include "../third-party/catch.hpp" #endif #define C(name) TERMPAINT_CAPABILITY_ ## name static const std::array allSeq = { "\033[>c", "\033[>1c", "\033[>0;1c", "\033[=c", "\033[5n", "\033[6n", "\033[?6n", "\033[>q", "\033[1x", "\033]4;255;?\007", "\033P+q544e\033\\", }; static std::string TODO = "\x01TODO\x02"; static const std::vector allCaps = { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(TITLE_RESTORE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(CURSOR_SHAPE_OSC50), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(88_COLOR), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR), }; static std::vector allCapsBut(std::initializer_list excluded) { std::vector ret = allCaps; for (int cap : excluded) { ret.erase(std::remove(ret.begin(), ret.end(), cap), ret.end()); } return ret; } struct SeqResult { std::string reply; std::string junk = std::string(); }; enum PatchStatus { WithoutGlitchPatching, NeedsGlitchPatching }; struct TestCase { std::string name; std::map seq; std::string auto_detect_result_text; std::vector caps; std::string self_reported_name_and_version; PatchStatus glitchPatching; }; #define STRINGIFY2(x) #x #define STRINGIFY1(x) STRINGIFY2(x) #define LINEINFO " (on line " STRINGIFY1(__LINE__) ")" static const std::initializer_list tests = { // --------------- { "xterm 264" LINEINFO, { { "\033[>c", { "\033[>0;264;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>0;264;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;128;128;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: xterm(264) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(TITLE_RESTORE), C(EXTENDED_CHARSET), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "xterm 280" LINEINFO, { { "\033[>c", { "\033[>41;280;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>41;280;0c" }}, { "\033[=c", { "\033P!|0\033\\" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS};1R" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: xterm(280) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(TITLE_RESTORE), C(EXTENDED_CHARSET), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "xterm 336" LINEINFO, { { "\033[>c", { "\033[>41;336;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>41;336;0c" }}, { "\033[=c", { "\033P!|00000000\033\\" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS};1R" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: xterm(336) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(TITLE_RESTORE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "xterm 354" LINEINFO, { { "\033[>c", { "\033[>41;354;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>41;354;0c" }}, { "\033[=c", { "\033P!|00000000\033\\" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS};1R" }}, { "\033[>q", { "\033P>|XTerm(354)\033\\" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: xterm(354) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(TITLE_RESTORE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "XTerm(354)", WithoutGlitchPatching }, // --------------- { "DA3 new id promise (safe-CPR)" LINEINFO, // promise that newly allocated DA3 ids will be seen as fully featured { { "\033[>c", { "\033[>61;234;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>61;234;0c", }}, { "\033[=c", { "\033P!|FEFEFEFE\033\\", }}, { "\033[5n", { "\033[0n", }}, { "\033[6n", { "\033[{POS}R", }}, { "\033[?6n", { "\033[?{POS}R", }}, { "\033[>q", { "", }}, { "\033[1x", { "", }}, { "\033]4;255;?\007", { "", }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: unknown full featured(0) safe-CPR seq:>=", allCapsBut({C(CURSOR_SHAPE_OSC50), C(88_COLOR)}), // should have all compliant capabilites. // excluding CURSOR_SHAPE_OSC50 because it's konsole specific and non standard // excluding 88_COLOR because it reduces 256 color palette to 88 color. "", WithoutGlitchPatching }, // --------------- { "DA3 new id promise (safe-CPR) with terminal software self report" LINEINFO, // promise that newly allocated DA3 ids will be seen as fully featured { { "\033[>c", { "\033[>61;234;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>61;234;0c", }}, { "\033[=c", { "\033P!|FEFEFEFE\033\\", }}, { "\033[5n", { "\033[0n", }}, { "\033[6n", { "\033[{POS}R", }}, { "\033[?6n", { "\033[?{POS}R", }}, { "\033[>q", { "\033P>|Someterm 34.56\033\\", }}, { "\033[1x", { "", }}, { "\033]4;255;?\007", { "", }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: unknown full featured(0) safe-CPR seq:>=", allCapsBut({C(CURSOR_SHAPE_OSC50), C(88_COLOR)}), // should have all compliant capabilites. // excluding CURSOR_SHAPE_OSC50 because it's konsole specific and non standard // excluding 88_COLOR because it reduces 256 color palette to 88 color. "Someterm 34.56", WithoutGlitchPatching }, // --------------- { "DA3 new id promise (?CPR not safe)" LINEINFO, // promise that newly allocated DA3 ids will be seen as fully featured { { "\033[>c", { "\033[>61;234;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>61;234;0c", }}, { "\033[=c", { "\033P!|FEFEFEFE\033\\", }}, { "\033[5n", { "\033[0n", }}, { "\033[6n", { "\033[{POS}R", }}, { "\033[?6n", { "\033[{POS}R", }}, { "\033[>q", { "", }}, { "\033[1x", { "", }}, { "\033]4;255;?\007", { "", }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: unknown full featured(0) seq:>=", allCapsBut({C(CURSOR_SHAPE_OSC50), C(88_COLOR)}), // should have all compliant capabilites. // excluding CURSOR_SHAPE_OSC50 because it's konsole specific and non standard // excluding 88_COLOR because it reduces 256 color palette to 88 color. "", WithoutGlitchPatching }, // --------------- { "DA3 new id promise (?CPR not safe) with terminal software self report" LINEINFO, // promise that newly allocated DA3 ids will be seen as fully featured { { "\033[>c", { "\033[>61;234;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>61;234;0c", }}, { "\033[=c", { "\033P!|FEFEFEFE\033\\", }}, { "\033[5n", { "\033[0n", }}, { "\033[6n", { "\033[{POS}R", }}, { "\033[?6n", { "\033[{POS}R", }}, { "\033[>q", { "\033P>|Someterm 34.56\033\\", }}, { "\033[1x", { "", }}, { "\033]4;255;?\007", { "", }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: unknown full featured(0) seq:>=", allCapsBut({C(CURSOR_SHAPE_OSC50), C(88_COLOR)}), // should have all compliant capabilites. // excluding CURSOR_SHAPE_OSC50 because it's konsole specific and non standard // excluding 88_COLOR because it reduces 256 color palette to 88 color. "Someterm 34.56", WithoutGlitchPatching }, // --------------- { "DA3 new id promise (no safe-CPR)" LINEINFO, // promise that newly allocated DA3 ids will be seen as fully featured { { "\033[>c", { "\033[>61;234;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>61;234;0c", }}, { "\033[=c", { "\033P!|FEFEFEFE\033\\", }}, { "\033[5n", { "\033[0n", }}, { "\033[6n", { "\033[{POS}R", }}, { "\033[?6n", { "", }}, { "\033[>q", { "", }}, { "\033[1x", { "", }}, { "\033]4;255;?\007", { "", }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: unknown full featured(0) seq:>=", allCapsBut({C(CURSOR_SHAPE_OSC50), C(88_COLOR)}), // should have all compliant capabilites. See above for details "", WithoutGlitchPatching }, // --------------- { "DA3 new id promise (no safe-CPR) with terminal software self report" LINEINFO, // promise that newly allocated DA3 ids will be seen as fully featured { { "\033[>c", { "\033[>61;234;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>61;234;0c", }}, { "\033[=c", { "\033P!|FEFEFEFE\033\\", }}, { "\033[5n", { "\033[0n", }}, { "\033[6n", { "\033[{POS}R", }}, { "\033[?6n", { "", }}, { "\033[>q", { "\033P>|Someterm 34.56\033\\", }}, { "\033[1x", { "", }}, { "\033]4;255;?\007", { "", }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: unknown full featured(0) seq:>=", allCapsBut({C(CURSOR_SHAPE_OSC50), C(88_COLOR)}), // should have all compliant capabilites. See above for details "Someterm 34.56", WithoutGlitchPatching }, // --------------- { "DA3 new id promise (safe-CPR, CSI>1c)" LINEINFO, // promise that newly allocated DA3 ids will be seen as fully featured { { "\033[>c", { "\033[>61;234;0c" }}, { "\033[>1c", { "\033[>61;234;0c" }}, { "\033[>0;1c", { "\033[>61;234;0c", }}, { "\033[=c", { "\033P!|FEFEFEFE\033\\", }}, { "\033[5n", { "\033[0n", }}, { "\033[6n", { "\033[{POS}R", }}, { "\033[?6n", { "\033[?{POS}R", }}, { "\033[>q", { "", }}, { "\033[1x", { "", }}, { "\033]4;255;?\007", { "", }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: unknown full featured(0) safe-CPR seq:>=", allCapsBut({C(CURSOR_SHAPE_OSC50), C(88_COLOR)}), // should have all compliant capabilites. // excluding CURSOR_SHAPE_OSC50 because it's konsole specific and non standard // excluding 88_COLOR because it reduces 256 color palette to 88 color. "", WithoutGlitchPatching }, // --------------- { "DA3 new id promise (safe-CPR, CSI>1c) with terminal software self report" LINEINFO, // promise that newly allocated DA3 ids will be seen as fully featured { { "\033[>c", { "\033[>61;234;0c" }}, { "\033[>1c", { "\033[>61;234;0c" }}, { "\033[>0;1c", { "\033[>61;234;0c", }}, { "\033[=c", { "\033P!|FEFEFEFE\033\\", }}, { "\033[5n", { "\033[0n", }}, { "\033[6n", { "\033[{POS}R", }}, { "\033[?6n", { "\033[?{POS}R", }}, { "\033[>q", { "\033P>|Someterm 34.56\033\\", }}, { "\033[1x", { "", }}, { "\033]4;255;?\007", { "", }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: unknown full featured(0) safe-CPR seq:>=", allCapsBut({C(CURSOR_SHAPE_OSC50), C(88_COLOR)}), // should have all compliant capabilites. // excluding CURSOR_SHAPE_OSC50 because it's konsole specific and non standard // excluding 88_COLOR because it reduces 256 color palette to 88 color. "Someterm 34.56", WithoutGlitchPatching }, // --------------- { "DA3 new id promise (?CPR not safe, CSI>1c)" LINEINFO, // promise that newly allocated DA3 ids will be seen as fully featured { { "\033[>c", { "\033[>61;234;0c" }}, { "\033[>1c", { "\033[>61;234;0c" }}, { "\033[>0;1c", { "\033[>61;234;0c", }}, { "\033[=c", { "\033P!|FEFEFEFE\033\\", }}, { "\033[5n", { "\033[0n", }}, { "\033[6n", { "\033[{POS}R", }}, { "\033[?6n", { "\033[{POS}R", }}, { "\033[>q", { "", }}, { "\033[1x", { "", }}, { "\033]4;255;?\007", { "", }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: unknown full featured(0) seq:>=", allCapsBut({C(CURSOR_SHAPE_OSC50), C(88_COLOR)}), // should have all compliant capabilites. // excluding CURSOR_SHAPE_OSC50 because it's konsole specific and non standard // excluding 88_COLOR because it reduces 256 color palette to 88 color. "", WithoutGlitchPatching }, // --------------- { "DA3 new id promise (?CPR not safe, CSI>1c) with terminal software self report" LINEINFO, // promise that newly allocated DA3 ids will be seen as fully featured { { "\033[>c", { "\033[>61;234;0c" }}, { "\033[>1c", { "\033[>61;234;0c" }}, { "\033[>0;1c", { "\033[>61;234;0c", }}, { "\033[=c", { "\033P!|FEFEFEFE\033\\", }}, { "\033[5n", { "\033[0n", }}, { "\033[6n", { "\033[{POS}R", }}, { "\033[?6n", { "\033[{POS}R", }}, { "\033[>q", { "\033P>|Someterm 34.56\033\\", }}, { "\033[1x", { "", }}, { "\033]4;255;?\007", { "", }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: unknown full featured(0) seq:>=", allCapsBut({C(CURSOR_SHAPE_OSC50), C(88_COLOR)}), // should have all compliant capabilites. // excluding CURSOR_SHAPE_OSC50 because it's konsole specific and non standard // excluding 88_COLOR because it reduces 256 color palette to 88 color. "Someterm 34.56", WithoutGlitchPatching }, // --------------- { "DA3 new id promise (no safe-CPR, CSI>1c)" LINEINFO, // promise that newly allocated DA3 ids will be seen as fully featured { { "\033[>c", { "\033[>61;234;0c" }}, { "\033[>1c", { "\033[>61;234;0c" }}, { "\033[>0;1c", { "\033[>61;234;0c", }}, { "\033[=c", { "\033P!|FEFEFEFE\033\\", }}, { "\033[5n", { "\033[0n", }}, { "\033[6n", { "\033[{POS}R", }}, { "\033[?6n", { "", }}, { "\033[>q", { "", }}, { "\033[1x", { "", }}, { "\033]4;255;?\007", { "", }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: unknown full featured(0) seq:>=", allCapsBut({C(CURSOR_SHAPE_OSC50), C(88_COLOR)}), // should have all compliant capabilites. See above for details "", WithoutGlitchPatching }, // --------------- { "DA3 new id promise (no safe-CPR, CSI>1c) with terminal software self report" LINEINFO, // promise that newly allocated DA3 ids will be seen as fully featured { { "\033[>c", { "\033[>61;234;0c" }}, { "\033[>1c", { "\033[>61;234;0c" }}, { "\033[>0;1c", { "\033[>61;234;0c", }}, { "\033[=c", { "\033P!|FEFEFEFE\033\\", }}, { "\033[5n", { "\033[0n", }}, { "\033[6n", { "\033[{POS}R", }}, { "\033[?6n", { "", }}, { "\033[>q", { "\033P>|Someterm 34.56\033\\", }}, { "\033[1x", { "", }}, { "\033]4;255;?\007", { "", }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: unknown full featured(0) seq:>=", allCapsBut({C(CURSOR_SHAPE_OSC50), C(88_COLOR)}), // should have all compliant capabilites. See above for details "Someterm 34.56", WithoutGlitchPatching }, // --------------- { "DA3 new id promise (no safe-CPR, CSI 1x)" LINEINFO, // promise that newly allocated DA3 ids will be seen as fully featured { { "\033[>c", { "\033[>61;234;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>61;234;0c", }}, { "\033[=c", { "\033P!|FEFEFEFE\033\\", }}, { "\033[5n", { "\033[0n", }}, { "\033[6n", { "\033[{POS}R", }}, { "\033[?6n", { "", }}, { "\033[>q", { "", }}, { "\033[1x", { "\033[3;1;1;128;128;1;0x", }}, { "\033]4;255;?\007", { "", }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: unknown full featured(0) seq:>=", allCapsBut({C(CURSOR_SHAPE_OSC50), C(88_COLOR)}), // should have all compliant capabilites. See above for details "", WithoutGlitchPatching }, // --------------- { "DA3 new id promise (no safe-CPR, CSI 1x) with terminal software self report" LINEINFO, // promise that newly allocated DA3 ids will be seen as fully featured { { "\033[>c", { "\033[>61;234;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>61;234;0c", }}, { "\033[=c", { "\033P!|FEFEFEFE\033\\", }}, { "\033[5n", { "\033[0n", }}, { "\033[6n", { "\033[{POS}R", }}, { "\033[?6n", { "", }}, { "\033[>q", { "\033P>|Someterm 34.56\033\\", }}, { "\033[1x", { "\033[3;1;1;128;128;1;0x", }}, { "\033]4;255;?\007", { "", }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: unknown full featured(0) seq:>=", allCapsBut({C(CURSOR_SHAPE_OSC50), C(88_COLOR)}), // should have all compliant capabilites. See above for details "Someterm 34.56", WithoutGlitchPatching }, // --------------- { "DA3 new id promise (no safe-CPR, CSI>1c, CSI 1x)" LINEINFO, // promise that newly allocated DA3 ids will be seen as fully featured { { "\033[>c", { "\033[>61;234;0c" }}, { "\033[>1c", { "\033[>61;234;0c" }}, { "\033[>0;1c", { "\033[>61;234;0c", }}, { "\033[=c", { "\033P!|FEFEFEFE\033\\", }}, { "\033[5n", { "\033[0n", }}, { "\033[6n", { "\033[{POS}R", }}, { "\033[?6n", { "", }}, { "\033[>q", { "", }}, { "\033[1x", { "\033[3;1;1;128;128;1;0x", }}, { "\033]4;255;?\007", { "", }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: unknown full featured(0) seq:>=", allCapsBut({C(CURSOR_SHAPE_OSC50), C(88_COLOR)}), // should have all compliant capabilites. See above for details "", WithoutGlitchPatching }, // --------------- { "DA3 new id promise (no safe-CPR, CSI>1c, CSI 1x) with terminal software self report" LINEINFO, // promise that newly allocated DA3 ids will be seen as fully featured { { "\033[>c", { "\033[>61;234;0c" }}, { "\033[>1c", { "\033[>61;234;0c" }}, { "\033[>0;1c", { "\033[>61;234;0c", }}, { "\033[=c", { "\033P!|FEFEFEFE\033\\", }}, { "\033[5n", { "\033[0n", }}, { "\033[6n", { "\033[{POS}R", }}, { "\033[?6n", { "", }}, { "\033[>q", { "\033P>|Someterm 34.56\033\\", }}, { "\033[1x", { "\033[3;1;1;128;128;1;0x", }}, { "\033]4;255;?\007", { "", }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: unknown full featured(0) seq:>=", allCapsBut({C(CURSOR_SHAPE_OSC50), C(88_COLOR)}), // should have all compliant capabilites. See above for details "Someterm 34.56", WithoutGlitchPatching }, // --------------- { "vte 0.28.0" LINEINFO, { { "\033[>c", { "\033[>1;2800;0c" }}, { "\033[>1c", { "\033[>1;2800;0c" }}, { "\033[>0;1c", { "", "XXXXXXX" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "", "XXXX" }}, { "\033[1x", { "\033[?x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: vte(2800) safe-CPR seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE) }, "", WithoutGlitchPatching }, // --------------- { "vte 0.36.0" LINEINFO, { { "\033[>c", { "\033[>1;3600;0c" }}, { "\033[>1c", { "\033[>1;3600;0c" }}, { "\033[>0;1c", { "", "XXXXXXX" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "", "XXXX" }}, { "\033[1x", { "\033[?x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: vte(3600) safe-CPR seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE) }, "", WithoutGlitchPatching }, // --------------- { "vte 0.40.0" LINEINFO, { { "\033[>c", { "\033[>1;4000;0c" }}, { "\033[>1c", { "\033[>1;4000;0c" }}, { "\033[>0;1c", { "", "XXXXXXX" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "", "XXXX" }}, { "\033[1x", { "\033[?x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: vte(4000) safe-CPR seq:", { C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE) }, "", WithoutGlitchPatching }, // --------------- { "vte 0.54.0" LINEINFO, { { "\033[>c", { "\033[>65;5400;1c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>65;5400;1c" }}, { "\033[=c", { "\033P!|7E565445\033\\" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS};1R" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;120;120;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\033\\" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: vte(5400) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(TITLE_RESTORE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE) }, "", WithoutGlitchPatching }, // --------------- { "vte 0.55.0" LINEINFO, { { "\033[>c", { "\033[>65;5500;1c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>65;5500;1c" }}, { "\033[=c", { "\033P!|7E565445\033\\" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS};1R" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;120;120;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: vte(5500) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(TITLE_RESTORE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE) }, "", WithoutGlitchPatching }, // --------------- { "vte 0.75.0" LINEINFO, { { "\033[>c", { "\033[>65;7500;1c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>65;7500;1c" }}, { "\033[=c", { "\033P!|7E565445\033\\" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS};1R" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;120;120;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: vte(7500) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(TITLE_RESTORE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE) }, "", WithoutGlitchPatching }, // --------------- { "vte 0.75.1" LINEINFO, { { "\033[>c", { "\033[>61;7501;1c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>61;7501;1c" }}, { "\033[=c", { "\033P!|7E565445\033\\" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS};1R" }}, { "\033[>q", { "\033P>|VTE(7501)\033\\" }}, { "\033[1x", { "\033[x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: vte(7501) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(TITLE_RESTORE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE) }, "VTE(7501)", WithoutGlitchPatching }, // --------------- { "vte 0.78.2" LINEINFO, { { "\033[>c", { "\033[>61;7802;1c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>61;7802;1c" }}, { "\033[=c", { "\033P!|7E565445\033\\" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS};1R" }}, { "\033[>q", { "\033P>|VTE(7802)\033\\" }}, { "\033[1x", { "\033[3;1;1;120;120;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: vte(7802) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(TITLE_RESTORE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE) }, "VTE(7802)", WithoutGlitchPatching }, // --------------- { "ficitious vte like without other ids than DCS>q" LINEINFO, { { "\033[>c", { "\033[>0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS};1R" }}, { "\033[>q", { "\033P>|VTE(7501)\033\\" }}, { "\033[1x", { "\033[3;1;1;120;120;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: vte(7501) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(TITLE_RESTORE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE) }, "VTE(7501)", WithoutGlitchPatching }, // --------------- { "kitty 0.13.3" LINEINFO, { { "\033[>c", { "\033[>1;4000;13c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>1;4000;13c" }}, { "\033[=c", { "", "c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\033\\" }}, { "\033P+q544e\033\\",{ "\033P1+r544e=787465726d2d6b69747479\033\\" }}, }, "Type: base(0) safe-CPR seq:>", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", NeedsGlitchPatching }, // --------------- { "kitty 0.14.0" LINEINFO, { { "\033[>c", { "\033[>1;4000;14c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>1;4000;14c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\033\\" }}, { "\033P+q544e\033\\",{ "\033P1+r544e=787465726d2d6b69747479\033\\" }}, }, "Type: kitty(14) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(TITLE_RESTORE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "kitty 0.31.0" LINEINFO, { { "\033[>c", { "\033[>1;4000;31c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "\033P>|kitty(0.31.0)\033\\" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\033\\" }}, { "\033P+q544e\033\\", { "\033P1+r544e=787465726d2d6b69747479\033\\" }}, }, "Type: kitty(31) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(TITLE_RESTORE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "kitty(0.31.0)", WithoutGlitchPatching }, // --------------- { "st 0.8.2" LINEINFO, { { "\033[>c", { "" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: incompatible with input handling(0) seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "cursor position, CSI>c but no terminal status" LINEINFO, { { "\033[>c", { "\033[>0;115;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: incompatible with input handling(0) seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "no cursor position but terminal status" LINEINFO, { { "\033[>c", { "" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: toodumb(0) seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "cursor position and terminal status" LINEINFO, { { "\033[>c", { "" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "cursor position, terminal status and terminal software self report" LINEINFO, { { "\033[>c", { "" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "\033P>|fictional\033\\" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "fictional", WithoutGlitchPatching }, // --------------- { "no cursor position but terminal status and ESC[>c" LINEINFO, { { "\033[>c", { "\033[>0;115;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: toodumb(0) seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, { "only ESC[>c" LINEINFO, { { "\033[>c", { "\033[>0;115;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "" }}, { "\033[6n", { "" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: toodumb(0) seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "cursor position, terminal status and ESC[>c" LINEINFO, { { "\033[>c", { "\033[>0;115;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "cursor position, terminal status and junk with ESC[>c" LINEINFO, { { "\033[>c", { "", "XX" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: misparsing(0) seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", NeedsGlitchPatching }, // --------------- { "cursor position, terminal status and ESC[1x" LINEINFO, { { "\033[>c", { "", "" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;120;120;1;0x" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "cursor position, terminal status, ESC[>1c and ?CPR not safe" LINEINFO, { { "\033[>c", { "\033[>1;4000;13c" }}, { "\033[>1c", { "\033[>1;4000;13c" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;120;120;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\033\\" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "cursor position, terminal status, ESC[>1c, ?CPR not safe and terminal software self report" LINEINFO, { { "\033[>c", { "\033[>1;4000;13c" }}, { "\033[>1c", { "\033[>1;4000;13c" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[{POS}R" }}, { "\033[>q", { "\033P>|fictional\033\\" }}, { "\033[1x", { "\033[3;1;1;120;120;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\033\\" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "fictional", WithoutGlitchPatching }, // --------------- { "cursor position, terminal status, ESC[=c glitches, ESC[>1c, safe-CPR" LINEINFO, { { "\033[>c", { "\033[>1;4000;13c" }}, { "\033[>1c", { "\033[>1;4000;13c" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "", "c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;120;120;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\033\\" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) safe-CPR seq:>", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", NeedsGlitchPatching }, // --------------- { "cursor position, terminal status, ESC[=c glitches 2x, ESC[>1c, safe-CPR" LINEINFO, { { "\033[>c", { "\033[>1;4000;13c" }}, { "\033[>1c", { "\033[>1;4000;13c" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "", "cc" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;120;120;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\033\\" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) safe-CPR seq:>", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", NeedsGlitchPatching }, // --------------- { "cursor position, terminal status, ESC[=c glitches, ESC[>1c" LINEINFO, { { "\033[>c", { "\033[>1;4000;13c" }}, { "\033[>1c", { "\033[>1;4000;13c" }}, { "\033[>0;1c", { "\033[>1;4000;13c" }}, { "\033[=c", { "", "c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;120;120;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\033\\" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", NeedsGlitchPatching }, // --------------- { "cursor position, terminal status, ESC[=c glitches, ESC[>1c, ESC[>0;1c, safe-CPR" LINEINFO, { { "\033[>c", { "\033[>1;4000;13c" }}, { "\033[>1c", { "\033[>1;4000;13c" }}, { "\033[>0;1c", { "\033[>1;4000;13c" }}, { "\033[=c", { "", "c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;120;120;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\033\\" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) safe-CPR seq:>", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", NeedsGlitchPatching }, // --------------- { "cursor position, terminal status, ESC[>1c, ESC[>0;1c" LINEINFO, { { "\033[>c", { "" }}, { "\033[>1c", { "\033[>1;4000;13c" }}, { "\033[>0;1c", { "\033[>1;4000;13c\033[>1;4000;13c" }}, { "\033[=c", { "", }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "cursor position, terminal status, ESC[>1c, ESC[>0;1c and terminal software self report" LINEINFO, { { "\033[>c", { "" }}, { "\033[>1c", { "\033[>1;4000;13c" }}, { "\033[>0;1c", { "\033[>1;4000;13c\033[>1;4000;13c" }}, { "\033[=c", { "", }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "\033P>|fictional\033\\" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "fictional", WithoutGlitchPatching }, // --------------- { "cursor position, terminal status, ESC[>c and aliases ESC[=c" LINEINFO, { { "\033[>c", { "\033[>1;95;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "\033[?1;2c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[24;1R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "cursor position, terminal status, ESC[>c, ESC[>1c, aliases ESC[=c, ?CPR is not safe and ESC[1x" LINEINFO, { { "\033[>c", { "\033[>1;95;0c" }}, { "\033[>1c", { "\033[>1;95;0c" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "\033[?1;2c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[24;1R" }}, { "\033[?6n", { "\033[24;1R" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;120;120;1;0x" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "weston-terminal 8.0.0" LINEINFO, { { "\033[>c", { "\033[?6c" }}, { "\033[>1c", { "\033[?6c" }}, { "\033[>0;1c", { "\033[?6c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: toodumb(0) seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "alacritty 0.2.9" LINEINFO, { { "\033[>c", { "\033[?6c" }}, { "\033[>1c", { "\033[?6c" }}, { "\033[>0;1c", { "\033[?6c" }}, { "\033[=c", { "\033[?6c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: toodumb(0) seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "alacritty 0.4.0" LINEINFO, { { "\033[>c", { "" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "alacritty 0.12.2" LINEINFO, { { "\033[>c", { "\033[>0;1901;1c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>0;1901;1c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\", { "" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "eterm 0.9.6" LINEINFO, { { "\033[>c", { "" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "", "+q544e" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "qml-module-termwidget 0.2+git20220109.6322802" LINEINFO, // used in cool-retro-term and lomiri-terminal-app { { "\033[>c", { "\033[>0;115;0c" }}, { "\033[>1c", { "\033[>0;115;0c" }}, { "\033[>0;1c", { "\033[>0;115;0c\033[>0;115;0c" }}, { "\033[=c", { "", "c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;112;112;1;0x" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "", "+q544e" }}, }, "Type: konsole(0) seq:>", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(CURSOR_SHAPE_OSC50), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "", NeedsGlitchPatching }, // --------------- { "deepin-termial 5.9.40" LINEINFO, { { "\033[>c", { "\033[>0;115;0c" }}, { "\033[>1c", { "\033[>0;115;0c" }}, { "\033[>0;1c", { "\033[>0;115;0c\033[>0;115;0c" }}, { "\033[=c", { "", "c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;112;112;1;0x" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "", "+q544e" }}, }, "Type: konsole(0) seq:>", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(CURSOR_SHAPE_OSC50), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "", NeedsGlitchPatching }, // --------------- { "qtermwidget 1.3.0" LINEINFO, // use in cool-retro-term and lomiri-terminal-app { { "\033[>c", { "\033[>0;115;0c" }}, { "\033[>1c", { "\033[>0;115;0c" }}, { "\033[>0;1c", { "\033[>0;115;0c\033[>0;115;0c" }}, { "\033[=c", { "", "c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;112;112;1;0x" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "", "+q544e" }}, }, "Type: konsole(0) seq:>", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(CURSOR_SHAPE_OSC50), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "", NeedsGlitchPatching }, // --------------- { "konsole 14.12.3" LINEINFO, { { "\033[>c", { "\033[>0;115;0c" }}, { "\033[>1c", { "\033[>0;115;0c" }}, { "\033[>0;1c", { "\033[>0;115;0c\033[>0;115;0c" }}, { "\033[=c", { "", "c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;112;112;1;0x" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "", "+q544e" }}, }, "Type: konsole(0) seq:>", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(CURSOR_SHAPE_OSC50), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "", NeedsGlitchPatching }, // --------------- { "konsole 22.03.70" LINEINFO, { { "\033[>c", { "\033[>0;115;0c" }}, { "\033[>1c", { "\033[>0;115;0c" }}, { "\033[>0;1c", { "\033[>0;115;0c\033[>0;115;0c" }}, { "\033[=c", { "\033P!|7E4B4445\033\\"}}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;112;112;1;0x" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: konsole(220370) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(CURSOR_SHAPE_OSC50), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "konsole 23.08.1" LINEINFO, { { "\033[>c", { "\033[>1;115;0c" }}, { "\033[>1c", { "\033[>1;115;0c" }}, { "\033[>0;1c", { "\033[>1;115;0c\033[>1;115;0c" }}, { "\033[=c", { "\033P!|7E4B4445\033\\" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "\033P>|Konsole 23.08.1\033\\" }}, { "\033[1x", { "\033[3;1;1;112;112;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\", { "" }}, }, "Type: konsole(230801) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(CURSOR_SHAPE_OSC50), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "Konsole 23.08.1", WithoutGlitchPatching }, // --------------- { "mlterm 3.8.9" LINEINFO, { { "\033[>c", { "\033[>24;279;0c" }}, { "\033[>1c", { "\033[>24;279;0c" }}, { "\033[>0;1c", { "\033[>24;279;0c" }}, { "\033[=c", { "\033P!|000000\033\\" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS};1R" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;112;112;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\033\\" }}, { "\033P+q544e\033\\",{ "\033P1+r544e=6D6C7465726D\033\\" }}, }, "Type: mlterm(0) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "mlterm 3.9.3" LINEINFO, { { "\033[>c", { "\033[>24;279;0c" }}, { "\033[>1c", { "\033[>24;279;0c" }}, { "\033[>0;1c", { "\033[>24;279;0c" }}, { "\033[=c", { "\033P!|000000\033\\" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS};1R" }}, { "\033[>q", { "\033P>|mlterm(3.9.3)\033\\" }}, { "\033[1x", { "\033[3;1;1;112;112;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\033\\" }}, { "\033P+q544e\033\\", { "\033P1+r544e=6D6C7465726D\033\\" }}, }, "Type: mlterm(3009003) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "mlterm(3.9.3)", WithoutGlitchPatching }, // --------------- { "mosh 1.3.2" LINEINFO, { { "\033[>c", { "\033[>1;10;0c" }}, { "\033[>1c", { "\033[>1;10;0c" }}, { "\033[>0;1c", { "\033[>1;10;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "pangoterm with libvterm 0.1.3" LINEINFO, { { "\033[>c", { "\033[>0;100;0c" }}, { "\033[>1c", { "\033[>0;100;0c" }}, { "\033[>0;1c", { "\033[>0;100;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "like pangoterm with libvterm 0.1.3 but with terminal software self report" LINEINFO, { { "\033[>c", { "\033[>0;100;0c" }}, { "\033[>1c", { "\033[>0;100;0c" }}, { "\033[>0;1c", { "\033[>0;100;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "\033P>|fictional\033\\" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "fictional", WithoutGlitchPatching }, // --------------- { "pterm/putty 0.73" LINEINFO, { { "\033[>c", { "\033[>0;100;0c" }}, { "\033[>1c", { "\033[>0;100;0c" }}, { "\033[>0;1c", { "\033[>0;100;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "", "+q544e" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "pterm/putty 0.79" LINEINFO, { { "\033[>c", { "\033[>0;136;0c" }}, { "\033[>1c", { "\033[>0;136;0c" }}, { "\033[>0;1c", { "\033[>0;136;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;112;112;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\", { "", "+q544e" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "screen 3.9.15" LINEINFO, { { "\033[>c", { "\033[>83;30915;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>83;30915;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;112;112;1;0x" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "", "+q544e" }}, }, "Type: screen(30915) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "Teraterm 3.105" LINEINFO, { { "\033[>c", { "\033[>32;331;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>32;331;0c" }}, { "\033[=c", { "\033P!|FFFFFFFF\033\\" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\",{ "\033P0+r\033\\" }}, }, "Type: unknown full featured(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(TITLE_RESTORE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "terminology 1.6.0" LINEINFO, { { "\033[>c", { "\033[>61;337;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>61;337;0c" }}, { "\033[=c", { "\033P!|7E7E5459\033\\" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS};1R" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: terminology(0) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "terminology 1.7.0" LINEINFO, { { "\033[>c", { "\033[>61;337;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>61;337;0c" }}, { "\033[=c", { "\033P!|7E7E5459\033\\" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS};1R" }}, { "\033[>q", { "\033P>|terminology 1.7.0\033\\" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: terminology(1007000) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(TITLE_RESTORE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "terminology 1.7.0", WithoutGlitchPatching }, // --------------- { "terminus 1.0.104 with xtermjs" LINEINFO, { { "\033[>c", { "\033[>0;276;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>0;276;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "tmux 0.9" LINEINFO, { { "\033[>c", { "" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "", "+q544e" }}, }, "Type: incompatible with input handling(0) seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "tmux 1.3" LINEINFO, { { "\033[>c", { "" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "tmux 1.7" LINEINFO, { { "\033[>c", { "\033[>0;95;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>0;95;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "tmux 2.0" LINEINFO, { { "\033[>c", { "\033[>84;0;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>84;0;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: tmux(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "tmux 3.3a" LINEINFO, { { "\033[>c", { "\033[>84;0;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>84;0;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "\033P>|tmux 3.3a\033\\" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\", { "" }}, }, "Type: tmux(3003000) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "tmux 3.3a", WithoutGlitchPatching }, // --------------- { "like tmux 2.0 but with terminal software self report" LINEINFO, { { "\033[>c", { "\033[>84;0;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>84;0;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "\033P>|fictional\033\\" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: tmux(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "fictional", WithoutGlitchPatching }, // --------------- { "rxvt-unicode 9.09" LINEINFO, { { "\033[>c", { "\033[>85;95;0c" }}, { "\033[>1c", { "\033[>85;95;0c" }}, { "\033[>0;1c", { "\033[>85;95;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;128;128;1;0x" }}, { "\033]4;255;?\007", { "\033]4;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: urxvt(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(CLEARED_COLORING), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "rxvt-unicode 9.09 with 88color compile time option" LINEINFO, { { "\033[>c", { "\033[>85;95;0c" }}, { "\033[>1c", { "\033[>85;95;0c" }}, { "\033[>0;1c", { "\033[>85;95;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;128;128;1;0x" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: urxvt(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(88_COLOR), C(CLEARED_COLORING), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "like rxvt-unicode 9.09 but with terminal software self report" LINEINFO, { { "\033[>c", { "\033[>85;95;0c" }}, { "\033[>1c", { "\033[>85;95;0c" }}, { "\033[>0;1c", { "\033[>85;95;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "\033P>|fictional\033\\" }}, { "\033[1x", { "\033[3;1;1;128;128;1;0x" }}, { "\033]4;255;?\007", { "\033]4;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: urxvt(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(CLEARED_COLORING), C(CLEARED_COLORING_DEFCOLOR) }, "fictional", WithoutGlitchPatching }, // --------------- { "like rxvt-unicode 9.09 with 88color compile time option but with terminal software self report" LINEINFO, { { "\033[>c", { "\033[>85;95;0c" }}, { "\033[>1c", { "\033[>85;95;0c" }}, { "\033[>0;1c", { "\033[>85;95;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "\033P>|fictional\033\\" }}, { "\033[1x", { "\033[3;1;1;128;128;1;0x" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: urxvt(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(88_COLOR), C(CLEARED_COLORING), C(CLEARED_COLORING_DEFCOLOR) }, "fictional", WithoutGlitchPatching }, // --------------- { "fbterm" LINEINFO, { { "\033[>c", { "", "c" }}, { "\033[>1c", { "", "1c" }}, { "\033[>0;1c", { "", "0;1c" }}, { "\033[=c", { "", "c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[{POS}R" }}, { "\033[>q", { "", "q" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "", ";255;?" }}, { "\033P+q544e\033\\",{ "", "+q544e" }}, }, "Type: misparsing(0) seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", NeedsGlitchPatching }, // --------------- { "kmscon" LINEINFO, // using libtsm { { "\033[>c", { "\033[>1;1;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "\033[?60;1;6;9;15c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "foot 1.13.1" LINEINFO, { { "\033[>c", { "\033[>1;011301;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>1;011301;0c" }}, { "\033[=c", { "\033P!|464f4f54\033\\" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "\033P>|foot(1.13.1)\033\\" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\", { "\033P1+r544e=666F6F74\033\\" }}, }, "Type: unknown full featured(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(TITLE_RESTORE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "foot(1.13.1)", WithoutGlitchPatching }, // --------------- { "stterm" LINEINFO, { { "\033[>c", { "" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\", { "" }}, }, "Type: incompatible with input handling(0) seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "xiterm+thai" LINEINFO, { { "\033[>c", { "\033[?1;2c" }}, { "\033[>1c", { "\033[?1;2c" }}, { "\033[>0;1c", { "\033[?1;2c" }}, { "\033[=c", { "\033[?1;2c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\", { "", "+q544e" }}, }, "Type: toodumb(0) seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "zutty" LINEINFO, { { "\033[>c", { "\033[>64;0;0c" }}, { "\033[>1c", { "\033[>64;0;0c" }}, { "\033[>0;1c", { "\033[>64;0;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\033\\" }}, { "\033P+q544e\033\\", { "" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "linux vc" LINEINFO, { { "\033[>c", { "" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "", "+q544e" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "netbsd 9.1 wscon" LINEINFO, // also openbsd 6.8 { { "\033[>c", { "\033[>24;20;0c" }}, { "\033[>1c", { "\033[>24;20;0c" }}, { "\033[>0;1c", { "\033[>24;20;0c" }}, { "\033[=c", { "", "c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { TODO }}, // actually leaves terminal in ST state in netbsd (openbsd is robust) { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", NeedsGlitchPatching }, // --------------- { "iTerm2 3.3.12" LINEINFO, { { "\033[>c", { "\033[>0;95;0c" }}, { "\033[>1c", { "\033[>0;95;0c" }}, { "\033[>0;1c", { "\033[>0;95;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:ee/ee/ed\007" }}, { "\033P+q544e\033\\",{ "\033P1+r544E=695465726d32\033\\" }}, }, "Type: iterm2(0) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "iTerm2 3.4.20201030-nightly" LINEINFO, { { "\033[>c", { "\033[>0;96;0c" }}, { "\033[>1c", { "\033[>0;96;0c" }}, { "\033[>0;1c", { "\033[>0;96;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "\033P>|iTerm2 3.4.20201030-nightly\033\\" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:ee/ee/ed\007" }}, { "\033P+q544e\033\\",{ "\033P1+r544E=695465726d32\033\\" }}, }, "Type: iterm2(3004000) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "iTerm2 3.4.20201030-nightly", WithoutGlitchPatching }, // --------------- { "Apple Terminal 433" LINEINFO, { { "\033[>c", { "\033[>1;95;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "\033[?1;2c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;112;112;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\",{ "", "+q544e" }}, }, "Type: apple terminal(0) seq:>", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "mintty 3.2.0" LINEINFO, { { "\033[>c", { "\033[>77;30200;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>77;30200;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "\033P>|mintty 3.2.0\033\\" }}, { "\033[1x", { "\033[3;1;1;120;120;1;0x" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\033\\" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: mintty(30200) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(TITLE_RESTORE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(MAY_TRY_TAGGED_PASTE), C(CLEARED_COLORING_DEFCOLOR) }, "mintty 3.2.0", WithoutGlitchPatching }, // --------------- { "conhost.exe" LINEINFO, { { "\033[>c", { "" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "", "+q544e" }}, }, "Type: incompatible with input handling(0) seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "microsoft terminal 1.1.1812.0" LINEINFO, { { "\033[>c", { "" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ TODO }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "microsoft terminal 1.3.2382.0" LINEINFO, { { "\033[>c", { "\033[>0;10;1c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "" }}, { "\033[=c", { "\033P!|00000000\033\\" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: microsoft terminal(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "microsoft terminal 1.19.3172.0" LINEINFO, { { "\033[>c", { "\033[>0;10;1c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>0;10;1c" }}, { "\033[=c", { "\033P!|00000000\033\\" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;1;1;128;128;1;0x" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: microsoft terminal(0) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(TRUECOLOR_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "ZOC Terminal 7.25.8" LINEINFO, { { "\033[>c", { "\033[>1;206;0c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[>1;206;0c" }}, { "\033[=c", { std::string("\x90!|\007%\010\000\x9c", 8) }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[{POS};1R" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "jetbrains JediTerm 2.31" LINEINFO, { { "\033[>c", { "\033[?6c" }}, { "\033[>1c", { "" }}, { "\033[>0;1c", { "\033[?6c" }}, { "\033[=c", { "\033[?6c", "=" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "", "+q544e" }}, }, "Type: toodumb(0) seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "xshell 7 beta" LINEINFO, { { "\033[>c", { "\033[>0;136;0c" }}, { "\033[>1c", { "\033[>0;136;0c" }}, { "\033[>0;1c", { "\033[>0;136;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "ios: Termius 4.6.7" LINEINFO, { { "\033[>c", { "\033[>0;95;0c" }}, { "\033[>1c", { "\033[>0;95;0c" }}, { "\033[>0;1c", { "\033[>0;95;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[?{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "\033[3;5;2;64;64;1;0x" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: base(0) safe-CPR seq:>=", { C(CSI_POSTFIX_MOD), C(MAY_TRY_CURSOR_SHAPE), C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, // --------------- { "android: connectbot 1.9.5" LINEINFO, { { "\033[>c", { "", "c" }}, { "\033[>1c", { "", "1c" }}, { "\033[>0;1c", { "", "0;1c" }}, { "\033[=c", { "", "c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "", "q" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: misparsing(0) seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", NeedsGlitchPatching }, // --------------- { "android: JuiceSSH 3.2.0" LINEINFO, { { "\033[>c", { "", "c" }}, { "\033[>1c", { "", "1c" }}, { "\033[>0;1c", { "", "0;1c" }}, { "\033[=c", { "", "c" }}, { "\033[5n", { "\033[0n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "" }}, { "\033[>q", { "", "q" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "" }}, { "\033P+q544e\033\\",{ "", "+q544e" }}, }, "Type: misparsing(0) seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", NeedsGlitchPatching }, // --------------- { "terminus 1.0.104 with hterm" LINEINFO, { { "\033[>c", { "\033[>0;256;0c" }}, { "\033[>1c", { "\033[>0;256;0c" }}, { "\033[>0;1c", { "\033[>0;256;0c" }}, { "\033[=c", { "" }}, { "\033[5n", { "\0330n" }}, { "\033[6n", { "\033[{POS}R" }}, { "\033[?6n", { "\033[{POS}R" }}, { "\033[>q", { "" }}, { "\033[1x", { "" }}, { "\033]4;255;?\007", { "\033]4;255;rgb:eeee/eeee/eeee\007" }}, { "\033P+q544e\033\\",{ "" }}, }, "Type: incompatible with input handling(0) seq:", { C(MAY_TRY_CURSOR_SHAPE_BAR), C(EXTENDED_CHARSET), C(TRUECOLOR_MAYBE_SUPPORTED), C(CLEARED_COLORING), C(7BIT_ST), C(CLEARED_COLORING_DEFCOLOR) }, "", WithoutGlitchPatching }, }; static std::string replace(const std::string& str, const std::string& from, const std::string& to) { size_t start_pos = str.find(from); if (start_pos == std::string::npos) { return str; } std::string ret = str; ret.replace(start_pos, from.length(), to); return ret; } static void event_callback(void *ctx, termpaint_event *event) { bool& events_leaked = *static_cast(ctx); if (event->type != TERMPAINT_EV_AUTO_DETECT_FINISHED) { events_leaked = true; } } #define CHECK_UPDATE_OK(...) CHECK( __VA_ARGS__ ); \ if (!(__VA_ARGS__)) ok = false TEST_CASE("finger printing") { const TestCase testcase = GENERATE(values(tests)); CAPTURE(testcase.name); REQUIRE(testcase.seq.size() == allSeq.size()); // test case sanity check bool ok = true; for (const auto& seq : allSeq) { CHECK_UPDATE_OK(testcase.seq.find(seq) != testcase.seq.end()); // test case sanity check } auto theTest = [&] (int initialX, int initialY, int width, int height) { struct Integration : public termpaint_integration { std::string sent; std::string log; } integration; bool events_leaked = false; termpaint_integration_init(&integration, [] (termpaint_integration*) {}, // free [] (termpaint_integration *integration_generic, const char *data, int length) { auto& integration = *static_cast(integration_generic); integration.sent.append(data, length); }, [] (termpaint_integration*) {} // flush ); termpaint_integration_set_logging_func(&integration, [] (termpaint_integration *integration_generic, const char *data, int length) { auto& integration = *static_cast(integration_generic); integration.log.append(data, length); }); terminal_uptr term; term.reset(termpaint_terminal_new(&integration)); termpaint_terminal_set_event_cb(term, event_callback, &events_leaked); termpaint_terminal_auto_detect(term); PatchStatus glitchPatchingWasNeeded = PatchStatus::WithoutGlitchPatching; std::set> glitched; int cursorX = initialX; int cursorY = initialY; bool pendingWrap = false; auto wrapIfNeeded = [&] { if (pendingWrap) { pendingWrap = false; cursorX = 0; if (cursorY + 1 >= height) { std::set> glitchedNew; for (auto pos : glitched) { glitchedNew.insert(std::make_tuple(std::get<0>(pos), std::get<1>(pos) - 1)); } glitched = glitchedNew; } else { cursorY += 1; } } }; auto advance = [&] { if (cursorX + 1 < width) { cursorX += 1; } else { pendingWrap = true; } }; auto isNumeric = [](char c) { return '0' <= c && c <= '9';}; for (size_t i = 1; i <= integration.sent.size(); i++) { const std::string part = integration.sent.substr(0, i); if (part == " ") { wrapIfNeeded(); CHECK(glitched.find(std::make_tuple(cursorX, cursorY)) != glitched.end()); glitched.erase(std::make_tuple(cursorX, cursorY)); advance(); integration.sent = integration.sent.substr(1); i = 0; } else if (part == "\010") { cursorX = std::max(cursorX - 1, 0); integration.sent = integration.sent.substr(1); i = 0; } else if (part.size() >= 4 && part.substr(0, 2) == "\033[" && part[part.length()-1] == 'H' && isNumeric(part[2]) && isNumeric(part[part.length()-2])) { size_t colOffs; CHECK_UPDATE_OK((colOffs = part.find_first_of(';')) != std::string::npos); cursorX = std::stoi(part.substr(colOffs + 1)) - 1; cursorY = std::stoi(part.substr(2)) - 1; pendingWrap = false; integration.sent = integration.sent.substr(i); i = 0; } else { const auto it = testcase.seq.find(part); if (it != testcase.seq.end()) { const auto result = it->second; REQUIRE(result.reply != TODO); if (result.reply.size()) { std::string replyAdjusted = result.reply; std::string cursorPosition = std::to_string(cursorY + 1) + ";" + std::to_string(cursorX + 1); replyAdjusted = replace(replyAdjusted, "{POS}", cursorPosition); termpaint_terminal_add_input_data(term, replyAdjusted.data(), replyAdjusted.size()); } if (result.junk.size()) { glitchPatchingWasNeeded = PatchStatus::NeedsGlitchPatching; for (size_t j = 0; j < result.junk.size(); j++) { wrapIfNeeded(); glitched.insert(std::make_tuple(cursorX, cursorY)); advance(); } } integration.sent = integration.sent.substr(i); i = 0; } } } INFO(integration.log); CHECK(integration.log.find("ran off autodetect") == std::string::npos); REQUIRE(integration.sent == ""); CHECK_UPDATE_OK(termpaint_terminal_auto_detect_state(term) != termpaint_auto_detect_running); CHECK_UPDATE_OK(!events_leaked); bool misdetectionExpected = false; if (initialX + 1 == width && ((testcase.seq.at("\033[>c").junk.size() == 1) || (testcase.seq.at("\033[=c").junk.size() == 1))) { misdetectionExpected = true; } auto glitchedFiltered = glitched; // The last column can not be reliabily cleared, because cursor position does not advance // instead an invisible wrap pending flag is set. Just ignore glitches there for now. glitchedFiltered.erase(std::make_tuple(width - 1, cursorY)); CAPTURE(cursorY); CHECK_UPDATE_OK(glitchedFiltered == std::set>{}); char auto_detect_result_text[1000]; termpaint_terminal_auto_detect_result_text(term, auto_detect_result_text, sizeof (auto_detect_result_text)); if (!misdetectionExpected) { // wrapping messes with misparsing detection CHECK_UPDATE_OK(auto_detect_result_text == testcase.auto_detect_result_text); } std::vector detected_caps; for (int cap : allCaps) { if (termpaint_terminal_capable(term, cap)) { detected_caps.push_back(cap); } } if (!misdetectionExpected) { // wrapping messes with misparsing detection CHECK_UPDATE_OK(detected_caps == testcase.caps); } if (testcase.self_reported_name_and_version.size()) { REQUIRE(termpaint_terminal_self_reported_name_and_version(term)); CHECK_UPDATE_OK(termpaint_terminal_self_reported_name_and_version(term) == testcase.self_reported_name_and_version); } else { CHECK_UPDATE_OK(termpaint_terminal_self_reported_name_and_version(term) == NULL); } CHECK_UPDATE_OK(glitchPatchingWasNeeded == testcase.glitchPatching); term.reset(); termpaint_integration_deinit(&integration); }; theTest(0, 0, 40, 4); if (ok && testcase.glitchPatching == NeedsGlitchPatching) { SECTION("Varying terminal positions") { struct TermSetup { int initialX, initialY, width, height; }; const auto setup = GENERATE( TermSetup{38, 0, 40, 4}, TermSetup{39, 0, 40, 4}, TermSetup{0, 3, 40, 4}, TermSetup{39, 3, 40, 4} ); CAPTURE(setup.initialX, setup.initialY, setup.width, setup.height); theTest(setup.initialX, setup.initialY, setup.width, setup.height); } } } termpaint-0.3.1/tests/hashtest.cpp000066400000000000000000000067021477303547200172300ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include #ifndef BUNDLED_CATCH2 #ifdef CATCH3 #include "catch2/catch_all.hpp" #else #include "catch2/catch.hpp" #endif #else #include "../third-party/catch.hpp" #endif #include template const unsigned char* u8p(X); // intentionally undefined template <> const unsigned char* u8p(const char *str) { return (const unsigned char*)str; } struct termpaint_hash_test : public termpaint_hash_item { int data; }; TEST_CASE("hash: Add strings") { termpaint_hash* hash = static_cast(calloc(1, sizeof(termpaint_hash))); hash->item_size = sizeof(termpaint_hash_test); for (int i = 0; i < 128; i++) { std::string str = "test"; str += std::to_string(i + 1); void *item = termpaintp_hash_ensure(hash, u8p(str.data())); REQUIRE(hash->count == i + 1); CHECK(item == termpaintp_hash_get(hash, u8p(str.data()))); } termpaintp_hash_destroy(hash); free(hash); } TEST_CASE("hash: Add and retrieve strings") { termpaint_hash* hash = static_cast(calloc(1, sizeof(termpaint_hash))); hash->item_size = sizeof(termpaint_hash_test); static_cast(termpaintp_hash_ensure(hash, u8p("test1")))->data = 12; static_cast(termpaintp_hash_ensure(hash, u8p("test2")))->data = 42; static_cast(termpaintp_hash_ensure(hash, u8p("test3")))->data = 128; REQUIRE(static_cast(termpaintp_hash_ensure(hash, u8p("test2")))->data == 42); // grow and rehash for (int i = 0; i < 128; i++) { std::string str = "test"; str += std::to_string(i + 1); termpaintp_hash_ensure(hash, u8p(str.data())); } REQUIRE(static_cast(termpaintp_hash_ensure(hash, u8p("test2")))->data == 42); termpaintp_hash_destroy(hash); free(hash); } TEST_CASE("hash: GC") { termpaint_hash* hash = static_cast(calloc(1, sizeof(termpaint_hash))); hash->item_size = sizeof(termpaint_hash_test); hash->gc_mark_cb = [] (termpaint_hash* h) { static_cast(termpaintp_hash_ensure(h, u8p("test1")))->unused = false; static_cast(termpaintp_hash_ensure(h, u8p("test2")))->unused = false; static_cast(termpaintp_hash_ensure(h, u8p("test3")))->unused = false; }; static_cast(termpaintp_hash_ensure(hash, u8p("test1")))->data = 1; static_cast(termpaintp_hash_ensure(hash, u8p("test2")))->data = 2; static_cast(termpaintp_hash_ensure(hash, u8p("test3")))->data = 3; REQUIRE(static_cast(termpaintp_hash_ensure(hash, u8p("test2")))->data == 2); // add strings that are garbage collected instead of growing. for (int i = 0; i < 128; i++) { std::string str = "test"; str += std::to_string(i + 1); termpaintp_hash_ensure(hash, u8p(str.data())); } CHECK(hash->allocated >= 3); CHECK(hash->allocated <= 32); REQUIRE(static_cast(termpaintp_hash_ensure(hash, u8p("test1")))->data == 1); REQUIRE(static_cast(termpaintp_hash_ensure(hash, u8p("test2")))->data == 2); REQUIRE(static_cast(termpaintp_hash_ensure(hash, u8p("test3")))->data == 3); termpaintp_hash_destroy(hash); free(hash); } termpaint-0.3.1/tests/input_tests.cpp000066400000000000000000001770721477303547200177770ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include #include #include #ifndef BUNDLED_CATCH2 #ifdef CATCH3 #include "catch2/catch_all.hpp" #else #include "catch2/catch.hpp" #endif #else #include "../third-party/catch.hpp" #endif #ifndef BUNDLED_PICOJSON #include "picojson.h" #else #include "../third-party/picojson.h" #endif #if __GNUC__ #pragma GCC diagnostic ignored "-Wpadded" #endif typedef bool _Bool; using jarray = picojson::value::array; using jobject = picojson::value::object; #include "../termpaint_input.h" #define U8(x) reinterpret_cast(u8##x) template static int toInt(T x); static int toInt(size_t x) { if (x > std::numeric_limits::max()) { throw std::runtime_error("out of range in conversion to int"); } return static_cast(x); } template static unsigned toUInt(T x) { const unsigned result = static_cast(x); if (result != x) { throw std::runtime_error("out of range in conversion to int"); } return x; } template Result wrapper(void* state, Args... args) { using FnType = std::function; FnType& w = *reinterpret_cast(state); return w(args...); } template void wrap(void (*set_cb)(TYPE1 *ctx, Result (*cb)(void *user_data, Args...), void *user_data), TYPE1 *ctx, std::function &fn) { void *state = reinterpret_cast(&fn); set_cb(ctx, &wrapper, state); } bool parses_as_one(const char *input) { enum { START, OK, ERROR } state = START; std::function<_Bool(const char*, unsigned, _Bool overflow)> callback = [input, &state] (const char *data, unsigned length, _Bool overflow) -> _Bool { (void)overflow; if (state == START) { if (length == strlen(input) && memcmp(input, data, length) == 0) { state = OK; } else { state = ERROR; } } else if (state == OK) { state = ERROR; } return true; }; termpaint_input *input_ctx = termpaint_input_new(); wrap(termpaint_input_set_raw_filter_cb, input_ctx, callback); termpaint_input_add_data(input_ctx, input, strlen(input)); termpaint_input_free(input_ctx); return state == OK; } #define SS3_7 "\033O" #define SS3_8 "\x8f" #define CSI7 "\033[" #define CSI8 "\x9b" #define DCS7 "\033P" #define DCS8 "\x90" #define OSC7 "\033]" #define OSC8 "\x9d" #define ST7 "\033\\" #define ST8 "\x9c" TEST_CASE( "Input is correctly seperated", "[sep]" ) { REQUIRE(parses_as_one("A")); REQUIRE(parses_as_one("a")); REQUIRE(parses_as_one(CSI7 "1;3A")); // xterm: alt-arrow_up REQUIRE(parses_as_one(CSI8 "1;3A")); // xterm: alt-arrow_up with 8bit CSI REQUIRE(parses_as_one(DCS7 "1$r0m" ST7)); // possible reply to "\033P$qm\033\\"; REQUIRE(parses_as_one(DCS8 "1$r0m" ST8)); REQUIRE(!parses_as_one(DCS8 "1$r0m\007")); REQUIRE(parses_as_one(OSC7 "lsome title" ST7)); // possible reply to "\[21t" REQUIRE(parses_as_one(OSC7 "lsome title\007")); REQUIRE(parses_as_one(SS3_7 "P")); // F1 REQUIRE(parses_as_one(SS3_8 "P")); // F1 } TEST_CASE("input: raw filter") { struct TestCase { const std::string sequence; int events; }; const auto testCase = GENERATE( TestCase{ "\033[1;1$y", 1 }, TestCase{ "\033[1;1R", 1 }, TestCase{ "\033\033a", 2 }, TestCase{ "\033\033[1;1R", 2 } ); bool do_filter_out = GENERATE( false, true ); INFO((testCase.sequence[1] == '\033' ? "ESC ESC" + testCase.sequence.substr(2) : "ESC" + testCase.sequence.substr(1))); CAPTURE(do_filter_out); std::string string_in_filter; std::function<_Bool(const char*, unsigned, _Bool overflow)> callback = [&] (const char *data, unsigned length, _Bool overflow) -> _Bool { string_in_filter += std::string(data, length); REQUIRE(!overflow); return do_filter_out; }; int no_events = 0; std::function event_callback = [&] (termpaint_event* event) -> void { (void)event; if (do_filter_out) { FAIL("should have been filtered out"); } else { ++no_events; } }; termpaint_input *input_ctx = termpaint_input_new(); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); wrap(termpaint_input_set_raw_filter_cb, input_ctx, callback); termpaint_input_add_data(input_ctx, testCase.sequence.data(), testCase.sequence.size()); if (!do_filter_out) { REQUIRE(no_events == testCase.events); } REQUIRE(string_in_filter == testCase.sequence); termpaint_input_free(input_ctx); } TEST_CASE( "evil utf8" ) { std::vector events; unsigned num_events = 0; std::function event_callback = [&] (termpaint_event* event) -> void { if (num_events < events.size()) { INFO("event index " << num_events); auto& expected = events[num_events]; REQUIRE(expected.type == event->type); REQUIRE(expected.c.length == event->c.length); REQUIRE(memcmp(expected.c.string, event->c.string, expected.c.length) == 0); } else { FAIL("more events than expected"); } ++num_events; }; termpaint_input *input_ctx = termpaint_input_new(); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); std::string input; SECTION("X") { input = "\x41\xc2\x3e"; events.resize(3); events[0].type = TERMPAINT_EV_CHAR; events[0].c.length = 1; events[0].c.string = "\x41"; events[1].type = TERMPAINT_EV_INVALID_UTF8; events[1].c.length = 1; events[1].c.string = "\xc2"; events[2].type = TERMPAINT_EV_CHAR; events[2].c.length = 1; events[2].c.string = "\x3e"; termpaint_input_add_data(input_ctx, input.data(), input.size()); REQUIRE(num_events == events.size()); } termpaint_input_free(input_ctx); } // TODO more malformed utf8 tests? TEST_CASE("input: malformed utf8") { struct TestCase { const std::string sequence; const std::string desc; }; const auto testCase = GENERATE( TestCase{ "\xff", "0xff" }, TestCase{ "\xfe", "0xfe" }, //TestCase{ "\x80", "lone continuation byte" }, for now this is tested for in malformed sequences TestCase{ "\xc0\xa0", "overlong (2) encoding of 32" }, TestCase{ "\xc1\xa0", "overlong (2) encoding of 96" }, TestCase{ "\xe0\x80\xa0", "overlong (3) encoding of 32" }, TestCase{ "\xf0\x80\x80\xa0", "overlong (4) encoding of 32" }, TestCase{ "\033\xff", "ESC + 0xff" }, TestCase{ "\033\xfe", "ESC + 0xfe" }, TestCase{ "\033\xc0\xa0", "ESC + overlong (2) encoding of 32" }, TestCase{ "\033\xc1\xa0", "ESC + overlong (2) encoding of 96" }, TestCase{ "\033\xe0\x80\xa0", "ESC + overlong (3) encoding of 32" }, TestCase{ "\033\xf0\x80\x80\xa0", "ESC + overlong (4) encoding of 32" }, TestCase{ "\xf8\xbf\xbf\xbf\xbf", "5 byte utf-8 like encoding" }, TestCase{ "\xfc\xbf\xbf\xbf\xbf\xbf", "6 byte utf-8 like encoding" }, TestCase{ "\033\xf8\xbf\xbf\xbf\xbf", "ESC + 5 byte utf-8 like encoding" }, TestCase{ "\033\xfc\xbf\xbf\xbf\xbf\xbf", "ESC + 6 byte utf-8 like encoding" }, TestCase{ "\xfcz", "6 byte start byte followed by non continuation" }, TestCase{ "\xf8z", "5 byte start byte followed by non continuation" }, TestCase{ "\xf0z", "4 byte start byte followed by non continuation" }, TestCase{ "\xe0z", "3 byte start byte followed by non continuation" }, TestCase{ "\xc2z", "2 byte start byte followed by non continuation" } ); INFO(testCase.desc); enum { START, GOT_EVENT } state = START; std::function event_callback = [&] (termpaint_event* event) -> void { if (state == GOT_EVENT) { if (testCase.sequence[testCase.sequence.size()-1] != 'z') { FAIL("more events than expected"); } } else if (state == START) { REQUIRE(event->type == TERMPAINT_EV_INVALID_UTF8); state = GOT_EVENT; } else { FAIL("unexpected state " << state); } }; termpaint_input *input_ctx = termpaint_input_new(); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); termpaint_input_add_data(input_ctx, testCase.sequence.data(), testCase.sequence.size()); REQUIRE(state == GOT_EVENT); termpaint_input_free(input_ctx); } TEST_CASE( "Overflow is handled correctly", "[overflow]" ) { enum { START, GOT_RAW, GOT_EVENT, ERROR } state = START; std::function<_Bool(const char*, unsigned, _Bool overflow)> raw_callback = [&state] (const char *data, unsigned length, _Bool overflow) -> _Bool { (void)data; (void)length; if (state == START) { if (overflow) { state = GOT_RAW; } else { state = ERROR; } } else { state = ERROR; } return false; }; termpaint_event captured_event; std::function event_callback = [&state, &captured_event] (termpaint_event* event) -> void { if (state == GOT_RAW) { captured_event = *event; state = GOT_EVENT; } else { state = ERROR; } }; termpaint_input *input_ctx = termpaint_input_new(); wrap(termpaint_input_set_raw_filter_cb, input_ctx, raw_callback); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); std::string input; // Workaround for Catch #734 auto runTest = [&] { termpaint_input_add_data(input_ctx, input.data(), input.size()); REQUIRE(state == GOT_EVENT); REQUIRE(captured_event.type == TERMPAINT_EV_OVERFLOW); /*REQUIRE(captured_event.length == 0); REQUIRE(captured_event.atom_or_string == nullptr); REQUIRE(captured_event.modifier == 0);*/ }; SECTION( "CSI7" ) { input = CSI7 + std::string(2000, '1') + "A"; runTest(); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); } SECTION( "CSI8" ) { input = CSI8 + std::string(2000, '1') + "A"; runTest(); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); } SECTION( "SS3_7" ) { input = SS3_7 + std::string(2000, '1') + "A"; runTest(); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); } SECTION( "SS3_8" ) { input = SS3_8 + std::string(2000, '1') + "A"; runTest(); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); } SECTION( "DCS7" ) { input = DCS7 + std::string(2000, '1') + "A" ST7; runTest(); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); } SECTION( "DCS8" ) { input = DCS8 + std::string(2000, '1') + "A" ST8; runTest(); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); } SECTION( "OSC7" ) { input = OSC7 + std::string(2000, '1') + "A" ST7; runTest(); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); } SECTION( "OSC8" ) { input = OSC8 + std::string(2000, '1') + "A" ST8; runTest(); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); } SECTION( "CSI retrigger" ) { input = std::string("\033[") + std::string(1022, '.') + "\033["; runTest(); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 2); } SECTION( "OSC retrigger" ) { input = std::string("\033]") + std::string(1022, '.') + "\033["; runTest(); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 2); } termpaint_input_free(input_ctx); } TEST_CASE("input: double esc handling") { struct TestCase { const std::string sequence; int type; const std::string desc; }; const auto testCase = GENERATE( TestCase{ "\033\033[ @", TERMPAINT_EV_UNKNOWN, "short unrecognised sequence" }, TestCase{ "\033\033[ @", TERMPAINT_EV_UNKNOWN, "long unrecognised sequence" }, TestCase{ "\033\033[10;21R", TERMPAINT_EV_CURSOR_POSITION, "cursor position report" } ); enum { START, GOT_ESC, GOT_EVENT } state = START; std::function event_callback = [&] (termpaint_event* event) -> void { if (state == GOT_EVENT) { FAIL("more events than expected"); } else if (state == START) { CHECK(event->type == TERMPAINT_EV_KEY); CHECK(event->key.modifier == 0); CHECK(event->key.atom == termpaint_input_escape()); CHECK(event->key.length == strlen(termpaint_input_escape())); state = GOT_ESC; } else if (state == GOT_ESC) { REQUIRE(event->type == testCase.type); state = GOT_EVENT; } else { FAIL("unexpected state " << state); } }; termpaint_input *input_ctx = termpaint_input_new(); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); termpaint_input_add_data(input_ctx, testCase.sequence.data(), testCase.sequence.size()); REQUIRE(state == GOT_EVENT); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); termpaint_input_free(input_ctx); } TEST_CASE("input: unmapped/rejected sequences") { struct TestCase { const std::string sequence; const std::string desc; int tweak = 0; }; const auto testCase = GENERATE( TestCase{ "\033[0[", "CSI with parameter and [ as final" }, TestCase{ "\033[100R", "cursor position report, missing second parameter" }, TestCase{ "\033[1R", "cursor position report, to short by missing second parameter" }, TestCase{ "\033[1:0R", "cursor position report, subparameter 1" }, TestCase{ "\033[1;1:0R", "cursor position report, subparameter 2" }, TestCase{ "\033[1;0R", "cursor position report, zero 1st parameter" }, TestCase{ "\033[0;10R", "cursor position report, zero 2nd parameter" }, TestCase{ "\033[1;2147483648R", "cursor position report, out of int range 1" }, TestCase{ "\033[1;2147483650R", "cursor position report, out of int range 2" }, TestCase{ "\033[1\x01;1R", "cursor position report, embedded C0" }, TestCase{ "\033[1\x80;1R", "cursor position report, embedded 0x80" }, TestCase{ "\033[100$y", "mode report, missing second parameter" }, TestCase{ "\033[1$y", "mode report, to short by missing second parameter" }, TestCase{ "\033[1:0$y", "mode report, subparameter 1" }, TestCase{ "\033[1;1:0$y", "mode report, subparameter 2" }, TestCase{ "\033[10000M", "mouse report, missing second and third parameter" }, TestCase{ "\033[10;10M", "mouse report, missing third parameter" }, TestCase{ "\033[1M", "mouse report, to short by missing second and third parameter" }, TestCase{ "\033[10:00M", "mouse report, subparameter 1" }, TestCase{ "\033[1;1:0M", "mouse report, subparameter 2" }, TestCase{ "\033[1;1;1:0M", "mouse report, subparameter 3" }, TestCase{ "\033[1;1;1;0M", "mouse report, excess parameters" }, TestCase{ "\033[0;33;33M", "mouse report, zero 1st parameter" }, TestCase{ "\033[33;0;33M", "mouse report, zero 2nd parameter" }, TestCase{ "\033[33;33;0M", "mouse report, zero 3rd parameter" }, TestCase{ "\033[1;2147483648;1M", "mouse report, excess value" }, TestCase{ "\033[<10000M", "mouse report, missing second and third parameter" }, TestCase{ "\033[<10;10M", "mouse report, missing third parameter" }, TestCase{ "\033[<1M", "mouse report, to short by missing second and third parameter" }, TestCase{ "\033[<10:00M", "mouse report, subparameter 1" }, TestCase{ "\033[<1;1:0M", "mouse report, subparameter 2" }, TestCase{ "\033[<1;1;1:0M", "mouse report, subparameter 3" }, TestCase{ "\033[<1;1;1;0M", "mouse report, excess parameters" }, TestCase{ "\033[<1;0;1M", "mouse report, zero 2nd parameter" }, TestCase{ "\033[<1;1;0M", "mouse report, zero 3rd parameter" }, TestCase{ "\033[<1;2147483648;1M", "mouse report, excess value" }, TestCase{ {"\033[M \000!", 6},"legacy mouse report, overflow x parameter" }, TestCase{ {"\033[M !\000", 6},"legacy mouse report, overflow y parameter" }, TestCase{ {"\033[M\000!!", 6},"legacy mouse report, zero button parameter" }, TestCase{ {"\033[M \001!", 6},"legacy mouse report, uncontrolled (1) overflow x parameter" }, TestCase{ {"\033[M !\001", 6},"legacy mouse report, uncontrolled (1) overflow y parameter" }, TestCase{ {"\033[M \037!", 6},"legacy mouse report, uncontrolled (31) overflow x parameter" }, TestCase{ {"\033[M !\037", 6},"legacy mouse report, uncontrolled (31) overflow y parameter" }, TestCase{ "\033[M\xc0\xc0!!","legacy multi-byte mouse report, invalid btn multi byte encoding", 1 }, TestCase{ "\033[M \xc0\xc0!","legacy multi-byte mouse report, invalid x multi byte encoding", 1 }, TestCase{ "\033[M !\xc0\xc0","legacy multi-byte mouse report, invalid y multi byte encoding", 1 }, TestCase{ "\033[M!!\xe0\xc0","legacy multi-byte mouse report, invalid y multi byte encoding 2", 1 }, TestCase{ "\033[M!\xe0\xc0!","legacy multi-byte mouse report, invalid x multi byte encoding 2", 1 }, TestCase{ "\033[M\xf0\xc0!!","legacy multi-byte mouse report, invalid btn multi byte encoding 2", 1 }, TestCase{ "\033[M\xf8\x80\x80\x80\x80\xc0!!","legacy multi-byte mouse report, overlong btn multi byte encoding 5 bytes", 1 }, TestCase{ "\033[M\x80\xc4\xa8!","legacy mouse multi-byte report, invalid btn multi byte only continuations", 1 }, TestCase{ "\033[M \xc4\xa8\x01","legacy mouse multi-byte report, uncontrolled (1) overflow y parameter", 1 }, TestCase{ "\033[M \x01\xc4\xa8","legacy mouse multi-byte report, uncontrolled (1) overflow x parameter", 1 }, TestCase{ "\033[M\x01!\xc4\xa8","legacy mouse multi-byte report, button parameter out of range", 1 }, TestCase{ "\x9b M", "8 bit CSI with space and M" }, TestCase{ "\033[27;10;1@", "CSI @ with start like modify other" }, TestCase{ "\033[27;10;~", "modify other without character number" }, TestCase{ "\033[27; 00~", "modify other with space before the parameter" }, TestCase{ "\033[27;?00~", "modify other with question mark before the parameter" }, TestCase{ "\033[27;1; ~", "modify other with space after the parameter" }, TestCase{ "\033[27;0;1~", "modify other with mod == 0" }, TestCase{ "\033[27;2147483648;1~", "modify other with out of range parameter" }, TestCase{ "\033]\033\\", "empty OSC sequence" }, TestCase{ "\033]\007", "empty OSC sequence (BEL)" }, TestCase{ "\033]XX\033\\", "non numeric OSC sequence (X)" }, TestCase{ "\033]XX\007", "non numeric OSC sequence (X,BEL)" }, TestCase{ "\033] \033\\", "non numeric OSC sequence (space)" }, TestCase{ "\033] \007", "non numeric OSC sequence (space,BEL)" }, TestCase{ "\033]00\033\\", "numeric OSC sequence without additional payload" }, TestCase{ "\033]00\007", "numeric OSC sequence without additional payload (BEL)" }, TestCase{ "\033]0X\033\\", "OSC sequence with inital digit but non digit following (X)" }, TestCase{ "\033]0X\007", "OSC sequence with inital digit but non digit following (X,BEL)" }, TestCase{ "\033]0*\033\\", "OSC sequence with inital digit but non digit following (*)" }, TestCase{ "\033]0*\007", "OSC sequence with inital digit but non digit following (*,BEL)" }, TestCase{ "\x80", "0x80" }, TestCase{ "\033\x80", "ESC + 0x80" }, TestCase{ "\033[?\x1f@", "CSI with embedded control character" }, TestCase{ "\033]abc\\\033x", "OSC with backslash and mistermination" }, TestCase{ "\033]0;\033\\", "OSC sequence with unassigned number 0" }, TestCase{ "\033]0;\007", "OSC sequence with unassigned number 0 (BEL)" }, TestCase{ "\033]700;\033\\", "OSC sequence with unassigned number" }, TestCase{ "\033]700;\007", "OSC sequence with unassigned number (BEL)" }, TestCase{ "\033]999999;\033\\", "OSC sequence with huge number (no overflowing)" }, TestCase{ "\033]999999;\007", "OSC sequence with huge number (no overflowing,BEL)" }, TestCase{ "\033]2147483648\033\\", "OSC sequence with huge number (outside int range1)" }, TestCase{ "\033]2147483648\007", "OSC sequence with huge number (outside int range1,BEL)" }, TestCase{ "\033]2147483650\033\\", "OSC sequence with huge number (outside int range2)" }, TestCase{ "\033]2147483650\007", "OSC sequence with huge number (outside int range2,BEL)" }, TestCase{ "\033]4;2147483648;rgb:0000/0000/0000\033\\", "OSC 4 sequence with huge number (outside int range)" }, TestCase{ "\033]4;2147483648;rgb:0000/0000/0000\007", "OSC 4 sequence with huge number (outside int range,BEL)" }, TestCase{ "\033]700;\033[ @", "unterminated OSC sequence", 2 }, TestCase{ "\033P700\033\\", "DCS with 700"}, TestCase{ "\033P700\007", "DCS with 700 (BEL)"}, TestCase{ "\033P!700\033\\", "DCS with !700" }, TestCase{ "\033P!700\007", "DCS with !700 (BEL)" }, TestCase{ "\033Pabc\\\033x", "DSC with backslash and mistermination" }, TestCase{ "\033O;Z", "SS3 with empty parameters" } ); INFO(testCase.desc); enum { START, GOT_EVENT, GOT_EVENT2 } state = START; std::function event_callback = [&] (termpaint_event* event) -> void { if (state == GOT_EVENT) { if (testCase.tweak & 2) { state = GOT_EVENT2; } else { FAIL("more events than expected"); } } else if (state == START) { REQUIRE(event->type == TERMPAINT_EV_UNKNOWN); state = GOT_EVENT; } else { FAIL("unexpected state " << state); } }; termpaint_input *input_ctx = termpaint_input_new(); termpaint_input_expect_legacy_mouse_reports(input_ctx, (testCase.tweak & 1) ? TERMPAINT_INPUT_EXPECT_LEGACY_MOUSE_MODE_1005 : TERMPAINT_INPUT_EXPECT_LEGACY_MOUSE); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); termpaint_input_add_data(input_ctx, testCase.sequence.data(), testCase.sequence.size()); if (testCase.tweak & 2) { REQUIRE(state == GOT_EVENT2); } else { REQUIRE(state == GOT_EVENT); } REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); termpaint_input_free(input_ctx); } TEST_CASE("input: nullptr as handler") { struct TestCase { const std::string sequence; const std::string desc; }; const auto testCase = GENERATE( TestCase{ "\033[1;1R", "1" }, TestCase{ "\033\033\033[6n", "2" }, TestCase{ "\033\033[1;1R", "3" } ); INFO(testCase.desc); // This mostly tests that this is not crashing. termpaint_input *input_ctx = termpaint_input_new(); termpaint_input_add_data(input_ctx, testCase.sequence.data(), testCase.sequence.size()); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); termpaint_input_free(input_ctx); } static int hexToInt(char input) { if ('0' <= input && input <= '9') { return input - '0'; } else if ('a' <= input && input <= 'f') { return input - 'a' + 10; } else if ('A' <= input && input <= 'F') { return input - 'A' + 10; } else { FAIL("fixture file is broken"); } return -1; } TEST_CASE( "Recorded sequences parsed as usual", "[pin-recorded]" ) { std::string path = "../tests/"; if (getenv("TERMPAINT_TEST_DATA")) { path = getenv("TERMPAINT_TEST_DATA"); } std::ifstream istrm(path + "/input_tests.json", std::ios::binary); picojson::value rootval; istrm >> rootval; if (istrm.fail()) { FAIL("Error while reading input_tests.json:" << picojson::get_last_error()); } jarray cases = rootval.get(); for (auto caseval: cases) { jobject caseobj = caseval.get(); std::string rawInputHex = caseobj["raw"].get(); std::string sectionName = caseobj["keyId"].get() + "-" + rawInputHex; CAPTURE(sectionName); CAPTURE(rawInputHex); std::string rawInput; for (size_t i=0; i < rawInputHex.size(); i+=2) { unsigned char ch; ch = (hexToInt(rawInputHex[i]) << 4) + hexToInt(rawInputHex[i+1]); rawInput.push_back(static_cast(ch)); } std::string expectedType = caseobj["type"].get(); std::string expectedValue; if (expectedType == "key") { expectedValue = caseobj["key"].get(); } else if (expectedType == "char") { expectedValue = caseobj["chars"].get(); } else { FAIL("Type in fixture not right: " + expectedType); } std::string expectedModStr = caseobj["mod"].get(); CAPTURE(expectedType); CAPTURE(expectedModStr); CAPTURE(expectedValue); enum { START, GOT_EVENT, GOT_SYNC } state = START; bool expectSync = false; std::function event_callback = [&] (termpaint_event* event) -> void { if (state == GOT_EVENT && !expectSync) { FAIL("more events than expected"); } else if (state == START) { std::string actualType; std::string actualValue; int actualModifier = 0; if (event->type == TERMPAINT_EV_CHAR) { actualType = "char"; actualValue = std::string(event->c.string, event->c.length); actualModifier = event->c.modifier; } else if (event->type == TERMPAINT_EV_KEY) { actualType = "key"; actualValue = std::string(event->key.atom); actualModifier = event->key.modifier; } else { actualType = "???"; } int expectedMod = 0; if (expectedModStr.find('S') != std::string::npos) { expectedMod |= TERMPAINT_MOD_SHIFT; } if (expectedModStr.find('A') != std::string::npos) { expectedMod |= TERMPAINT_MOD_ALT; } if (expectedModStr.find('C') != std::string::npos) { expectedMod |= TERMPAINT_MOD_CTRL; } CAPTURE(actualValue); REQUIRE(expectedType == actualType); if (event->type == TERMPAINT_EV_CHAR) { REQUIRE(expectedValue.size() == event->c.length); } REQUIRE(expectedValue == actualValue); REQUIRE(expectedMod == actualModifier); state = GOT_EVENT; } else if (state == GOT_EVENT) { bool wasSync = event->type == TERMPAINT_EV_MISC && event->misc.atom == termpaint_input_i_resync(); REQUIRE(wasSync); state = GOT_SYNC; } else { FAIL("unexpected state " << state); } }; if (rawInputHex == "1b" // ESC: the traditional hard case || rawInputHex == "1b1b" // alt ESC || rawInputHex == "1b50" || rawInputHex == "1b4f" // various urxvt sequences end in '$', which is not a final character for CSI || rawInputHex == "1b5b323324" // urxvt F11.S || rawInputHex == "1b1b5b323324" // urxvt F11.SA || rawInputHex == "1b5b323424" // urxvt F12.S || rawInputHex == "1b1b5b323424" // urxvt F12.SA || rawInputHex == "1b5b3224" // urxvt Insert.S || rawInputHex == "1b1b5b3224" // urxvt Insert.SA || rawInputHex == "1b5b3324" // urxvt Delete.S || rawInputHex == "1b1b5b3324" // urxvt Delete.SA || rawInputHex == "1b5b3524" // urxvt PageUp.S || rawInputHex == "1b1b5b3524" // urxvt PageUp.SA || rawInputHex == "1b5b3624" // urxvt PageDown.S || rawInputHex == "1b1b5b3624" // urxvt PageDown.SA || rawInputHex == "1b5b3724" // urxvt Home.S || rawInputHex == "1b1b5b3724" // urxvt Home.SA || rawInputHex == "1b5b3824" // urxvt End.S || rawInputHex == "1b1b5b3824" // urxvt End.SA ) { expectSync = true; } termpaint_input *input_ctx = termpaint_input_new(); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); std::string input; termpaint_input_add_data(input_ctx, rawInput.data(), rawInput.size()); if (!expectSync) { REQUIRE(state == GOT_EVENT); } else { REQUIRE(state == START); termpaint_input_add_data(input_ctx, "\033[0n", 4); REQUIRE(state == GOT_SYNC); } REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); termpaint_input_free(input_ctx); } } TEST_CASE("input: quirk: backspace 0x08/0x7f swapped") { struct TestCase { std::string rawInput; int mod; }; const auto testCase = GENERATE( TestCase{"\x08", 0}, TestCase{"\x7f", TERMPAINT_MOD_CTRL} ); std::string rawInput = testCase.rawInput; enum { START, GOT_EVENT } state = START; std::function event_callback = [&] (termpaint_event* event) -> void { if (state == GOT_EVENT) { FAIL("more events than expected"); } else if (state == START) { REQUIRE(event->type == TERMPAINT_EV_KEY); CHECK(event->key.modifier == testCase.mod); CHECK(std::string(event->key.atom) == termpaint_input_backspace()); state = GOT_EVENT; } else { FAIL("unexpected state " << state); } }; termpaint_input *input_ctx = termpaint_input_new(); termpaint_input_activate_quirk(input_ctx, TERMPAINT_INPUT_QUIRK_BACKSPACE_X08_AND_X7F_SWAPPED); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); std::string input; termpaint_input_add_data(input_ctx, rawInput.data(), rawInput.size()); REQUIRE(state == GOT_EVENT); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); termpaint_input_free(input_ctx); } TEST_CASE("input: quirk: c1 for shift-ctrl") { struct TestCase { std::string rawInput; std::string ch; }; const auto testCase = GENERATE( TestCase{U8("\u0080"), " "}, TestCase{U8("\u0081"), "A"}, TestCase{U8("\u0082"), "B"}, TestCase{U8("\u0083"), "C"}, TestCase{U8("\u0084"), "D"}, TestCase{U8("\u0085"), "E"}, TestCase{U8("\u0086"), "F"}, TestCase{U8("\u0087"), "G"}, TestCase{U8("\u0088"), "H"}, TestCase{U8("\u0089"), "I"}, TestCase{U8("\u008a"), "J"}, TestCase{U8("\u008b"), "K"}, TestCase{U8("\u008c"), "L"}, TestCase{U8("\u008d"), "M"}, TestCase{U8("\u008e"), "N"}, TestCase{U8("\u008f"), "O"}, TestCase{U8("\u0090"), "P"}, TestCase{U8("\u0091"), "Q"}, TestCase{U8("\u0092"), "R"}, TestCase{U8("\u0093"), "S"}, TestCase{U8("\u0094"), "T"}, TestCase{U8("\u0095"), "U"}, TestCase{U8("\u0096"), "V"}, TestCase{U8("\u0097"), "W"}, TestCase{U8("\u0098"), "X"}, TestCase{U8("\u0099"), "Y"}, TestCase{U8("\u009a"), "Z"} ); std::string rawInput = testCase.rawInput; CAPTURE(testCase.ch); enum { START, GOT_EVENT } state = START; std::function event_callback = [&] (termpaint_event* event) -> void { if (state == GOT_EVENT) { FAIL("more events than expected"); } else if (state == START) { if (testCase.ch == " ") { REQUIRE(event->type == TERMPAINT_EV_KEY); CHECK(event->key.modifier == (TERMPAINT_MOD_CTRL | TERMPAINT_MOD_SHIFT)); CHECK(std::string(event->key.atom) == termpaint_input_space()); } else { REQUIRE(event->type == TERMPAINT_EV_CHAR); CHECK(event->c.modifier == (TERMPAINT_MOD_CTRL | TERMPAINT_MOD_SHIFT)); CHECK(std::string(event->key.atom) == testCase.ch); } state = GOT_EVENT; } else { FAIL("unexpected state " << state); } }; termpaint_input *input_ctx = termpaint_input_new(); termpaint_input_activate_quirk(input_ctx, TERMPAINT_INPUT_QUIRK_C1_FOR_CTRL_SHIFT); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); std::string input; termpaint_input_add_data(input_ctx, rawInput.data(), rawInput.size()); REQUIRE(state == GOT_EVENT); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); termpaint_input_free(input_ctx); } TEST_CASE("input: events using raw") { struct TestCase { const std::string sequence; int type; const std::string contents; }; const auto testCase = GENERATE( TestCase{ "\033P!|0\033\\", TERMPAINT_EV_RAW_3RD_DEV_ATTRIB, "0" }, TestCase{ "\033P!|00000000\033\\", TERMPAINT_EV_RAW_3RD_DEV_ATTRIB, "00000000" }, TestCase{ "\033P!|7E565445\033\\", TERMPAINT_EV_RAW_3RD_DEV_ATTRIB, "7E565445" }, TestCase{ "\033[3;1;1;112;112;1;0x", TERMPAINT_EV_RAW_DECREQTPARM, "\033[3;1;1;112;112;1;0x" }, TestCase{ "\033[3;1;1;120;120;1;0x", TERMPAINT_EV_RAW_DECREQTPARM, "\033[3;1;1;120;120;1;0x" }, TestCase{ "\033[3;1;1;128;128;1;0x", TERMPAINT_EV_RAW_DECREQTPARM, "\033[3;1;1;128;128;1;0x" }, TestCase{ "\033[?x", TERMPAINT_EV_RAW_DECREQTPARM, "\033[?x" }, TestCase{ "\033[>0;115;0c", TERMPAINT_EV_RAW_SEC_DEV_ATTRIB, "\033[>0;115;0c" }, TestCase{ "\033[>0;95;0c", TERMPAINT_EV_RAW_SEC_DEV_ATTRIB, "\033[>0;95;0c" }, TestCase{ "\033[>1;3000;0c", TERMPAINT_EV_RAW_SEC_DEV_ATTRIB, "\033[>1;3000;0c" }, TestCase{ "\033[>41;280;0c", TERMPAINT_EV_RAW_SEC_DEV_ATTRIB, "\033[>41;280;0c" }, TestCase{ "\033[?6c", TERMPAINT_EV_RAW_PRI_DEV_ATTRIB, "\033[?6c" }, TestCase{ "\033P>|fancyterm 1.23\033\\", TERMPAINT_EV_RAW_TERM_NAME, "fancyterm 1.23"}, TestCase{ "\033P1+r544e=787465726d2d6b69747479\033\\", TERMPAINT_EV_RAW_TERMINFO_QUERY_REPLY, "1+r544e=787465726d2d6b69747479"} ); enum { START, GOT_EVENT } state = START; std::function event_callback = [&] (termpaint_event* event) -> void { if (state == GOT_EVENT) { FAIL("more events than expected"); } else if (state == START) { REQUIRE(event->type == testCase.type); REQUIRE(std::string(event->raw.string, event->raw.length) == testCase.contents); state = GOT_EVENT; } else { FAIL("unexpected state " << state); } }; termpaint_input *input_ctx = termpaint_input_new(); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); termpaint_input_add_data(input_ctx, testCase.sequence.data(), testCase.sequence.size()); REQUIRE(state == GOT_EVENT); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); termpaint_input_free(input_ctx); } TEST_CASE("input: misc events") { struct TestCase { const std::string sequence; const std::string event; }; const auto testCase = GENERATE( TestCase{ "\033[I", "FocusIn" }, TestCase{ "\033[O", "FocusOut" } ); enum { START, GOT_EVENT } state = START; std::function event_callback = [&] (termpaint_event* event) -> void { if (state == GOT_EVENT) { FAIL("more events than expected"); } else if (state == START) { REQUIRE(event->type == TERMPAINT_EV_MISC); REQUIRE(std::string(event->misc.atom, event->misc.length) == testCase.event); state = GOT_EVENT; } else { FAIL("unexpected state " << state); } }; termpaint_input *input_ctx = termpaint_input_new(); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); termpaint_input_add_data(input_ctx, testCase.sequence.data(), testCase.sequence.size()); REQUIRE(state == GOT_EVENT); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); termpaint_input_free(input_ctx); } TEST_CASE("input: cursor position report") { struct TestCase { const std::string sequence; bool expect; bool safe; int x; int y; }; const auto testCase = GENERATE( TestCase{ "\033[1;1R", false, false, 0, 0 }, TestCase{ "\033[1;2R", true, false, 1, 0 }, TestCase{ "\033[1;3R", true, false, 2, 0 }, TestCase{ "\033[1;4R", true, false, 3, 0 }, TestCase{ "\033[1;5R", true, false, 4, 0 }, TestCase{ "\033[1;6R", true, false, 5, 0 }, TestCase{ "\033[1;7R", true, false, 6, 0 }, TestCase{ "\033[1;8R", true, false, 7, 0 }, TestCase{ "\033[1;9R", false, false, 8, 0 }, TestCase{ "\033[4;10R", false, false, 9, 3 }, TestCase{ "\033[?1;1R", false, true, 0, 0 }, TestCase{ "\033[?1;2R", false, true, 1, 0 }, TestCase{ "\033[?1;3R", false, true, 2, 0 }, TestCase{ "\033[?1;4R", false, true, 3, 0 }, TestCase{ "\033[?1;5R", false, true, 4, 0 }, TestCase{ "\033[?1;6R", false, true, 5, 0 }, TestCase{ "\033[?1;7R", false, true, 6, 0 }, TestCase{ "\033[?1;8R", false, true, 7, 0 }, TestCase{ "\033[?1;9R", false, true, 8, 0 }, TestCase{ "\033[?4;10R", false, true, 9, 3 }, TestCase{ "\033[?1;1;1R", false, true, 0, 0 }, TestCase{ "\033[?1;2;1R", false, true, 1, 0 }, TestCase{ "\033[?1;3;1R", false, true, 2, 0 }, TestCase{ "\033[?1;4;1R", false, true, 3, 0 }, TestCase{ "\033[?1;5;1R", false, true, 4, 0 }, TestCase{ "\033[?1;6;1R", false, true, 5, 0 }, TestCase{ "\033[?1;7;1R", false, true, 6, 0 }, TestCase{ "\033[?1;8;1R", false, true, 7, 0 }, TestCase{ "\033[?1;9;1R", false, true, 8, 0 }, TestCase{ "\033[?4;10;1R", false, true, 9, 3 } ); enum { START, GOT_EVENT } state = START; INFO( "ESC" + testCase.sequence.substr(1)); std::function event_callback = [&] (termpaint_event* event) -> void { if (state == GOT_EVENT) { FAIL("more events than expected"); } else if (state == START) { REQUIRE(event->type == TERMPAINT_EV_CURSOR_POSITION); CHECK(event->cursor_position.safe == testCase.safe); CHECK(event->cursor_position.x == testCase.x); CHECK(event->cursor_position.y == testCase.y); state = GOT_EVENT; } else { FAIL("unexpected state " << state); } }; termpaint_input *input_ctx = termpaint_input_new(); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); if (testCase.expect) { termpaint_input_expect_cursor_position_report(input_ctx); } termpaint_input_add_data(input_ctx, testCase.sequence.data(), testCase.sequence.size()); REQUIRE(state == GOT_EVENT); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); termpaint_input_free(input_ctx); } TEST_CASE("input: mouse report") { struct TestCase { const std::string sequence; int x; int y; int raw; int action; int button; int mod; }; const auto testCase = GENERATE( TestCase{ "\033[<0;1;1M", 0, 0, 0, TERMPAINT_MOUSE_PRESS, 0, 0 }, TestCase{ "\033[<0;1;1m", 0, 0, 0, TERMPAINT_MOUSE_RELEASE, 0, 0 }, TestCase{ "\033[32;1;1M", 0, 0, 0, TERMPAINT_MOUSE_PRESS, 0, 0 }, TestCase{ "\033[35;1;1M", 0, 0, 3, TERMPAINT_MOUSE_RELEASE, 3, 0 }, TestCase{ "\033[M !!", 0, 0, 0, TERMPAINT_MOUSE_PRESS, 0, 0 }, TestCase{ "\033[M#!!", 0, 0, 3, TERMPAINT_MOUSE_RELEASE, 3, 0 }, TestCase{ "\033[<0;192;40M", 191, 39, 0, TERMPAINT_MOUSE_PRESS, 0, 0 }, TestCase{ "\033[<0;192;40m", 191, 39, 0, TERMPAINT_MOUSE_RELEASE, 0, 0 }, TestCase{ "\033[32;192;40M", 191, 39, 0, TERMPAINT_MOUSE_PRESS, 0, 0 }, TestCase{ "\033[35;192;40M", 191, 39, 3, TERMPAINT_MOUSE_RELEASE, 3, 0 }, TestCase{ "\033[M \xe0H", 191, 39, 0, TERMPAINT_MOUSE_PRESS, 0, 0 }, TestCase{ "\033[M#\xe0H", 191, 39, 3, TERMPAINT_MOUSE_RELEASE, 3, 0 }, TestCase{ "\033[M àH", 191, 39, 0, TERMPAINT_MOUSE_PRESS, 0, 0 }, TestCase{ "\033[M Hà", 39, 191, 0, TERMPAINT_MOUSE_PRESS, 0, 0 }, TestCase{ "\033[M \xe0\xa0\x80H", 2015, 39, 0, TERMPAINT_MOUSE_PRESS, 0, 0 }, TestCase{ "\033[M H\xe0\xa0\x80", 39, 2015, 0, TERMPAINT_MOUSE_PRESS, 0, 0 }, TestCase{ "\033[M \xef\xbf\xbfH", 65502, 39, 0, TERMPAINT_MOUSE_PRESS, 0, 0 }, TestCase{ "\033[M H\xef\xbf\xbf", 39, 65502, 0, TERMPAINT_MOUSE_PRESS, 0, 0 }, TestCase{ "\033[M \xf0\x90\x80\x80H", 65503, 39, 0, TERMPAINT_MOUSE_PRESS, 0, 0 }, TestCase{ "\033[M H\xf0\x90\x80\x80", 39, 65503, 0, TERMPAINT_MOUSE_PRESS, 0, 0 }, TestCase{ "\033[<8;105;18M", 104, 17, 8, TERMPAINT_MOUSE_PRESS, 0, TERMPAINT_MOD_ALT }, TestCase{ "\033[<8;105;18m", 104, 17, 8, TERMPAINT_MOUSE_RELEASE, 0, TERMPAINT_MOD_ALT }, TestCase{ "\033[40;105;18M", 104, 17, 8, TERMPAINT_MOUSE_PRESS, 0, TERMPAINT_MOD_ALT }, TestCase{ "\033[43;105;18M", 104, 17, 11, TERMPAINT_MOUSE_RELEASE, 3, TERMPAINT_MOD_ALT }, TestCase{ "\033[<24;105;18M", 104, 17, 24, TERMPAINT_MOUSE_PRESS, 0, TERMPAINT_MOD_ALT | TERMPAINT_MOD_CTRL }, TestCase{ "\033[<24;105;18m", 104, 17, 24, TERMPAINT_MOUSE_RELEASE, 0, TERMPAINT_MOD_ALT | TERMPAINT_MOD_CTRL }, TestCase{ "\033[56;105;18M", 104, 17, 24, TERMPAINT_MOUSE_PRESS, 0, TERMPAINT_MOD_ALT | TERMPAINT_MOD_CTRL }, TestCase{ "\033[59;105;18M", 104, 17, 27, TERMPAINT_MOUSE_RELEASE, 3, TERMPAINT_MOD_ALT | TERMPAINT_MOD_CTRL }, TestCase{ "\033[<4;105;18M", 104, 17, 4, TERMPAINT_MOUSE_PRESS, 0, TERMPAINT_MOD_SHIFT }, TestCase{ "\033[<4;105;18m", 104, 17, 4, TERMPAINT_MOUSE_RELEASE, 0, TERMPAINT_MOD_SHIFT }, TestCase{ "\033[36;105;18M", 104, 17, 4, TERMPAINT_MOUSE_PRESS, 0, TERMPAINT_MOD_SHIFT }, TestCase{ "\033[39;105;18M", 104, 17, 7, TERMPAINT_MOUSE_RELEASE, 3, TERMPAINT_MOD_SHIFT }, TestCase{ "\033[<64;35;5M", 34, 4, 64, TERMPAINT_MOUSE_PRESS, 4, 0 }, TestCase{ "\033[<65;35;5M", 34, 4, 65, TERMPAINT_MOUSE_PRESS, 5, 0 }, TestCase{ "\033[96;35;5M", 34, 4, 64, TERMPAINT_MOUSE_PRESS, 4, 0 }, TestCase{ "\033[97;35;5M", 34, 4, 65, TERMPAINT_MOUSE_PRESS, 5, 0 }, TestCase{ "\033[<128;1;1M", 0, 0, 128, TERMPAINT_MOUSE_PRESS, 8, 0 }, TestCase{ "\033[<128;1;1m", 0, 0, 128, TERMPAINT_MOUSE_RELEASE, 8, 0 }, TestCase{ "\033[160;1;1M", 0, 0, 128, TERMPAINT_MOUSE_PRESS, 8, 0 }, TestCase{ "\033[<129;1;1M", 0, 0, 129, TERMPAINT_MOUSE_PRESS, 9, 0 }, TestCase{ "\033[<129;1;1m", 0, 0, 129, TERMPAINT_MOUSE_RELEASE, 9, 0 }, TestCase{ "\033[161;1;1M", 0, 0, 129, TERMPAINT_MOUSE_PRESS, 9, 0 }, TestCase{ "\033[M¡!!", 0, 0, 129, TERMPAINT_MOUSE_PRESS, 9, 0 }, TestCase{ "\033[<32;35;5M", 34, 4, 32, TERMPAINT_MOUSE_MOVE, 0, 0 }, TestCase{ "\033[64;35;5M", 34, 4, 32, TERMPAINT_MOUSE_MOVE, 0, 0 } ); enum { START, GOT_EVENT } state = START; INFO( "ESC" + testCase.sequence.substr(1)); std::function event_callback = [&] (termpaint_event* event) -> void { if (state == GOT_EVENT) { FAIL("more events than expected"); } else if (state == START) { REQUIRE(event->type == TERMPAINT_EV_MOUSE); CHECK(event->mouse.x == testCase.x); CHECK(event->mouse.y == testCase.y); CHECK(event->mouse.raw_btn_and_flags == testCase.raw); CHECK(event->mouse.action == testCase.action); CHECK(event->mouse.button == testCase.button); CHECK(event->mouse.modifier == testCase.mod); state = GOT_EVENT; } else { FAIL("unexpected state " << state); } }; termpaint_input *input_ctx = termpaint_input_new(); if (testCase.sequence[2] == 'M') { if (testCase.sequence.size() == 6) { termpaint_input_expect_legacy_mouse_reports(input_ctx, TERMPAINT_INPUT_EXPECT_LEGACY_MOUSE); } else { termpaint_input_expect_legacy_mouse_reports(input_ctx, TERMPAINT_INPUT_EXPECT_LEGACY_MOUSE_MODE_1005); } } wrap(termpaint_input_set_event_cb, input_ctx, event_callback); termpaint_input_add_data(input_ctx, testCase.sequence.data(), testCase.sequence.size()); REQUIRE(state == GOT_EVENT); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); termpaint_input_free(input_ctx); } TEST_CASE("input: legacy mouse disable") { std::string sequence = "\033[M!!!"; enum { START, GOT_UNKNOWN, GOT_BANG1, GOT_BANG2, GOT_BANG3 } state = START; std::function event_callback = [&] (termpaint_event* event) -> void { if (state == GOT_BANG3) { FAIL("more events than expected"); } else if (state == START) { REQUIRE(event->type == TERMPAINT_EV_UNKNOWN); state = GOT_UNKNOWN; } else if (state == GOT_UNKNOWN) { REQUIRE(event->type == TERMPAINT_EV_CHAR); REQUIRE(event->c.length == 1); REQUIRE(event->c.string[0] == '!'); state = GOT_BANG1; } else if (state == GOT_BANG1) { REQUIRE(event->type == TERMPAINT_EV_CHAR); REQUIRE(event->c.length == 1); REQUIRE(event->c.string[0] == '!'); state = GOT_BANG2; } else if (state == GOT_BANG2) { REQUIRE(event->type == TERMPAINT_EV_CHAR); REQUIRE(event->c.length == 1); REQUIRE(event->c.string[0] == '!'); state = GOT_BANG3; } else { FAIL("unexpected state " << state); } }; termpaint_input *input_ctx = termpaint_input_new(); termpaint_input_expect_legacy_mouse_reports(input_ctx, TERMPAINT_INPUT_EXPECT_NO_LEGACY_MOUSE); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); termpaint_input_add_data(input_ctx, sequence.data(), sequence.size()); REQUIRE(state == GOT_BANG3); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); termpaint_input_free(input_ctx); } TEST_CASE("input: mode report") { struct TestCase { const std::string sequence; bool kind; int number; int status; }; const auto testCase = GENERATE( TestCase{ "\033[1;1$y", false, 1, 1 }, TestCase{ "\033[?1;1$y", true, 1, 1 }, TestCase{ "\033[?1000;4$y", true, 1000, 4 }, TestCase{ "\033[1;0$y", false, 1, 0 } ); enum { START, GOT_EVENT } state = START; INFO( "ESC" + testCase.sequence.substr(1)); std::function event_callback = [&] (termpaint_event* event) -> void { if (state == GOT_EVENT) { FAIL("more events than expected"); } else if (state == START) { REQUIRE(event->type == TERMPAINT_EV_MODE_REPORT); CHECK(event->mode.kind == testCase.kind); CHECK(event->mode.number == testCase.number); CHECK(event->mode.status == testCase.status); state = GOT_EVENT; } else { FAIL("unexpected state " << state); } }; termpaint_input *input_ctx = termpaint_input_new(); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); termpaint_input_add_data(input_ctx, testCase.sequence.data(), testCase.sequence.size()); REQUIRE(state == GOT_EVENT); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); termpaint_input_free(input_ctx); } TEST_CASE("input: color slot report") { struct TestCase { const std::string sequence; const int number; const std::string content; }; const auto testCase = GENERATE( TestCase{ "\033]10;rgb:0000/0000/0000\033\\", 10, "rgb:0000/0000/0000" }, TestCase{ "\033]10;rgb:0000/0000/0000\007", 10, "rgb:0000/0000/0000" }, TestCase{ "\033]10;rgb:0000/0000/0000\x9c", 10, "rgb:0000/0000/0000" }, TestCase{ "\033]14;red\033\\", 14, "red" }, TestCase{ "\033]14;red\007", 14, "red" }, TestCase{ "\033]14;red\x9c", 14, "red" }, TestCase{ "\033]17;#ffffff\033\\", 17, "#ffffff" }, TestCase{ "\033]17;#ffffff\007", 17, "#ffffff" }, TestCase{ "\033]19;rgba:aaaa/0000/8080/ffff\033\\", 19, "rgba:aaaa/0000/8080/ffff" }, TestCase{ "\033]19;rgba:aaaa/0000/8080/ffff\007", 19, "rgba:aaaa/0000/8080/ffff" }, TestCase{ "\033]705;CIELab:0.45/.23/.56\033\\", 705, "CIELab:0.45/.23/.56" }, TestCase{ "\033]705;CIELab:0.45/.23/.56\007", 705, "CIELab:0.45/.23/.56" }, TestCase{ "\033]708;#fff\033\\", 708, "#fff" }, TestCase{ "\033]708;#fff\007", 708, "#fff" }, TestCase{ "\033]708;#aaaabbbbcccc;\033\\", 708, "#aaaabbbbcccc" }, TestCase{ "\033]708;#aaaabbbbcccc;\007", 708, "#aaaabbbbcccc" } ); enum { START, GOT_EVENT } state = START; INFO( "ESC" + testCase.sequence.substr(1)); std::function event_callback = [&] (termpaint_event* event) -> void { if (state == GOT_EVENT) { FAIL("more events than expected"); } else if (state == START) { REQUIRE(event->type == TERMPAINT_EV_COLOR_SLOT_REPORT); CHECK(event->color_slot_report.slot == testCase.number); CHECK(std::string(event->color_slot_report.color, event->color_slot_report.length) == testCase.content); state = GOT_EVENT; } else { FAIL("unexpected state " << state); } }; termpaint_input *input_ctx = termpaint_input_new(); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); termpaint_input_add_data(input_ctx, testCase.sequence.data(), testCase.sequence.size()); REQUIRE(state == GOT_EVENT); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); termpaint_input_free(input_ctx); } TEST_CASE("input: palette color report") { struct TestCase { const std::string sequence; const int number; const std::string content; }; const auto testCase = GENERATE( TestCase{ "\033]4;10;rgb:0000/0000/0000\033\\", 10, "rgb:0000/0000/0000" }, TestCase{ "\033]4;10;rgb:0000/0000/0000\007", 10, "rgb:0000/0000/0000" }, TestCase{ "\033]4;10;rgb:0000/0000/0000\x9c", 10, "rgb:0000/0000/0000" }, TestCase{ "\033]4;14;red\033\\", 14, "red" }, TestCase{ "\033]4;17;#ffffff\033\\", 17, "#ffffff" }, TestCase{ "\033]4;19;rgba:aaaa/0000/8080/ffff\033\\", 19, "rgba:aaaa/0000/8080/ffff" }, TestCase{ "\033]4;255;CIELab:0.45/.23/.56\033\\", 255, "CIELab:0.45/.23/.56" }, TestCase{ "\033]4;87;#fff\033\\", 87, "#fff" }, TestCase{ "\033]4;0;#aaaabbbbcccc;\033\\", 0, "#aaaabbbbcccc" }, TestCase{ "\033]4;rgb:0000/0000/0000\033\\", -1, "rgb:0000/0000/0000" }, TestCase{ "\033]4;rgb:0000/0000/0000\007", -1, "rgb:0000/0000/0000" } ); enum { START, GOT_EVENT } state = START; INFO( "ESC" + testCase.sequence.substr(1)); std::function event_callback = [&] (termpaint_event* event) -> void { if (state == GOT_EVENT) { FAIL("more events than expected"); } else if (state == START) { REQUIRE(event->type == TERMPAINT_EV_PALETTE_COLOR_REPORT); CHECK(event->palette_color_report.color_index == testCase.number); CHECK(std::string(event->palette_color_report.color_desc, event->palette_color_report.length) == testCase.content); state = GOT_EVENT; } else { FAIL("unexpected state " << state); } }; termpaint_input *input_ctx = termpaint_input_new(); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); termpaint_input_add_data(input_ctx, testCase.sequence.data(), testCase.sequence.size()); REQUIRE(state == GOT_EVENT); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); termpaint_input_free(input_ctx); } TEST_CASE("input: bracketed paste manual") { enum { START, PASTE_BEGIN, GOT_A, GOT_B, GOT_C, PASTE_END } state = START; std::function event_callback = [&] (termpaint_event* event) -> void { if (state == PASTE_END) { FAIL("more events than expected"); } else if (state == START) { REQUIRE(event->type == TERMPAINT_EV_MISC); CHECK(event->misc.atom == termpaint_input_paste_begin()); state = PASTE_BEGIN; } else if (state == PASTE_BEGIN) { REQUIRE(event->type == TERMPAINT_EV_CHAR); CHECK(event->c.length == 1); CHECK(event->c.string[0] == 'a'); state = GOT_A; } else if (state == GOT_A) { REQUIRE(event->type == TERMPAINT_EV_CHAR); CHECK(event->c.length == 1); CHECK(event->c.string[0] == 'b'); state = GOT_B; } else if (state == GOT_B) { REQUIRE(event->type == TERMPAINT_EV_CHAR); CHECK(event->c.length == 1); CHECK(event->c.string[0] == 'c'); state = GOT_C; } else if (state == GOT_C) { REQUIRE(event->type == TERMPAINT_EV_MISC); CHECK(event->misc.atom == termpaint_input_paste_end()); state = PASTE_END; } else { FAIL("unexpected state " << state); } }; termpaint_input *input_ctx = termpaint_input_new(); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); termpaint_input_handle_paste(input_ctx, false); std::string sequence = "\033[200~abc\033[201~"; termpaint_input_add_data(input_ctx, sequence.data(), sequence.size()); REQUIRE(state == PASTE_END); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); termpaint_input_free(input_ctx); } TEST_CASE("input: bracketed paste handling") { // this test is intentionally loose not fixing the exact distribution // of the sequence over events. std::string pasted_data; enum { START, PASTE_DATA, PASTE_END } state = START; std::function event_callback = [&] (termpaint_event* event) -> void { if (state == PASTE_END) { FAIL("more events than expected"); } else if (state == START) { REQUIRE(event->type == TERMPAINT_EV_PASTE); CHECK(event->paste.initial); pasted_data += std::string(event->paste.string, event->paste.length); state = PASTE_DATA; } else if (state == PASTE_DATA) { REQUIRE(event->type == TERMPAINT_EV_PASTE); if (event->paste.final) { state = PASTE_END; } else { state = PASTE_DATA; } pasted_data += std::string(event->paste.string, event->paste.length); } else { FAIL("unexpected state " << state); } }; termpaint_input *input_ctx = termpaint_input_new(); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); // handle_paste is true by default std::string sequence = "\033[200~abc\033[201~"; termpaint_input_add_data(input_ctx, sequence.data(), sequence.size()); REQUIRE(state == PASTE_END); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); REQUIRE(pasted_data == "abc"); termpaint_input_free(input_ctx); } TEST_CASE("input: retriggering") { // test mechanism to detect end of sequences that are prefixes to other valid sequence types. // this also force terminates most unterminated sequences. struct TestCase { const std::string sequence; const std::string desc; }; const auto testCase = GENERATE( TestCase{ "\033", "escape" }, TestCase{ "\033[", "alt-[" }, TestCase{ "\033O", "alt-O" }, TestCase{ "\033P", "alt-P" }, TestCase{ "\033]", "alt-]" }, TestCase{ "\033]stuff", "unterminated OSC" }, TestCase{ "\033P \\", "unterminated DSC with backslash" }, TestCase{ "\033Pstuff", "unterminated DSC" }, TestCase{ "\033] \\", "unterminated OSC with backslash" }, TestCase{ "\033[?45$", "unterminated CSI" }, TestCase{ "\033O45", "unterminated SS3" }, TestCase{ "\xc0", "unterminated utf8 (2)" }, TestCase{ "\xe0", "unterminated utf8 (3)" }, TestCase{ "\xf0", "unterminated utf8 (4)" }, TestCase{ "\xf8", "unterminated utf8 (5)" }, TestCase{ "\xfc", "unterminated utf8 (6)" } ); enum { INCOMPLETE, RETRIGGER, GOT_EVENT, GOT_RESYNC } state = INCOMPLETE; INFO(testCase.desc); std::function event_callback = [&] (termpaint_event* event) -> void { if (state == GOT_RESYNC) { FAIL("more events than expected"); } else if (state == INCOMPLETE) { FAIL("incomplete sequence caused event"); } else if (state == RETRIGGER) { if (testCase.desc == "escape") { REQUIRE(event->type == TERMPAINT_EV_KEY); REQUIRE(event->key.atom == termpaint_input_escape()); } else if (testCase.desc == "alt-[") { REQUIRE(event->type == TERMPAINT_EV_CHAR); REQUIRE(event->c.modifier == TERMPAINT_MOD_ALT); REQUIRE(std::string(event->c.string, event->c.length) == "["); } else if (testCase.desc == "alt-O") { REQUIRE(event->type == TERMPAINT_EV_CHAR); REQUIRE(event->c.modifier == TERMPAINT_MOD_ALT); REQUIRE(std::string(event->c.string, event->c.length) == "O"); } else if (testCase.desc == "alt-P") { REQUIRE(event->type == TERMPAINT_EV_CHAR); REQUIRE(event->c.modifier == TERMPAINT_MOD_ALT); REQUIRE(std::string(event->c.string, event->c.length) == "P"); } else if (testCase.desc == "alt-]") { REQUIRE(event->type == TERMPAINT_EV_CHAR); REQUIRE(event->c.modifier == TERMPAINT_MOD_ALT); REQUIRE(std::string(event->c.string, event->c.length) == "]"); } else { if (testCase.sequence[0] == '\033') { REQUIRE(event->type == TERMPAINT_EV_UNKNOWN); } else { REQUIRE(event->type == TERMPAINT_EV_INVALID_UTF8); } } state = GOT_EVENT; } else if (state == GOT_EVENT) { REQUIRE(event->type == TERMPAINT_EV_MISC); CHECK(event->misc.atom == termpaint_input_i_resync()); state = GOT_RESYNC; } else { FAIL("unexpected state " << state); } }; termpaint_input *input_ctx = termpaint_input_new(); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); termpaint_input_add_data(input_ctx, testCase.sequence.data(), testCase.sequence.size()); state = RETRIGGER; const std::string resync = "\033[0n"; termpaint_input_add_data(input_ctx, resync.data(), resync.size()); REQUIRE(state == GOT_RESYNC); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); termpaint_input_free(input_ctx); } TEST_CASE("input: atoms") { #define TEST_ATOM(name, s) CHECK(termpaint_input_ ## name() == std::string(s)) TEST_ATOM(i_resync, "i_resync"); TEST_ATOM(enter, "Enter"); TEST_ATOM(space, "Space"); TEST_ATOM(tab, "Tab"); TEST_ATOM(backspace, "Backspace"); TEST_ATOM(context_menu, "ContextMenu"); TEST_ATOM(delete, "Delete"); TEST_ATOM(end, "End"); TEST_ATOM(home, "Home"); TEST_ATOM(insert, "Insert"); TEST_ATOM(page_down, "PageDown"); TEST_ATOM(page_up, "PageUp"); TEST_ATOM(arrow_down, "ArrowDown"); TEST_ATOM(arrow_left, "ArrowLeft"); TEST_ATOM(arrow_right, "ArrowRight"); TEST_ATOM(arrow_up, "ArrowUp"); TEST_ATOM(numpad_divide, "NumpadDivide"); TEST_ATOM(numpad_multiply, "NumpadMultiply"); TEST_ATOM(numpad_subtract, "NumpadSubtract"); TEST_ATOM(numpad_add, "NumpadAdd"); TEST_ATOM(numpad_enter, "NumpadEnter"); TEST_ATOM(numpad_decimal, "NumpadDecimal"); TEST_ATOM(numpad0, "Numpad0"); TEST_ATOM(numpad1, "Numpad1"); TEST_ATOM(numpad2, "Numpad2"); TEST_ATOM(numpad3, "Numpad3"); TEST_ATOM(numpad4, "Numpad4"); TEST_ATOM(numpad5, "Numpad5"); TEST_ATOM(numpad6, "Numpad6"); TEST_ATOM(numpad7, "Numpad7"); TEST_ATOM(numpad8, "Numpad8"); TEST_ATOM(numpad9, "Numpad9"); TEST_ATOM(escape, "Escape"); TEST_ATOM(f1, "F1"); TEST_ATOM(f2, "F2"); TEST_ATOM(f3, "F3"); TEST_ATOM(f4, "F4"); TEST_ATOM(f5, "F5"); TEST_ATOM(f6, "F6"); TEST_ATOM(f7, "F7"); TEST_ATOM(f8, "F8"); TEST_ATOM(f9, "F9"); TEST_ATOM(f10, "F10"); TEST_ATOM(f11, "F11"); TEST_ATOM(f12, "F12"); TEST_ATOM(focus_in, "FocusIn"); TEST_ATOM(focus_out, "FocusOut"); } TEST_CASE("input: peek buffer") { std::string sequence = "\033[0n"; enum { START, GOT_KEY } state = START; std::function event_callback = [&] (termpaint_event* event) -> void { if (state == GOT_KEY) { FAIL("more events than expected"); } else if (state == START) { REQUIRE(event->type == TERMPAINT_EV_MISC); REQUIRE(event->misc.atom == termpaint_input_i_resync()); state = GOT_KEY; } else { FAIL("unexpected state " << state); } }; termpaint_input *input_ctx = termpaint_input_new(); wrap(termpaint_input_set_event_cb, input_ctx, event_callback); std::string buffer; for (size_t i = 0; i < sequence.size(); i++) { CAPTURE(i); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == toInt(buffer.size())); REQUIRE(std::string(termpaint_input_peek_buffer(input_ctx), buffer.size()) == buffer); termpaint_input_add_data(input_ctx, sequence.data() + i, 1); buffer.append(sequence.data() + i, 1); } REQUIRE(state == GOT_KEY); REQUIRE(termpaint_input_peek_buffer_length(input_ctx) == 0); termpaint_input_free(input_ctx); } termpaint-0.3.1/tests/input_tests.json000066400000000000000000006026041477303547200201600ustar00rootroot00000000000000[ { "key": "Space", "keyId": "Space.C", "mod": " C", "raw": "00", "type": "key" }, { "chars": "a", "keyId": "a.C", "mod": " C", "raw": "01", "type": "char" }, { "chars": "b", "keyId": "b.C", "mod": " C", "raw": "02", "type": "char" }, { "chars": "c", "keyId": "c.C", "mod": " C", "raw": "03", "type": "char" }, { "chars": "d", "keyId": "d.C", "mod": " C", "raw": "04", "type": "char" }, { "chars": "e", "keyId": "e.C", "mod": " C", "raw": "05", "type": "char" }, { "chars": "f", "keyId": "f.C", "mod": " C", "raw": "06", "type": "char" }, { "chars": "g", "keyId": "g.C", "mod": " C", "raw": "07", "type": "char" }, { "key": "Backspace", "keyId": "Backspace.C", "mod": " C", "raw": "08", "type": "key" }, { "key": "Tab", "keyId": "Tab.", "mod": " ", "raw": "09", "type": "key" }, { "chars": "j", "keyId": "j.C", "mod": " C", "raw": "0a", "type": "char" }, { "chars": "k", "keyId": "k.C", "mod": " C", "raw": "0b", "type": "char" }, { "chars": "l", "keyId": "l.C", "mod": " C", "raw": "0c", "type": "char" }, { "key": "Enter", "keyId": "Enter.", "mod": " ", "raw": "0d", "type": "key" }, { "chars": "n", "keyId": "n.C", "mod": " C", "raw": "0e", "type": "char" }, { "chars": "o", "keyId": "o.C", "mod": " C", "raw": "0f", "type": "char" }, { "chars": "p", "keyId": "p.C", "mod": " C", "raw": "10", "type": "char" }, { "chars": "q", "keyId": "q.C", "mod": " C", "raw": "11", "type": "char" }, { "chars": "r", "keyId": "r.C", "mod": " C", "raw": "12", "type": "char" }, { "chars": "s", "keyId": "s.C", "mod": " C", "raw": "13", "type": "char" }, { "chars": "t", "keyId": "t.C", "mod": " C", "raw": "14", "type": "char" }, { "chars": "u", "keyId": "u.C", "mod": " C", "raw": "15", "type": "char" }, { "chars": "v", "keyId": "v.C", "mod": " C", "raw": "16", "type": "char" }, { "chars": "w", "keyId": "w.C", "mod": " C", "raw": "17", "type": "char" }, { "chars": "x", "keyId": "x.C", "mod": " C", "raw": "18", "type": "char" }, { "chars": "y", "keyId": "y.C", "mod": " C", "raw": "19", "type": "char" }, { "chars": "z", "keyId": "z.C", "mod": " C", "raw": "1a", "type": "char" }, { "key": "Escape", "keyId": "Escape.", "mod": " ", "raw": "1b", "type": "key" }, { "key": "Space", "keyId": "Space.AC", "mod": " AC", "raw": "1b00", "type": "key" }, { "chars": "a", "keyId": "a.AC", "mod": " AC", "raw": "1b01", "type": "char" }, { "chars": "b", "keyId": "b.AC", "mod": " AC", "raw": "1b02", "type": "char" }, { "chars": "c", "keyId": "c.AC", "mod": " AC", "raw": "1b03", "type": "char" }, { "chars": "d", "keyId": "d.AC", "mod": " AC", "raw": "1b04", "type": "char" }, { "chars": "e", "keyId": "e.AC", "mod": " AC", "raw": "1b05", "type": "char" }, { "chars": "f", "keyId": "f.AC", "mod": " AC", "raw": "1b06", "type": "char" }, { "chars": "g", "keyId": "g.AC", "mod": " AC", "raw": "1b07", "type": "char" }, { "key": "Backspace", "keyId": "Backspace.AC", "mod": " AC", "raw": "1b08", "type": "key" }, { "key": "Tab", "keyId": "Tab.A", "mod": " A ", "raw": "1b09", "type": "key" }, { "chars": "j", "keyId": "j.AC", "mod": " AC", "raw": "1b0a", "type": "char" }, { "chars": "k", "keyId": "k.AC", "mod": " AC", "raw": "1b0b", "type": "char" }, { "chars": "l", "keyId": "l.AC", "mod": " AC", "raw": "1b0c", "type": "char" }, { "key": "Enter", "keyId": "Enter.A", "mod": " A ", "raw": "1b0d", "type": "key" }, { "chars": "n", "keyId": "n.AC", "mod": " AC", "raw": "1b0e", "type": "char" }, { "chars": "o", "keyId": "o.AC", "mod": " AC", "raw": "1b0f", "type": "char" }, { "chars": "p", "keyId": "p.AC", "mod": " AC", "raw": "1b10", "type": "char" }, { "chars": "q", "keyId": "q.AC", "mod": " AC", "raw": "1b11", "type": "char" }, { "chars": "r", "keyId": "r.AC", "mod": " AC", "raw": "1b12", "type": "char" }, { "chars": "s", "keyId": "s.AC", "mod": " AC", "raw": "1b13", "type": "char" }, { "chars": "t", "keyId": "t.AC", "mod": " AC", "raw": "1b14", "type": "char" }, { "chars": "u", "keyId": "u.AC", "mod": " AC", "raw": "1b15", "type": "char" }, { "chars": "v", "keyId": "v.AC", "mod": " AC", "raw": "1b16", "type": "char" }, { "chars": "w", "keyId": "w.AC", "mod": " AC", "raw": "1b17", "type": "char" }, { "chars": "x", "keyId": "x.AC", "mod": " AC", "raw": "1b18", "type": "char" }, { "chars": "y", "keyId": "y.AC", "mod": " AC", "raw": "1b19", "type": "char" }, { "chars": "z", "keyId": "z.AC", "mod": " AC", "raw": "1b1a", "type": "char" }, { "key": "Escape", "keyId": "Escape.A", "mod": " A ", "raw": "1b1b", "type": "key" }, { "key": "NumpadEnter", "keyId": "NumpadEnter.A", "mod": " A ", "raw": "1b1b4f4d", "type": "key" }, { "key": "ArrowUp", "keyId": "ArrowUp.AC", "mod": " AC", "raw": "1b1b4f61", "type": "key" }, { "key": "ArrowDown", "keyId": "ArrowDown.AC", "mod": " AC", "raw": "1b1b4f62", "type": "key" }, { "key": "ArrowRight", "keyId": "ArrowRight.AC", "mod": " AC", "raw": "1b1b4f63", "type": "key" }, { "key": "ArrowLeft", "keyId": "ArrowLeft.AC", "mod": " AC", "raw": "1b1b4f64", "type": "key" }, { "key": "NumpadMultiply", "keyId": "NumpadMultiply.A", "mod": " A ", "raw": "1b1b4f6a", "type": "key" }, { "key": "NumpadAdd", "keyId": "NumpadAdd.A", "mod": " A ", "raw": "1b1b4f6b", "type": "key" }, { "key": "NumpadSubtract", "keyId": "NumpadSubtract.A", "mod": " A ", "raw": "1b1b4f6d", "type": "key" }, { "key": "NumpadDecimal", "keyId": "NumpadDecimal.A", "mod": " A ", "raw": "1b1b4f6e", "type": "key" }, { "key": "NumpadDivide", "keyId": "NumpadDivide.A", "mod": " A ", "raw": "1b1b4f6f", "type": "key" }, { "key": "Numpad0", "keyId": "Numpad0.A", "mod": " A ", "raw": "1b1b4f70", "type": "key" }, { "key": "Numpad1", "keyId": "Numpad1.A", "mod": " A ", "raw": "1b1b4f71", "type": "key" }, { "key": "Numpad2", "keyId": "Numpad2.A", "mod": " A ", "raw": "1b1b4f72", "type": "key" }, { "key": "Numpad3", "keyId": "Numpad3.A", "mod": " A ", "raw": "1b1b4f73", "type": "key" }, { "key": "Numpad4", "keyId": "Numpad4.A", "mod": " A ", "raw": "1b1b4f74", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.A", "mod": " A ", "raw": "1b1b4f75", "type": "key" }, { "key": "Numpad6", "keyId": "Numpad6.A", "mod": " A ", "raw": "1b1b4f76", "type": "key" }, { "key": "Numpad7", "keyId": "Numpad7.A", "mod": " A ", "raw": "1b1b4f77", "type": "key" }, { "key": "Numpad8", "keyId": "Numpad8.A", "mod": " A ", "raw": "1b1b4f78", "type": "key" }, { "key": "Numpad9", "keyId": "Numpad9.A", "mod": " A ", "raw": "1b1b4f79", "type": "key" }, { "key": "F1", "keyId": "F1.A", "mod": " A ", "raw": "1b1b5b31317e", "type": "key" }, { "key": "F2", "keyId": "F2.A", "mod": " A ", "raw": "1b1b5b31327e", "type": "key" }, { "key": "F3", "keyId": "F3.A", "mod": " A ", "raw": "1b1b5b31337e", "type": "key" }, { "key": "F4", "keyId": "F4.A", "mod": " A ", "raw": "1b1b5b31347e", "type": "key" }, { "key": "F5", "keyId": "F5.A", "mod": " A ", "raw": "1b1b5b31357e", "type": "key" }, { "key": "F6", "keyId": "F6.A", "mod": " A ", "raw": "1b1b5b31377e", "type": "key" }, { "key": "F7", "keyId": "F7.A", "mod": " A ", "raw": "1b1b5b31387e", "type": "key" }, { "key": "F8", "keyId": "F8.A", "mod": " A ", "raw": "1b1b5b31397e", "type": "key" }, { "key": "Insert", "keyId": "Insert.SA", "mod": "SA ", "raw": "1b1b5b3224", "type": "key" }, { "key": "F9", "keyId": "F9.A", "mod": " A ", "raw": "1b1b5b32307e", "type": "key" }, { "key": "F10", "keyId": "F10.A", "mod": " A ", "raw": "1b1b5b32317e", "type": "key" }, { "key": "F11", "keyId": "F11.SA", "mod": "SA ", "raw": "1b1b5b323324", "type": "key" }, { "key": "F11", "keyId": "F11.SAC", "mod": "SAC", "raw": "1b1b5b323340", "type": "key" }, { "key": "F11", "keyId": "F11.AC", "mod": " AC", "raw": "1b1b5b32335e", "type": "key" }, { "key": "F11", "keyId": "F11.A", "mod": " A ", "raw": "1b1b5b32337e", "type": "key" }, { "key": "F12", "keyId": "F12.SA", "mod": "SA ", "raw": "1b1b5b323424", "type": "key" }, { "key": "F12", "keyId": "F12.SAC", "mod": "SAC", "raw": "1b1b5b323440", "type": "key" }, { "key": "F12", "keyId": "F12.AC", "mod": " AC", "raw": "1b1b5b32345e", "type": "key" }, { "key": "F12", "keyId": "F12.A", "mod": " A ", "raw": "1b1b5b32347e", "type": "key" }, { "key": "F1", "keyId": "F1.SAC", "mod": "SAC", "raw": "1b1b5b32355e", "type": "key" }, { "key": "F1", "keyId": "F1.SA", "mod": "SA ", "raw": "1b1b5b32357e", "type": "key" }, { "key": "F2", "keyId": "F2.SAC", "mod": "SAC", "raw": "1b1b5b32365e", "type": "key" }, { "key": "F2", "keyId": "F2.SA", "mod": "SA ", "raw": "1b1b5b32367e", "type": "key" }, { "key": "F3", "keyId": "F3.SAC", "mod": "SAC", "raw": "1b1b5b32385e", "type": "key" }, { "key": "F3", "keyId": "F3.SA", "mod": "SA ", "raw": "1b1b5b32387e", "type": "key" }, { "key": "F4", "keyId": "F4.SAC", "mod": "SAC", "raw": "1b1b5b32395e", "type": "key" }, { "key": "F4", "keyId": "F4.SA", "mod": "SA ", "raw": "1b1b5b32397e", "type": "key" }, { "key": "Insert", "keyId": "Insert.SAC", "mod": "SAC", "raw": "1b1b5b3240", "type": "key" }, { "key": "Insert", "keyId": "Insert.AC", "mod": " AC", "raw": "1b1b5b325e", "type": "key" }, { "key": "Delete", "keyId": "Delete.SA", "mod": "SA ", "raw": "1b1b5b3324", "type": "key" }, { "key": "F5", "keyId": "F5.SAC", "mod": "SAC", "raw": "1b1b5b33315e", "type": "key" }, { "key": "F5", "keyId": "F5.SA", "mod": "SA ", "raw": "1b1b5b33317e", "type": "key" }, { "key": "F6", "keyId": "F6.SAC", "mod": "SAC", "raw": "1b1b5b33325e", "type": "key" }, { "key": "F6", "keyId": "F6.SA", "mod": "SA ", "raw": "1b1b5b33327e", "type": "key" }, { "key": "F7", "keyId": "F7.SAC", "mod": "SAC", "raw": "1b1b5b33335e", "type": "key" }, { "key": "F7", "keyId": "F7.SA", "mod": "SA ", "raw": "1b1b5b33337e", "type": "key" }, { "key": "F8", "keyId": "F8.SAC", "mod": "SAC", "raw": "1b1b5b33345e", "type": "key" }, { "key": "F8", "keyId": "F8.SA", "mod": "SA ", "raw": "1b1b5b33347e", "type": "key" }, { "key": "Delete", "keyId": "Delete.SAC", "mod": "SAC", "raw": "1b1b5b3340", "type": "key" }, { "key": "Delete", "keyId": "Delete.AC", "mod": " AC", "raw": "1b1b5b335e", "type": "key" }, { "key": "Delete", "keyId": "Delete.A", "mod": " A ", "raw": "1b1b5b337e", "type": "key" }, { "key": "PageUp", "keyId": "PageUp.SA", "mod": "SA ", "raw": "1b1b5b3524", "type": "key" }, { "key": "PageUp", "keyId": "PageUp.SAC", "mod": "SAC", "raw": "1b1b5b3540", "type": "key" }, { "key": "PageUp", "keyId": "PageUp.AC", "mod": " AC", "raw": "1b1b5b355e", "type": "key" }, { "key": "PageUp", "keyId": "PageUp.A", "mod": " A ", "raw": "1b1b5b357e", "type": "key" }, { "key": "PageDown", "keyId": "PageDown.SA", "mod": "SA ", "raw": "1b1b5b3624", "type": "key" }, { "key": "PageDown", "keyId": "PageDown.SAC", "mod": "SAC", "raw": "1b1b5b3640", "type": "key" }, { "key": "PageDown", "keyId": "PageDown.AC", "mod": " AC", "raw": "1b1b5b365e", "type": "key" }, { "key": "PageDown", "keyId": "PageDown.A", "mod": " A ", "raw": "1b1b5b367e", "type": "key" }, { "key": "Home", "keyId": "Home.SA", "mod": "SA ", "raw": "1b1b5b3724", "type": "key" }, { "key": "Home", "keyId": "Home.SAC", "mod": "SAC", "raw": "1b1b5b3740", "type": "key" }, { "key": "Home", "keyId": "Home.AC", "mod": " AC", "raw": "1b1b5b375e", "type": "key" }, { "key": "Home", "keyId": "Home.A", "mod": " A ", "raw": "1b1b5b377e", "type": "key" }, { "key": "End", "keyId": "End.SA", "mod": "SA ", "raw": "1b1b5b3824", "type": "key" }, { "key": "End", "keyId": "End.SAC", "mod": "SAC", "raw": "1b1b5b3840", "type": "key" }, { "key": "End", "keyId": "End.AC", "mod": " AC", "raw": "1b1b5b385e", "type": "key" }, { "key": "ArrowUp", "keyId": "ArrowUp.A", "mod": " A ", "raw": "1b1b5b41", "type": "key" }, { "key": "ArrowDown", "keyId": "ArrowDown.A", "mod": " A ", "raw": "1b1b5b42", "type": "key" }, { "key": "ArrowRight", "keyId": "ArrowRight.A", "mod": " A ", "raw": "1b1b5b43", "type": "key" }, { "key": "ArrowLeft", "keyId": "ArrowLeft.A", "mod": " A ", "raw": "1b1b5b44", "type": "key" }, { "key": "ArrowUp", "keyId": "ArrowUp.SA", "mod": "SA ", "raw": "1b1b5b61", "type": "key" }, { "key": "ArrowDown", "keyId": "ArrowDown.SA", "mod": "SA ", "raw": "1b1b5b62", "type": "key" }, { "key": "ArrowRight", "keyId": "ArrowRight.SA", "mod": "SA ", "raw": "1b1b5b63", "type": "key" }, { "key": "ArrowLeft", "keyId": "ArrowLeft.SA", "mod": "SA ", "raw": "1b1b5b64", "type": "key" }, { "chars": "\\", "keyId": "\\.AC", "mod": " AC", "raw": "1b1c", "type": "char" }, { "chars": "]", "keyId": "].AC", "mod": " AC", "raw": "1b1d", "type": "char" }, { "chars": "~", "keyId": "~.AC", "mod": " AC", "raw": "1b1e", "type": "char" }, { "chars": "?", "keyId": "?.AC", "mod": " AC", "raw": "1b1f", "type": "char" }, { "key": "Space", "keyId": "Space.A", "mod": " A ", "raw": "1b20", "type": "key" }, { "chars": "!", "keyId": "!.A", "mod": " A ", "raw": "1b21", "type": "char" }, { "chars": "\"", "keyId": "\".A", "mod": " A ", "raw": "1b22", "type": "char" }, { "chars": "#", "keyId": "#.A", "mod": " A ", "raw": "1b23", "type": "char" }, { "chars": "$", "keyId": "$.A", "mod": " A ", "raw": "1b24", "type": "char" }, { "chars": "%", "keyId": "%.A", "mod": " A ", "raw": "1b25", "type": "char" }, { "chars": "&", "keyId": "&.A", "mod": " A ", "raw": "1b26", "type": "char" }, { "chars": "'", "keyId": "'.A", "mod": " A ", "raw": "1b27", "type": "char" }, { "chars": "(", "keyId": "(.A", "mod": " A ", "raw": "1b28", "type": "char" }, { "chars": ")", "keyId": ").A", "mod": " A ", "raw": "1b29", "type": "char" }, { "chars": "*", "keyId": "*.A", "mod": " A ", "raw": "1b2a", "type": "char" }, { "chars": "+", "keyId": "+.A", "mod": " A ", "raw": "1b2b", "type": "char" }, { "chars": ",", "keyId": ",.A", "mod": " A ", "raw": "1b2c", "type": "char" }, { "chars": "-", "keyId": "-.A", "mod": " A ", "raw": "1b2d", "type": "char" }, { "chars": ".", "keyId": "..A", "mod": " A ", "raw": "1b2e", "type": "char" }, { "chars": "/", "keyId": "/.A", "mod": " A ", "raw": "1b2f", "type": "char" }, { "chars": "0", "keyId": "0.A", "mod": " A ", "raw": "1b30", "type": "char" }, { "chars": "1", "keyId": "1.A", "mod": " A ", "raw": "1b31", "type": "char" }, { "chars": "2", "keyId": "2.A", "mod": " A ", "raw": "1b32", "type": "char" }, { "chars": "3", "keyId": "3.A", "mod": " A ", "raw": "1b33", "type": "char" }, { "chars": "4", "keyId": "4.A", "mod": " A ", "raw": "1b34", "type": "char" }, { "chars": "5", "keyId": "5.A", "mod": " A ", "raw": "1b35", "type": "char" }, { "chars": "6", "keyId": "6.A", "mod": " A ", "raw": "1b36", "type": "char" }, { "chars": "7", "keyId": "7.A", "mod": " A ", "raw": "1b37", "type": "char" }, { "chars": "8", "keyId": "8.A", "mod": " A ", "raw": "1b38", "type": "char" }, { "chars": "9", "keyId": "9.A", "mod": " A ", "raw": "1b39", "type": "char" }, { "chars": ":", "keyId": ":.A", "mod": " A ", "raw": "1b3a", "type": "char" }, { "chars": ";", "keyId": ";.A", "mod": " A ", "raw": "1b3b", "type": "char" }, { "chars": "<", "keyId": "<.A", "mod": " A ", "raw": "1b3c", "type": "char" }, { "chars": "=", "keyId": "=.A", "mod": " A ", "raw": "1b3d", "type": "char" }, { "chars": ">", "keyId": ">.A", "mod": " A ", "raw": "1b3e", "type": "char" }, { "chars": "?", "keyId": "?.A", "mod": " A ", "raw": "1b3f", "type": "char" }, { "chars": "A", "keyId": "A.A", "mod": " A ", "raw": "1b41", "type": "char" }, { "chars": "B", "keyId": "B.A", "mod": " A ", "raw": "1b42", "type": "char" }, { "chars": "C", "keyId": "C.A", "mod": " A ", "raw": "1b43", "type": "char" }, { "chars": "D", "keyId": "D.A", "mod": " A ", "raw": "1b44", "type": "char" }, { "chars": "E", "keyId": "E.A", "mod": " A ", "raw": "1b45", "type": "char" }, { "chars": "F", "keyId": "F.A", "mod": " A ", "raw": "1b46", "type": "char" }, { "chars": "G", "keyId": "G.A", "mod": " A ", "raw": "1b47", "type": "char" }, { "chars": "H", "keyId": "H.A", "mod": " A ", "raw": "1b48", "type": "char" }, { "chars": "I", "keyId": "I.A", "mod": " A ", "raw": "1b49", "type": "char" }, { "chars": "J", "keyId": "J.A", "mod": " A ", "raw": "1b4a", "type": "char" }, { "chars": "K", "keyId": "K.A", "mod": " A ", "raw": "1b4b", "type": "char" }, { "chars": "L", "keyId": "L.A", "mod": " A ", "raw": "1b4c", "type": "char" }, { "chars": "M", "keyId": "M.A", "mod": " A ", "raw": "1b4d", "type": "char" }, { "chars": "N", "keyId": "N.A", "mod": " A ", "raw": "1b4e", "type": "char" }, { "chars": "O", "keyId": "O.A", "mod": " A ", "raw": "1b4f", "type": "char" }, { "key": "NumpadEnter", "keyId": "NumpadEnter.S", "mod": "S ", "raw": "1b4f324d", "type": "key" }, { "key": "F1", "keyId": "F1.S", "mod": "S ", "raw": "1b4f3250", "type": "key" }, { "key": "F2", "keyId": "F2.S", "mod": "S ", "raw": "1b4f3251", "type": "key" }, { "key": "F3", "keyId": "F3.S", "mod": "S ", "raw": "1b4f3252", "type": "key" }, { "key": "F4", "keyId": "F4.S", "mod": "S ", "raw": "1b4f3253", "type": "key" }, { "key": "NumpadMultiply", "keyId": "NumpadMultiply.S", "mod": "S ", "raw": "1b4f326a", "type": "key" }, { "key": "NumpadAdd", "keyId": "NumpadAdd.S", "mod": "S ", "raw": "1b4f326b", "type": "key" }, { "key": "NumpadDecimal", "keyId": "NumpadDecimal.S", "mod": "S ", "raw": "1b4f326c", "type": "key" }, { "key": "NumpadSubtract", "keyId": "NumpadSubtract.S", "mod": "S ", "raw": "1b4f326d", "type": "key" }, { "key": "NumpadDivide", "keyId": "NumpadDivide.S", "mod": "S ", "raw": "1b4f326f", "type": "key" }, { "key": "Numpad0", "keyId": "Numpad0.S", "mod": "S ", "raw": "1b4f3270", "type": "key" }, { "key": "Numpad1", "keyId": "Numpad1.S", "mod": "S ", "raw": "1b4f3271", "type": "key" }, { "key": "Numpad2", "keyId": "Numpad2.S", "mod": "S ", "raw": "1b4f3272", "type": "key" }, { "key": "Numpad3", "keyId": "Numpad3.S", "mod": "S ", "raw": "1b4f3273", "type": "key" }, { "key": "Numpad4", "keyId": "Numpad4.S", "mod": "S ", "raw": "1b4f3274", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.S", "mod": "S ", "raw": "1b4f3275", "type": "key" }, { "key": "Numpad6", "keyId": "Numpad6.S", "mod": "S ", "raw": "1b4f3276", "type": "key" }, { "key": "Numpad7", "keyId": "Numpad7.S", "mod": "S ", "raw": "1b4f3277", "type": "key" }, { "key": "Numpad8", "keyId": "Numpad8.S", "mod": "S ", "raw": "1b4f3278", "type": "key" }, { "key": "Numpad9", "keyId": "Numpad9.S", "mod": "S ", "raw": "1b4f3279", "type": "key" }, { "key": "NumpadEnter", "keyId": "NumpadEnter.A", "mod": " A ", "raw": "1b4f334d", "type": "key" }, { "key": "F1", "keyId": "F1.A", "mod": " A ", "raw": "1b4f3350", "type": "key" }, { "key": "F2", "keyId": "F2.A", "mod": " A ", "raw": "1b4f3351", "type": "key" }, { "key": "F3", "keyId": "F3.A", "mod": " A ", "raw": "1b4f3352", "type": "key" }, { "key": "F4", "keyId": "F4.A", "mod": " A ", "raw": "1b4f3353", "type": "key" }, { "key": "NumpadMultiply", "keyId": "NumpadMultiply.A", "mod": " A ", "raw": "1b4f336a", "type": "key" }, { "key": "NumpadAdd", "keyId": "NumpadAdd.A", "mod": " A ", "raw": "1b4f336b", "type": "key" }, { "key": "NumpadDecimal", "keyId": "NumpadDecimal.A", "mod": " A ", "raw": "1b4f336c", "type": "key" }, { "key": "NumpadSubtract", "keyId": "NumpadSubtract.A", "mod": " A ", "raw": "1b4f336d", "type": "key" }, { "key": "NumpadDivide", "keyId": "NumpadDivide.A", "mod": " A ", "raw": "1b4f336f", "type": "key" }, { "key": "Numpad0", "keyId": "Numpad0.A", "mod": " A ", "raw": "1b4f3370", "type": "key" }, { "key": "Numpad1", "keyId": "Numpad1.A", "mod": " A ", "raw": "1b4f3371", "type": "key" }, { "key": "Numpad2", "keyId": "Numpad2.A", "mod": " A ", "raw": "1b4f3372", "type": "key" }, { "key": "Numpad3", "keyId": "Numpad3.A", "mod": " A ", "raw": "1b4f3373", "type": "key" }, { "key": "Numpad4", "keyId": "Numpad4.A", "mod": " A ", "raw": "1b4f3374", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.A", "mod": " A ", "raw": "1b4f3375", "type": "key" }, { "key": "Numpad6", "keyId": "Numpad6.A", "mod": " A ", "raw": "1b4f3376", "type": "key" }, { "key": "Numpad7", "keyId": "Numpad7.A", "mod": " A ", "raw": "1b4f3377", "type": "key" }, { "key": "Numpad8", "keyId": "Numpad8.A", "mod": " A ", "raw": "1b4f3378", "type": "key" }, { "key": "Numpad9", "keyId": "Numpad9.A", "mod": " A ", "raw": "1b4f3379", "type": "key" }, { "key": "NumpadEnter", "keyId": "NumpadEnter.SA", "mod": "SA ", "raw": "1b4f344d", "type": "key" }, { "key": "F1", "keyId": "F1.SA", "mod": "SA ", "raw": "1b4f3450", "type": "key" }, { "key": "F2", "keyId": "F2.SA", "mod": "SA ", "raw": "1b4f3451", "type": "key" }, { "key": "F3", "keyId": "F3.SA", "mod": "SA ", "raw": "1b4f3452", "type": "key" }, { "key": "F4", "keyId": "F4.SA", "mod": "SA ", "raw": "1b4f3453", "type": "key" }, { "key": "NumpadMultiply", "keyId": "NumpadMultiply.SA", "mod": "SA ", "raw": "1b4f346a", "type": "key" }, { "key": "NumpadAdd", "keyId": "NumpadAdd.SA", "mod": "SA ", "raw": "1b4f346b", "type": "key" }, { "key": "NumpadDecimal", "keyId": "NumpadDecimal.SA", "mod": "SA ", "raw": "1b4f346c", "type": "key" }, { "key": "NumpadSubtract", "keyId": "NumpadSubtract.SA", "mod": "SA ", "raw": "1b4f346d", "type": "key" }, { "key": "NumpadDivide", "keyId": "NumpadDivide.SA", "mod": "SA ", "raw": "1b4f346f", "type": "key" }, { "key": "Numpad0", "keyId": "Numpad0.SA", "mod": "SA ", "raw": "1b4f3470", "type": "key" }, { "key": "Numpad1", "keyId": "Numpad1.SA", "mod": "SA ", "raw": "1b4f3471", "type": "key" }, { "key": "Numpad2", "keyId": "Numpad2.SA", "mod": "SA ", "raw": "1b4f3472", "type": "key" }, { "key": "Numpad3", "keyId": "Numpad3.SA", "mod": "SA ", "raw": "1b4f3473", "type": "key" }, { "key": "Numpad4", "keyId": "Numpad4.SA", "mod": "SA ", "raw": "1b4f3474", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.SA", "mod": "SA ", "raw": "1b4f3475", "type": "key" }, { "key": "Numpad6", "keyId": "Numpad6.SA", "mod": "SA ", "raw": "1b4f3476", "type": "key" }, { "key": "Numpad7", "keyId": "Numpad7.SA", "mod": "SA ", "raw": "1b4f3477", "type": "key" }, { "key": "Numpad8", "keyId": "Numpad8.SA", "mod": "SA ", "raw": "1b4f3478", "type": "key" }, { "key": "Numpad9", "keyId": "Numpad9.SA", "mod": "SA ", "raw": "1b4f3479", "type": "key" }, { "key": "NumpadEnter", "keyId": "NumpadEnter.C", "mod": " C", "raw": "1b4f354d", "type": "key" }, { "key": "F1", "keyId": "F1.C", "mod": " C", "raw": "1b4f3550", "type": "key" }, { "key": "F2", "keyId": "F2.C", "mod": " C", "raw": "1b4f3551", "type": "key" }, { "key": "F3", "keyId": "F3.C", "mod": " C", "raw": "1b4f3552", "type": "key" }, { "key": "F4", "keyId": "F4.C", "mod": " C", "raw": "1b4f3553", "type": "key" }, { "key": "NumpadMultiply", "keyId": "NumpadMultiply.C", "mod": " C", "raw": "1b4f356a", "type": "key" }, { "key": "NumpadAdd", "keyId": "NumpadAdd.C", "mod": " C", "raw": "1b4f356b", "type": "key" }, { "key": "NumpadDecimal", "keyId": "NumpadDecimal.C", "mod": " C", "raw": "1b4f356c", "type": "key" }, { "key": "NumpadSubtract", "keyId": "NumpadSubtract.C", "mod": " C", "raw": "1b4f356d", "type": "key" }, { "key": "NumpadDivide", "keyId": "NumpadDivide.C", "mod": " C", "raw": "1b4f356f", "type": "key" }, { "key": "Numpad0", "keyId": "Numpad0.C", "mod": " C", "raw": "1b4f3570", "type": "key" }, { "key": "Numpad1", "keyId": "Numpad1.C", "mod": " C", "raw": "1b4f3571", "type": "key" }, { "key": "Numpad2", "keyId": "Numpad2.C", "mod": " C", "raw": "1b4f3572", "type": "key" }, { "key": "Numpad3", "keyId": "Numpad3.C", "mod": " C", "raw": "1b4f3573", "type": "key" }, { "key": "Numpad4", "keyId": "Numpad4.C", "mod": " C", "raw": "1b4f3574", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.C", "mod": " C", "raw": "1b4f3575", "type": "key" }, { "key": "Numpad6", "keyId": "Numpad6.C", "mod": " C", "raw": "1b4f3576", "type": "key" }, { "key": "Numpad7", "keyId": "Numpad7.C", "mod": " C", "raw": "1b4f3577", "type": "key" }, { "key": "Numpad8", "keyId": "Numpad8.C", "mod": " C", "raw": "1b4f3578", "type": "key" }, { "key": "Numpad9", "keyId": "Numpad9.C", "mod": " C", "raw": "1b4f3579", "type": "key" }, { "key": "NumpadEnter", "keyId": "NumpadEnter.SC", "mod": "S C", "raw": "1b4f364d", "type": "key" }, { "key": "F1", "keyId": "F1.SC", "mod": "S C", "raw": "1b4f3650", "type": "key" }, { "key": "F2", "keyId": "F2.SC", "mod": "S C", "raw": "1b4f3651", "type": "key" }, { "key": "F3", "keyId": "F3.SC", "mod": "S C", "raw": "1b4f3652", "type": "key" }, { "key": "F4", "keyId": "F4.SC", "mod": "S C", "raw": "1b4f3653", "type": "key" }, { "key": "NumpadMultiply", "keyId": "NumpadMultiply.SC", "mod": "S C", "raw": "1b4f366a", "type": "key" }, { "key": "NumpadAdd", "keyId": "NumpadAdd.SC", "mod": "S C", "raw": "1b4f366b", "type": "key" }, { "key": "NumpadDecimal", "keyId": "NumpadDecimal.SC", "mod": "S C", "raw": "1b4f366c", "type": "key" }, { "key": "NumpadSubtract", "keyId": "NumpadSubtract.SC", "mod": "S C", "raw": "1b4f366d", "type": "key" }, { "key": "NumpadDivide", "keyId": "NumpadDivide.SC", "mod": "S C", "raw": "1b4f366f", "type": "key" }, { "key": "Numpad0", "keyId": "Numpad0.SC", "mod": "S C", "raw": "1b4f3670", "type": "key" }, { "key": "Numpad1", "keyId": "Numpad1.SC", "mod": "S C", "raw": "1b4f3671", "type": "key" }, { "key": "Numpad2", "keyId": "Numpad2.SC", "mod": "S C", "raw": "1b4f3672", "type": "key" }, { "key": "Numpad3", "keyId": "Numpad3.SC", "mod": "S C", "raw": "1b4f3673", "type": "key" }, { "key": "Numpad4", "keyId": "Numpad4.SC", "mod": "S C", "raw": "1b4f3674", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.SC", "mod": "S C", "raw": "1b4f3675", "type": "key" }, { "key": "Numpad6", "keyId": "Numpad6.SC", "mod": "S C", "raw": "1b4f3676", "type": "key" }, { "key": "Numpad7", "keyId": "Numpad7.SC", "mod": "S C", "raw": "1b4f3677", "type": "key" }, { "key": "Numpad8", "keyId": "Numpad8.SC", "mod": "S C", "raw": "1b4f3678", "type": "key" }, { "key": "Numpad9", "keyId": "Numpad9.SC", "mod": "S C", "raw": "1b4f3679", "type": "key" }, { "key": "NumpadEnter", "keyId": "NumpadEnter.AC", "mod": " AC", "raw": "1b4f374d", "type": "key" }, { "key": "NumpadDecimal", "keyId": "NumpadDecimal.AC", "mod": " AC", "raw": "1b4f376c", "type": "key" }, { "key": "Numpad0", "keyId": "Numpad0.AC", "mod": " AC", "raw": "1b4f3770", "type": "key" }, { "key": "Numpad1", "keyId": "Numpad1.AC", "mod": " AC", "raw": "1b4f3771", "type": "key" }, { "key": "Numpad2", "keyId": "Numpad2.AC", "mod": " AC", "raw": "1b4f3772", "type": "key" }, { "key": "Numpad3", "keyId": "Numpad3.AC", "mod": " AC", "raw": "1b4f3773", "type": "key" }, { "key": "Numpad4", "keyId": "Numpad4.AC", "mod": " AC", "raw": "1b4f3774", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.AC", "mod": " AC", "raw": "1b4f3775", "type": "key" }, { "key": "Numpad6", "keyId": "Numpad6.AC", "mod": " AC", "raw": "1b4f3776", "type": "key" }, { "key": "Numpad7", "keyId": "Numpad7.AC", "mod": " AC", "raw": "1b4f3777", "type": "key" }, { "key": "Numpad8", "keyId": "Numpad8.AC", "mod": " AC", "raw": "1b4f3778", "type": "key" }, { "key": "Numpad9", "keyId": "Numpad9.AC", "mod": " AC", "raw": "1b4f3779", "type": "key" }, { "key": "NumpadEnter", "keyId": "NumpadEnter.SAC", "mod": "SAC", "raw": "1b4f384d", "type": "key" }, { "key": "F1", "keyId": "F1.SAC", "mod": "SAC", "raw": "1b4f3850", "type": "key" }, { "key": "F2", "keyId": "F2.SAC", "mod": "SAC", "raw": "1b4f3851", "type": "key" }, { "key": "F3", "keyId": "F3.SAC", "mod": "SAC", "raw": "1b4f3852", "type": "key" }, { "key": "F4", "keyId": "F4.SAC", "mod": "SAC", "raw": "1b4f3853", "type": "key" }, { "key": "NumpadMultiply", "keyId": "NumpadMultiply.SAC", "mod": "SAC", "raw": "1b4f386a", "type": "key" }, { "key": "NumpadAdd", "keyId": "NumpadAdd.SAC", "mod": "SAC", "raw": "1b4f386b", "type": "key" }, { "key": "NumpadDecimal", "keyId": "NumpadDecimal.SAC", "mod": "SAC", "raw": "1b4f386c", "type": "key" }, { "key": "NumpadSubtract", "keyId": "NumpadSubtract.SAC", "mod": "SAC", "raw": "1b4f386d", "type": "key" }, { "key": "NumpadDivide", "keyId": "NumpadDivide.SAC", "mod": "SAC", "raw": "1b4f386f", "type": "key" }, { "key": "Numpad0", "keyId": "Numpad0.SAC", "mod": "SAC", "raw": "1b4f3870", "type": "key" }, { "key": "Numpad1", "keyId": "Numpad1.SAC", "mod": "SAC", "raw": "1b4f3871", "type": "key" }, { "key": "Numpad2", "keyId": "Numpad2.SAC", "mod": "SAC", "raw": "1b4f3872", "type": "key" }, { "key": "Numpad3", "keyId": "Numpad3.SAC", "mod": "SAC", "raw": "1b4f3873", "type": "key" }, { "key": "Numpad4", "keyId": "Numpad4.SAC", "mod": "SAC", "raw": "1b4f3874", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.SAC", "mod": "SAC", "raw": "1b4f3875", "type": "key" }, { "key": "Numpad6", "keyId": "Numpad6.SAC", "mod": "SAC", "raw": "1b4f3876", "type": "key" }, { "key": "Numpad7", "keyId": "Numpad7.SAC", "mod": "SAC", "raw": "1b4f3877", "type": "key" }, { "key": "Numpad8", "keyId": "Numpad8.SAC", "mod": "SAC", "raw": "1b4f3878", "type": "key" }, { "key": "Numpad9", "keyId": "Numpad9.SAC", "mod": "SAC", "raw": "1b4f3879", "type": "key" }, { "key": "ArrowUp", "keyId": "ArrowUp.", "mod": " ", "raw": "1b4f41", "type": "key" }, { "key": "ArrowDown", "keyId": "ArrowDown.", "mod": " ", "raw": "1b4f42", "type": "key" }, { "key": "ArrowRight", "keyId": "ArrowRight.", "mod": " ", "raw": "1b4f43", "type": "key" }, { "key": "ArrowLeft", "keyId": "ArrowLeft.", "mod": " ", "raw": "1b4f44", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.", "mod": " ", "raw": "1b4f45", "type": "key" }, { "key": "End", "keyId": "End.", "mod": " ", "raw": "1b4f46", "type": "key" }, { "key": "Home", "keyId": "Home.", "mod": " ", "raw": "1b4f48", "type": "key" }, { "key": "NumpadEnter", "keyId": "NumpadEnter.", "mod": " ", "raw": "1b4f4d", "type": "key" }, { "key": "F1", "keyId": "F1.", "mod": " ", "raw": "1b4f50", "type": "key" }, { "key": "F2", "keyId": "F2.", "mod": " ", "raw": "1b4f51", "type": "key" }, { "key": "F3", "keyId": "F3.", "mod": " ", "raw": "1b4f52", "type": "key" }, { "key": "F4", "keyId": "F4.", "mod": " ", "raw": "1b4f53", "type": "key" }, { "key": "ArrowUp", "keyId": "ArrowUp.C", "mod": " C", "raw": "1b4f61", "type": "key" }, { "key": "ArrowDown", "keyId": "ArrowDown.C", "mod": " C", "raw": "1b4f62", "type": "key" }, { "key": "ArrowRight", "keyId": "ArrowRight.C", "mod": " C", "raw": "1b4f63", "type": "key" }, { "key": "ArrowLeft", "keyId": "ArrowLeft.C", "mod": " C", "raw": "1b4f64", "type": "key" }, { "key": "NumpadMultiply", "keyId": "NumpadMultiply.", "mod": " ", "raw": "1b4f6a", "type": "key" }, { "key": "NumpadAdd", "keyId": "NumpadAdd.", "mod": " ", "raw": "1b4f6b", "type": "key" }, { "key": "NumpadDecimal", "keyId": "NumpadDecimal.A", "mod": " A ", "raw": "1b4f6c", "type": "key" }, { "key": "NumpadSubtract", "keyId": "NumpadSubtract.", "mod": " ", "raw": "1b4f6d", "type": "key" }, { "key": "NumpadDecimal", "keyId": "NumpadDecimal.", "mod": " ", "raw": "1b4f6e", "type": "key" }, { "key": "NumpadDivide", "keyId": "NumpadDivide.", "mod": " ", "raw": "1b4f6f", "type": "key" }, { "key": "Numpad0", "keyId": "Numpad0.", "mod": " ", "raw": "1b4f70", "type": "key" }, { "key": "Numpad1", "keyId": "Numpad1.", "mod": " ", "raw": "1b4f71", "type": "key" }, { "key": "Numpad2", "keyId": "Numpad2.", "mod": " ", "raw": "1b4f72", "type": "key" }, { "key": "Numpad3", "keyId": "Numpad3.", "mod": " ", "raw": "1b4f73", "type": "key" }, { "key": "Numpad4", "keyId": "Numpad4.", "mod": " ", "raw": "1b4f74", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.", "mod": " ", "raw": "1b4f75", "type": "key" }, { "key": "Numpad6", "keyId": "Numpad6.", "mod": " ", "raw": "1b4f76", "type": "key" }, { "key": "Numpad7", "keyId": "Numpad7.", "mod": " ", "raw": "1b4f77", "type": "key" }, { "key": "Numpad8", "keyId": "Numpad8.", "mod": " ", "raw": "1b4f78", "type": "key" }, { "key": "Numpad9", "keyId": "Numpad9.", "mod": " ", "raw": "1b4f79", "type": "key" }, { "chars": "P", "keyId": "P.A", "mod": " A ", "raw": "1b50", "type": "char" }, { "chars": "Q", "keyId": "Q.A", "mod": " A ", "raw": "1b51", "type": "char" }, { "chars": "R", "keyId": "R.A", "mod": " A ", "raw": "1b52", "type": "char" }, { "chars": "S", "keyId": "S.A", "mod": " A ", "raw": "1b53", "type": "char" }, { "chars": "T", "keyId": "T.A", "mod": " A ", "raw": "1b54", "type": "char" }, { "chars": "U", "keyId": "U.A", "mod": " A ", "raw": "1b55", "type": "char" }, { "chars": "V", "keyId": "V.A", "mod": " A ", "raw": "1b56", "type": "char" }, { "chars": "W", "keyId": "W.A", "mod": " A ", "raw": "1b57", "type": "char" }, { "chars": "X", "keyId": "X.A", "mod": " A ", "raw": "1b58", "type": "char" }, { "chars": "Y", "keyId": "Y.A", "mod": " A ", "raw": "1b59", "type": "char" }, { "chars": "Z", "keyId": "Z.A", "mod": " A ", "raw": "1b5a", "type": "char" }, { "chars": "d", "keyId": "d.A", "mod": " A ", "raw": "1b5b3130303b3375", "type": "char" }, { "chars": "d", "keyId": "d.C", "mod": " C", "raw": "1b5b3130303b3575", "type": "char" }, { "chars": "d", "keyId": "d.AC", "mod": " AC", "raw": "1b5b3130303b3775", "type": "char" }, { "chars": "e", "keyId": "e.A", "mod": " A ", "raw": "1b5b3130313b3375", "type": "char" }, { "chars": "e", "keyId": "e.C", "mod": " C", "raw": "1b5b3130313b3575", "type": "char" }, { "chars": "e", "keyId": "e.AC", "mod": " AC", "raw": "1b5b3130313b3775", "type": "char" }, { "chars": "f", "keyId": "f.A", "mod": " A ", "raw": "1b5b3130323b3375", "type": "char" }, { "chars": "f", "keyId": "f.C", "mod": " C", "raw": "1b5b3130323b3575", "type": "char" }, { "chars": "f", "keyId": "f.AC", "mod": " AC", "raw": "1b5b3130323b3775", "type": "char" }, { "chars": "g", "keyId": "g.A", "mod": " A ", "raw": "1b5b3130333b3375", "type": "char" }, { "chars": "g", "keyId": "g.C", "mod": " C", "raw": "1b5b3130333b3575", "type": "char" }, { "chars": "g", "keyId": "g.AC", "mod": " AC", "raw": "1b5b3130333b3775", "type": "char" }, { "chars": "h", "keyId": "h.A", "mod": " A ", "raw": "1b5b3130343b3375", "type": "char" }, { "chars": "h", "keyId": "h.C", "mod": " C", "raw": "1b5b3130343b3575", "type": "char" }, { "chars": "h", "keyId": "h.AC", "mod": " AC", "raw": "1b5b3130343b3775", "type": "char" }, { "chars": "i", "keyId": "i.A", "mod": " A ", "raw": "1b5b3130353b3375", "type": "char" }, { "chars": "i", "keyId": "i.C", "mod": " C", "raw": "1b5b3130353b3575", "type": "char" }, { "chars": "i", "keyId": "i.AC", "mod": " AC", "raw": "1b5b3130353b3775", "type": "char" }, { "chars": "j", "keyId": "j.A", "mod": " A ", "raw": "1b5b3130363b3375", "type": "char" }, { "chars": "j", "keyId": "j.C", "mod": " C", "raw": "1b5b3130363b3575", "type": "char" }, { "chars": "j", "keyId": "j.AC", "mod": " AC", "raw": "1b5b3130363b3775", "type": "char" }, { "chars": "k", "keyId": "k.A", "mod": " A ", "raw": "1b5b3130373b3375", "type": "char" }, { "chars": "k", "keyId": "k.C", "mod": " C", "raw": "1b5b3130373b3575", "type": "char" }, { "chars": "k", "keyId": "k.AC", "mod": " AC", "raw": "1b5b3130373b3775", "type": "char" }, { "chars": "l", "keyId": "l.A", "mod": " A ", "raw": "1b5b3130383b3375", "type": "char" }, { "chars": "l", "keyId": "l.C", "mod": " C", "raw": "1b5b3130383b3575", "type": "char" }, { "chars": "l", "keyId": "l.AC", "mod": " AC", "raw": "1b5b3130383b3775", "type": "char" }, { "chars": "m", "keyId": "m.A", "mod": " A ", "raw": "1b5b3130393b3375", "type": "char" }, { "chars": "m", "keyId": "m.C", "mod": " C", "raw": "1b5b3130393b3575", "type": "char" }, { "chars": "m", "keyId": "m.AC", "mod": " AC", "raw": "1b5b3130393b3775", "type": "char" }, { "chars": "n", "keyId": "n.A", "mod": " A ", "raw": "1b5b3131303b3375", "type": "char" }, { "chars": "n", "keyId": "n.C", "mod": " C", "raw": "1b5b3131303b3575", "type": "char" }, { "chars": "n", "keyId": "n.AC", "mod": " AC", "raw": "1b5b3131303b3775", "type": "char" }, { "chars": "o", "keyId": "o.A", "mod": " A ", "raw": "1b5b3131313b3375", "type": "char" }, { "chars": "o", "keyId": "o.C", "mod": " C", "raw": "1b5b3131313b3575", "type": "char" }, { "chars": "o", "keyId": "o.AC", "mod": " AC", "raw": "1b5b3131313b3775", "type": "char" }, { "chars": "p", "keyId": "p.A", "mod": " A ", "raw": "1b5b3131323b3375", "type": "char" }, { "chars": "p", "keyId": "p.C", "mod": " C", "raw": "1b5b3131323b3575", "type": "char" }, { "chars": "p", "keyId": "p.AC", "mod": " AC", "raw": "1b5b3131323b3775", "type": "char" }, { "chars": "q", "keyId": "q.A", "mod": " A ", "raw": "1b5b3131333b3375", "type": "char" }, { "chars": "q", "keyId": "q.C", "mod": " C", "raw": "1b5b3131333b3575", "type": "char" }, { "chars": "q", "keyId": "q.AC", "mod": " AC", "raw": "1b5b3131333b3775", "type": "char" }, { "chars": "r", "keyId": "r.A", "mod": " A ", "raw": "1b5b3131343b3375", "type": "char" }, { "chars": "r", "keyId": "r.C", "mod": " C", "raw": "1b5b3131343b3575", "type": "char" }, { "chars": "r", "keyId": "r.AC", "mod": " AC", "raw": "1b5b3131343b3775", "type": "char" }, { "chars": "s", "keyId": "s.A", "mod": " A ", "raw": "1b5b3131353b3375", "type": "char" }, { "chars": "s", "keyId": "s.C", "mod": " C", "raw": "1b5b3131353b3575", "type": "char" }, { "chars": "s", "keyId": "s.AC", "mod": " AC", "raw": "1b5b3131353b3775", "type": "char" }, { "chars": "t", "keyId": "t.A", "mod": " A ", "raw": "1b5b3131363b3375", "type": "char" }, { "chars": "t", "keyId": "t.C", "mod": " C", "raw": "1b5b3131363b3575", "type": "char" }, { "chars": "t", "keyId": "t.AC", "mod": " AC", "raw": "1b5b3131363b3775", "type": "char" }, { "chars": "u", "keyId": "u.A", "mod": " A ", "raw": "1b5b3131373b3375", "type": "char" }, { "chars": "u", "keyId": "u.C", "mod": " C", "raw": "1b5b3131373b3575", "type": "char" }, { "chars": "u", "keyId": "u.AC", "mod": " AC", "raw": "1b5b3131373b3775", "type": "char" }, { "chars": "v", "keyId": "v.A", "mod": " A ", "raw": "1b5b3131383b3375", "type": "char" }, { "chars": "v", "keyId": "v.C", "mod": " C", "raw": "1b5b3131383b3575", "type": "char" }, { "chars": "v", "keyId": "v.AC", "mod": " AC", "raw": "1b5b3131383b3775", "type": "char" }, { "chars": "w", "keyId": "w.A", "mod": " A ", "raw": "1b5b3131393b3375", "type": "char" }, { "chars": "w", "keyId": "w.C", "mod": " C", "raw": "1b5b3131393b3575", "type": "char" }, { "chars": "w", "keyId": "w.AC", "mod": " AC", "raw": "1b5b3131393b3775", "type": "char" }, { "key": "F1", "keyId": "F1.C", "mod": " C", "raw": "1b5b31315e", "type": "key" }, { "key": "F1", "keyId": "F1.", "mod": " ", "raw": "1b5b31317e", "type": "key" }, { "chars": "x", "keyId": "x.A", "mod": " A ", "raw": "1b5b3132303b3375", "type": "char" }, { "chars": "x", "keyId": "x.C", "mod": " C", "raw": "1b5b3132303b3575", "type": "char" }, { "chars": "x", "keyId": "x.AC", "mod": " AC", "raw": "1b5b3132303b3775", "type": "char" }, { "chars": "y", "keyId": "y.A", "mod": " A ", "raw": "1b5b3132313b3375", "type": "char" }, { "chars": "y", "keyId": "y.C", "mod": " C", "raw": "1b5b3132313b3575", "type": "char" }, { "chars": "y", "keyId": "y.AC", "mod": " AC", "raw": "1b5b3132313b3775", "type": "char" }, { "chars": "z", "keyId": "z.A", "mod": " A ", "raw": "1b5b3132323b3375", "type": "char" }, { "chars": "z", "keyId": "z.AC", "mod": " AC", "raw": "1b5b3132323b3775", "type": "char" }, { "key": "Backspace", "keyId": "Backspace.S", "mod": "S ", "raw": "1b5b3132373b3275", "type": "key" }, { "key": "Backspace", "keyId": "Backspace.A", "mod": " A ", "raw": "1b5b3132373b3375", "type": "key" }, { "key": "Backspace", "keyId": "Backspace.SA", "mod": "SA ", "raw": "1b5b3132373b3475", "type": "key" }, { "key": "F2", "keyId": "F2.C", "mod": " C", "raw": "1b5b31325e", "type": "key" }, { "key": "F2", "keyId": "F2.", "mod": " ", "raw": "1b5b31327e", "type": "key" }, { "key": "Enter", "keyId": "Enter.S", "mod": "S ", "raw": "1b5b31333b3275", "type": "key" }, { "key": "Enter", "keyId": "Enter.SA", "mod": "SA ", "raw": "1b5b31333b3475", "type": "key" }, { "key": "Enter", "keyId": "Enter.C", "mod": " C", "raw": "1b5b31333b3575", "type": "key" }, { "key": "Enter", "keyId": "Enter.SC", "mod": "S C", "raw": "1b5b31333b3675", "type": "key" }, { "key": "Enter", "keyId": "Enter.AC", "mod": " AC", "raw": "1b5b31333b3775", "type": "key" }, { "key": "Enter", "keyId": "Enter.SAC", "mod": "SAC", "raw": "1b5b31333b3875", "type": "key" }, { "key": "F3", "keyId": "F3.C", "mod": " C", "raw": "1b5b31335e", "type": "key" }, { "key": "F3", "keyId": "F3.", "mod": " ", "raw": "1b5b31337e", "type": "key" }, { "key": "F4", "keyId": "F4.C", "mod": " C", "raw": "1b5b31345e", "type": "key" }, { "key": "F4", "keyId": "F4.", "mod": " ", "raw": "1b5b31347e", "type": "key" }, { "key": "F5", "keyId": "F5.S", "mod": "S ", "raw": "1b5b31353b327e", "type": "key" }, { "key": "F5", "keyId": "F5.A", "mod": " A ", "raw": "1b5b31353b337e", "type": "key" }, { "key": "F5", "keyId": "F5.SA", "mod": "SA ", "raw": "1b5b31353b347e", "type": "key" }, { "key": "F5", "keyId": "F5.C", "mod": " C", "raw": "1b5b31353b357e", "type": "key" }, { "key": "F5", "keyId": "F5.SC", "mod": "S C", "raw": "1b5b31353b367e", "type": "key" }, { "key": "F5", "keyId": "F5.SAC", "mod": "SAC", "raw": "1b5b31353b387e", "type": "key" }, { "key": "F5", "keyId": "F5.C", "mod": " C", "raw": "1b5b31355e", "type": "key" }, { "key": "F5", "keyId": "F5.", "mod": " ", "raw": "1b5b31357e", "type": "key" }, { "chars": "\u00a7", "keyId": "\u00a7.SA", "mod": "SA ", "raw": "1b5b3136373b3475", "type": "char" }, { "chars": "\u00a7", "keyId": "\u00a7.SC", "mod": "S C", "raw": "1b5b3136373b3675", "type": "char" }, { "chars": "\u00a7", "keyId": "\u00a7.SAC", "mod": "SAC", "raw": "1b5b3136373b3875", "type": "char" }, { "chars": "\u00b0", "keyId": "\u00b0.SA", "mod": "SA ", "raw": "1b5b3137363b3475", "type": "char" }, { "chars": "\u00b0", "keyId": "\u00b0.SC", "mod": "S C", "raw": "1b5b3137363b3675", "type": "char" }, { "chars": "\u00b0", "keyId": "\u00b0.SAC", "mod": "SAC", "raw": "1b5b3137363b3875", "type": "char" }, { "key": "F6", "keyId": "F6.S", "mod": "S ", "raw": "1b5b31373b327e", "type": "key" }, { "key": "F6", "keyId": "F6.A", "mod": " A ", "raw": "1b5b31373b337e", "type": "key" }, { "key": "F6", "keyId": "F6.SA", "mod": "SA ", "raw": "1b5b31373b347e", "type": "key" }, { "key": "F6", "keyId": "F6.C", "mod": " C", "raw": "1b5b31373b357e", "type": "key" }, { "key": "F6", "keyId": "F6.SC", "mod": "S C", "raw": "1b5b31373b367e", "type": "key" }, { "key": "F6", "keyId": "F6.SAC", "mod": "SAC", "raw": "1b5b31373b387e", "type": "key" }, { "key": "F6", "keyId": "F6.C", "mod": " C", "raw": "1b5b31375e", "type": "key" }, { "key": "F6", "keyId": "F6.", "mod": " ", "raw": "1b5b31377e", "type": "key" }, { "chars": "\u00b4", "keyId": "\u00b4.A", "mod": " A ", "raw": "1b5b3138303b3375", "type": "char" }, { "chars": "\u00b4", "keyId": "\u00b4.C", "mod": " C", "raw": "1b5b3138303b3575", "type": "char" }, { "chars": "\u00b4", "keyId": "\u00b4.AC", "mod": " AC", "raw": "1b5b3138303b3775", "type": "char" }, { "key": "F7", "keyId": "F7.S", "mod": "S ", "raw": "1b5b31383b327e", "type": "key" }, { "key": "F7", "keyId": "F7.A", "mod": " A ", "raw": "1b5b31383b337e", "type": "key" }, { "key": "F7", "keyId": "F7.SA", "mod": "SA ", "raw": "1b5b31383b347e", "type": "key" }, { "key": "F7", "keyId": "F7.C", "mod": " C", "raw": "1b5b31383b357e", "type": "key" }, { "key": "F7", "keyId": "F7.SC", "mod": "S C", "raw": "1b5b31383b367e", "type": "key" }, { "key": "F7", "keyId": "F7.SAC", "mod": "SAC", "raw": "1b5b31383b387e", "type": "key" }, { "key": "F7", "keyId": "F7.C", "mod": " C", "raw": "1b5b31385e", "type": "key" }, { "key": "F7", "keyId": "F7.", "mod": " ", "raw": "1b5b31387e", "type": "key" }, { "chars": "\u00c4", "keyId": "\u00c4.SA", "mod": "SA ", "raw": "1b5b3139363b3475", "type": "char" }, { "chars": "\u00c4", "keyId": "\u00c4.SC", "mod": "S C", "raw": "1b5b3139363b3675", "type": "char" }, { "chars": "\u00c4", "keyId": "\u00c4.SAC", "mod": "SAC", "raw": "1b5b3139363b3875", "type": "char" }, { "key": "F8", "keyId": "F8.S", "mod": "S ", "raw": "1b5b31393b327e", "type": "key" }, { "key": "F8", "keyId": "F8.A", "mod": " A ", "raw": "1b5b31393b337e", "type": "key" }, { "key": "F8", "keyId": "F8.SA", "mod": "SA ", "raw": "1b5b31393b347e", "type": "key" }, { "key": "F8", "keyId": "F8.C", "mod": " C", "raw": "1b5b31393b357e", "type": "key" }, { "key": "F8", "keyId": "F8.SC", "mod": "S C", "raw": "1b5b31393b367e", "type": "key" }, { "key": "F8", "keyId": "F8.SAC", "mod": "SAC", "raw": "1b5b31393b387e", "type": "key" }, { "key": "F8", "keyId": "F8.C", "mod": " C", "raw": "1b5b31395e", "type": "key" }, { "key": "F8", "keyId": "F8.", "mod": " ", "raw": "1b5b31397e", "type": "key" }, { "key": "ArrowUp", "keyId": "ArrowUp.S", "mod": "S ", "raw": "1b5b313b3241", "type": "key" }, { "key": "ArrowDown", "keyId": "ArrowDown.S", "mod": "S ", "raw": "1b5b313b3242", "type": "key" }, { "key": "ArrowRight", "keyId": "ArrowRight.S", "mod": "S ", "raw": "1b5b313b3243", "type": "key" }, { "key": "ArrowLeft", "keyId": "ArrowLeft.S", "mod": "S ", "raw": "1b5b313b3244", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.S", "mod": "S ", "raw": "1b5b313b3245", "type": "key" }, { "key": "End", "keyId": "End.S", "mod": "S ", "raw": "1b5b313b3246", "type": "key" }, { "key": "Home", "keyId": "Home.S", "mod": "S ", "raw": "1b5b313b3248", "type": "key" }, { "key": "F1", "keyId": "F1.S", "mod": "S ", "raw": "1b5b313b3250", "type": "key" }, { "key": "F2", "keyId": "F2.S", "mod": "S ", "raw": "1b5b313b3251", "type": "key" }, { "key": "F3", "keyId": "F3.S", "mod": "S ", "raw": "1b5b313b3252", "type": "key" }, { "key": "F4", "keyId": "F4.S", "mod": "S ", "raw": "1b5b313b3253", "type": "key" }, { "key": "ArrowUp", "keyId": "ArrowUp.A", "mod": " A ", "raw": "1b5b313b3341", "type": "key" }, { "key": "ArrowDown", "keyId": "ArrowDown.A", "mod": " A ", "raw": "1b5b313b3342", "type": "key" }, { "key": "ArrowRight", "keyId": "ArrowRight.A", "mod": " A ", "raw": "1b5b313b3343", "type": "key" }, { "key": "ArrowLeft", "keyId": "ArrowLeft.A", "mod": " A ", "raw": "1b5b313b3344", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.A", "mod": " A ", "raw": "1b5b313b3345", "type": "key" }, { "key": "End", "keyId": "End.A", "mod": " A ", "raw": "1b5b313b3346", "type": "key" }, { "key": "Home", "keyId": "Home.A", "mod": " A ", "raw": "1b5b313b3348", "type": "key" }, { "key": "F1", "keyId": "F1.A", "mod": " A ", "raw": "1b5b313b3350", "type": "key" }, { "key": "F2", "keyId": "F2.A", "mod": " A ", "raw": "1b5b313b3351", "type": "key" }, { "key": "F3", "keyId": "F3.A", "mod": " A ", "raw": "1b5b313b3352", "type": "key" }, { "key": "F4", "keyId": "F4.A", "mod": " A ", "raw": "1b5b313b3353", "type": "key" }, { "key": "ArrowUp", "keyId": "ArrowUp.SA", "mod": "SA ", "raw": "1b5b313b3441", "type": "key" }, { "key": "ArrowDown", "keyId": "ArrowDown.SA", "mod": "SA ", "raw": "1b5b313b3442", "type": "key" }, { "key": "ArrowRight", "keyId": "ArrowRight.SA", "mod": "SA ", "raw": "1b5b313b3443", "type": "key" }, { "key": "ArrowLeft", "keyId": "ArrowLeft.SA", "mod": "SA ", "raw": "1b5b313b3444", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.SA", "mod": "SA ", "raw": "1b5b313b3445", "type": "key" }, { "key": "End", "keyId": "End.SA", "mod": "SA ", "raw": "1b5b313b3446", "type": "key" }, { "key": "Home", "keyId": "Home.SA", "mod": "SA ", "raw": "1b5b313b3448", "type": "key" }, { "key": "F1", "keyId": "F1.SA", "mod": "SA ", "raw": "1b5b313b3450", "type": "key" }, { "key": "F2", "keyId": "F2.SA", "mod": "SA ", "raw": "1b5b313b3451", "type": "key" }, { "key": "F3", "keyId": "F3.SA", "mod": "SA ", "raw": "1b5b313b3452", "type": "key" }, { "key": "F4", "keyId": "F4.SA", "mod": "SA ", "raw": "1b5b313b3453", "type": "key" }, { "key": "ArrowUp", "keyId": "ArrowUp.C", "mod": " C", "raw": "1b5b313b3541", "type": "key" }, { "key": "ArrowDown", "keyId": "ArrowDown.C", "mod": " C", "raw": "1b5b313b3542", "type": "key" }, { "key": "ArrowRight", "keyId": "ArrowRight.C", "mod": " C", "raw": "1b5b313b3543", "type": "key" }, { "key": "ArrowLeft", "keyId": "ArrowLeft.C", "mod": " C", "raw": "1b5b313b3544", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.C", "mod": " C", "raw": "1b5b313b3545", "type": "key" }, { "key": "End", "keyId": "End.C", "mod": " C", "raw": "1b5b313b3546", "type": "key" }, { "key": "Home", "keyId": "Home.C", "mod": " C", "raw": "1b5b313b3548", "type": "key" }, { "key": "F1", "keyId": "F1.C", "mod": " C", "raw": "1b5b313b3550", "type": "key" }, { "key": "F2", "keyId": "F2.C", "mod": " C", "raw": "1b5b313b3551", "type": "key" }, { "key": "F3", "keyId": "F3.C", "mod": " C", "raw": "1b5b313b3552", "type": "key" }, { "key": "F4", "keyId": "F4.C", "mod": " C", "raw": "1b5b313b3553", "type": "key" }, { "key": "ArrowUp", "keyId": "ArrowUp.SC", "mod": "S C", "raw": "1b5b313b3641", "type": "key" }, { "key": "ArrowDown", "keyId": "ArrowDown.SC", "mod": "S C", "raw": "1b5b313b3642", "type": "key" }, { "key": "ArrowRight", "keyId": "ArrowRight.SC", "mod": "S C", "raw": "1b5b313b3643", "type": "key" }, { "key": "ArrowLeft", "keyId": "ArrowLeft.SC", "mod": "S C", "raw": "1b5b313b3644", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.SC", "mod": "S C", "raw": "1b5b313b3645", "type": "key" }, { "key": "End", "keyId": "End.SC", "mod": "S C", "raw": "1b5b313b3646", "type": "key" }, { "key": "Home", "keyId": "Home.SC", "mod": "S C", "raw": "1b5b313b3648", "type": "key" }, { "key": "F1", "keyId": "F1.SC", "mod": "S C", "raw": "1b5b313b3650", "type": "key" }, { "key": "F2", "keyId": "F2.SC", "mod": "S C", "raw": "1b5b313b3651", "type": "key" }, { "key": "F3", "keyId": "F3.SC", "mod": "S C", "raw": "1b5b313b3652", "type": "key" }, { "key": "F4", "keyId": "F4.SC", "mod": "S C", "raw": "1b5b313b3653", "type": "key" }, { "key": "ArrowUp", "keyId": "ArrowUp.AC", "mod": " AC", "raw": "1b5b313b3741", "type": "key" }, { "key": "ArrowDown", "keyId": "ArrowDown.AC", "mod": " AC", "raw": "1b5b313b3742", "type": "key" }, { "key": "ArrowRight", "keyId": "ArrowRight.AC", "mod": " AC", "raw": "1b5b313b3743", "type": "key" }, { "key": "ArrowLeft", "keyId": "ArrowLeft.AC", "mod": " AC", "raw": "1b5b313b3744", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.AC", "mod": " AC", "raw": "1b5b313b3745", "type": "key" }, { "key": "End", "keyId": "End.AC", "mod": " AC", "raw": "1b5b313b3746", "type": "key" }, { "key": "Home", "keyId": "Home.AC", "mod": " AC", "raw": "1b5b313b3748", "type": "key" }, { "key": "ArrowUp", "keyId": "ArrowUp.SAC", "mod": "SAC", "raw": "1b5b313b3841", "type": "key" }, { "key": "ArrowDown", "keyId": "ArrowDown.SAC", "mod": "SAC", "raw": "1b5b313b3842", "type": "key" }, { "key": "ArrowRight", "keyId": "ArrowRight.SAC", "mod": "SAC", "raw": "1b5b313b3843", "type": "key" }, { "key": "ArrowLeft", "keyId": "ArrowLeft.SAC", "mod": "SAC", "raw": "1b5b313b3844", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.SAC", "mod": "SAC", "raw": "1b5b313b3845", "type": "key" }, { "key": "End", "keyId": "End.SAC", "mod": "SAC", "raw": "1b5b313b3846", "type": "key" }, { "key": "Home", "keyId": "Home.SAC", "mod": "SAC", "raw": "1b5b313b3848", "type": "key" }, { "key": "F1", "keyId": "F1.SAC", "mod": "SAC", "raw": "1b5b313b3850", "type": "key" }, { "key": "F2", "keyId": "F2.SAC", "mod": "SAC", "raw": "1b5b313b3851", "type": "key" }, { "key": "F3", "keyId": "F3.SAC", "mod": "SAC", "raw": "1b5b313b3852", "type": "key" }, { "key": "F4", "keyId": "F4.SAC", "mod": "SAC", "raw": "1b5b313b3853", "type": "key" }, { "key": "Home", "keyId": "Home.", "mod": " ", "raw": "1b5b317e", "type": "key" }, { "key": "Insert", "keyId": "Insert.S", "mod": "S ", "raw": "1b5b3224", "type": "key" }, { "key": "F9", "keyId": "F9.S", "mod": "S ", "raw": "1b5b32303b327e", "type": "key" }, { "key": "F9", "keyId": "F9.A", "mod": " A ", "raw": "1b5b32303b337e", "type": "key" }, { "key": "F9", "keyId": "F9.SA", "mod": "SA ", "raw": "1b5b32303b347e", "type": "key" }, { "key": "F9", "keyId": "F9.C", "mod": " C", "raw": "1b5b32303b357e", "type": "key" }, { "key": "F9", "keyId": "F9.SC", "mod": "S C", "raw": "1b5b32303b367e", "type": "key" }, { "key": "F9", "keyId": "F9.SAC", "mod": "SAC", "raw": "1b5b32303b387e", "type": "key" }, { "key": "F9", "keyId": "F9.C", "mod": " C", "raw": "1b5b32305e", "type": "key" }, { "key": "F9", "keyId": "F9.", "mod": " ", "raw": "1b5b32307e", "type": "key" }, { "chars": "\u00d6", "keyId": "\u00d6.SA", "mod": "SA ", "raw": "1b5b3231343b3475", "type": "char" }, { "chars": "\u00d6", "keyId": "\u00d6.SC", "mod": "S C", "raw": "1b5b3231343b3675", "type": "char" }, { "chars": "\u00d6", "keyId": "\u00d6.SAC", "mod": "SAC", "raw": "1b5b3231343b3875", "type": "char" }, { "key": "F10", "keyId": "F10.S", "mod": "S ", "raw": "1b5b32313b327e", "type": "key" }, { "key": "F10", "keyId": "F10.A", "mod": " A ", "raw": "1b5b32313b337e", "type": "key" }, { "key": "F10", "keyId": "F10.SA", "mod": "SA ", "raw": "1b5b32313b347e", "type": "key" }, { "key": "F10", "keyId": "F10.C", "mod": " C", "raw": "1b5b32313b357e", "type": "key" }, { "key": "F10", "keyId": "F10.SC", "mod": "S C", "raw": "1b5b32313b367e", "type": "key" }, { "key": "F10", "keyId": "F10.SAC", "mod": "SAC", "raw": "1b5b32313b387e", "type": "key" }, { "key": "F10", "keyId": "F10.C", "mod": " C", "raw": "1b5b32315e", "type": "key" }, { "key": "F10", "keyId": "F10.", "mod": " ", "raw": "1b5b32317e", "type": "key" }, { "chars": "\u00dc", "keyId": "\u00dc.SA", "mod": "SA ", "raw": "1b5b3232303b3475", "type": "char" }, { "chars": "\u00dc", "keyId": "\u00dc.SC", "mod": "S C", "raw": "1b5b3232303b3675", "type": "char" }, { "chars": "\u00dc", "keyId": "\u00dc.SAC", "mod": "SAC", "raw": "1b5b3232303b3875", "type": "char" }, { "chars": "\u00df", "keyId": "\u00df.A", "mod": " A ", "raw": "1b5b3232333b3375", "type": "char" }, { "chars": "\u00df", "keyId": "\u00df.C", "mod": " C", "raw": "1b5b3232333b3575", "type": "char" }, { "chars": "\u00df", "keyId": "\u00df.AC", "mod": " AC", "raw": "1b5b3232333b3775", "type": "char" }, { "chars": "\u00e4", "keyId": "\u00e4.A", "mod": " A ", "raw": "1b5b3232383b3375", "type": "char" }, { "chars": "\u00e4", "keyId": "\u00e4.C", "mod": " C", "raw": "1b5b3232383b3575", "type": "char" }, { "chars": "\u00e4", "keyId": "\u00e4.AC", "mod": " AC", "raw": "1b5b3232383b3775", "type": "char" }, { "key": "F11", "keyId": "F11.S", "mod": "S ", "raw": "1b5b323324", "type": "key" }, { "key": "F11", "keyId": "F11.S", "mod": "S ", "raw": "1b5b32333b327e", "type": "key" }, { "key": "F11", "keyId": "F11.A", "mod": " A ", "raw": "1b5b32333b337e", "type": "key" }, { "key": "F11", "keyId": "F11.SA", "mod": "SA ", "raw": "1b5b32333b347e", "type": "key" }, { "key": "F11", "keyId": "F11.C", "mod": " C", "raw": "1b5b32333b357e", "type": "key" }, { "key": "F11", "keyId": "F11.SC", "mod": "S C", "raw": "1b5b32333b367e", "type": "key" }, { "key": "F11", "keyId": "F11.SAC", "mod": "SAC", "raw": "1b5b32333b387e", "type": "key" }, { "key": "F11", "keyId": "F11.SC", "mod": "S C", "raw": "1b5b323340", "type": "key" }, { "key": "F11", "keyId": "F11.C", "mod": " C", "raw": "1b5b32335e", "type": "key" }, { "key": "F11", "keyId": "F11.", "mod": " ", "raw": "1b5b32337e", "type": "key" }, { "key": "F12", "keyId": "F12.S", "mod": "S ", "raw": "1b5b323424", "type": "key" }, { "chars": "\u00f6", "keyId": "\u00f6.A", "mod": " A ", "raw": "1b5b3234363b3375", "type": "char" }, { "chars": "\u00f6", "keyId": "\u00f6.C", "mod": " C", "raw": "1b5b3234363b3575", "type": "char" }, { "chars": "\u00f6", "keyId": "\u00f6.AC", "mod": " AC", "raw": "1b5b3234363b3775", "type": "char" }, { "key": "F12", "keyId": "F12.S", "mod": "S ", "raw": "1b5b32343b327e", "type": "key" }, { "key": "F12", "keyId": "F12.A", "mod": " A ", "raw": "1b5b32343b337e", "type": "key" }, { "key": "F12", "keyId": "F12.SA", "mod": "SA ", "raw": "1b5b32343b347e", "type": "key" }, { "key": "F12", "keyId": "F12.C", "mod": " C", "raw": "1b5b32343b357e", "type": "key" }, { "key": "F12", "keyId": "F12.SC", "mod": "S C", "raw": "1b5b32343b367e", "type": "key" }, { "key": "F12", "keyId": "F12.SAC", "mod": "SAC", "raw": "1b5b32343b387e", "type": "key" }, { "key": "F12", "keyId": "F12.SC", "mod": "S C", "raw": "1b5b323440", "type": "key" }, { "key": "F12", "keyId": "F12.C", "mod": " C", "raw": "1b5b32345e", "type": "key" }, { "key": "F12", "keyId": "F12.", "mod": " ", "raw": "1b5b32347e", "type": "key" }, { "chars": "\u00fc", "keyId": "\u00fc.A", "mod": " A ", "raw": "1b5b3235323b3375", "type": "char" }, { "chars": "\u00fc", "keyId": "\u00fc.C", "mod": " C", "raw": "1b5b3235323b3575", "type": "char" }, { "chars": "\u00fc", "keyId": "\u00fc.AC", "mod": " AC", "raw": "1b5b3235323b3775", "type": "char" }, { "key": "F1", "keyId": "F1.SC", "mod": "S C", "raw": "1b5b32355e", "type": "key" }, { "key": "F1", "keyId": "F1.S", "mod": "S ", "raw": "1b5b32357e", "type": "key" }, { "key": "F2", "keyId": "F2.SC", "mod": "S C", "raw": "1b5b32365e", "type": "key" }, { "key": "F2", "keyId": "F2.S", "mod": "S ", "raw": "1b5b32367e", "type": "key" }, { "key": "Backspace", "keyId": "Backspace.S", "mod": "S ", "raw": "1b5b32373b323b3132377e", "type": "key" }, { "key": "Enter", "keyId": "Enter.S", "mod": "S ", "raw": "1b5b32373b323b31337e", "type": "key" }, { "key": "Space", "keyId": "Space.S", "mod": "S ", "raw": "1b5b32373b323b33327e", "type": "key" }, { "chars": "A", "keyId": "A.S", "mod": "S ", "raw": "1b5b32373b323b36357e", "type": "char" }, { "chars": "B", "keyId": "B.S", "mod": "S ", "raw": "1b5b32373b323b36367e", "type": "char" }, { "chars": "C", "keyId": "C.S", "mod": "S ", "raw": "1b5b32373b323b36377e", "type": "char" }, { "chars": "D", "keyId": "D.S", "mod": "S ", "raw": "1b5b32373b323b36387e", "type": "char" }, { "chars": "E", "keyId": "E.S", "mod": "S ", "raw": "1b5b32373b323b36397e", "type": "char" }, { "chars": "F", "keyId": "F.S", "mod": "S ", "raw": "1b5b32373b323b37307e", "type": "char" }, { "chars": "G", "keyId": "G.S", "mod": "S ", "raw": "1b5b32373b323b37317e", "type": "char" }, { "chars": "H", "keyId": "H.S", "mod": "S ", "raw": "1b5b32373b323b37327e", "type": "char" }, { "chars": "I", "keyId": "I.S", "mod": "S ", "raw": "1b5b32373b323b37337e", "type": "char" }, { "chars": "J", "keyId": "J.S", "mod": "S ", "raw": "1b5b32373b323b37347e", "type": "char" }, { "chars": "K", "keyId": "K.S", "mod": "S ", "raw": "1b5b32373b323b37357e", "type": "char" }, { "chars": "L", "keyId": "L.S", "mod": "S ", "raw": "1b5b32373b323b37367e", "type": "char" }, { "chars": "M", "keyId": "M.S", "mod": "S ", "raw": "1b5b32373b323b37377e", "type": "char" }, { "chars": "N", "keyId": "N.S", "mod": "S ", "raw": "1b5b32373b323b37387e", "type": "char" }, { "chars": "O", "keyId": "O.S", "mod": "S ", "raw": "1b5b32373b323b37397e", "type": "char" }, { "chars": "P", "keyId": "P.S", "mod": "S ", "raw": "1b5b32373b323b38307e", "type": "char" }, { "chars": "Q", "keyId": "Q.S", "mod": "S ", "raw": "1b5b32373b323b38317e", "type": "char" }, { "chars": "R", "keyId": "R.S", "mod": "S ", "raw": "1b5b32373b323b38327e", "type": "char" }, { "chars": "S", "keyId": "S.S", "mod": "S ", "raw": "1b5b32373b323b38337e", "type": "char" }, { "chars": "T", "keyId": "T.S", "mod": "S ", "raw": "1b5b32373b323b38347e", "type": "char" }, { "chars": "U", "keyId": "U.S", "mod": "S ", "raw": "1b5b32373b323b38357e", "type": "char" }, { "chars": "V", "keyId": "V.S", "mod": "S ", "raw": "1b5b32373b323b38367e", "type": "char" }, { "chars": "W", "keyId": "W.S", "mod": "S ", "raw": "1b5b32373b323b38377e", "type": "char" }, { "chars": "X", "keyId": "X.S", "mod": "S ", "raw": "1b5b32373b323b38387e", "type": "char" }, { "chars": "Y", "keyId": "Y.S", "mod": "S ", "raw": "1b5b32373b323b38397e", "type": "char" }, { "chars": "Z", "keyId": "Z.S", "mod": "S ", "raw": "1b5b32373b323b39307e", "type": "char" }, { "chars": "_", "keyId": "_.S", "mod": "S ", "raw": "1b5b32373b323b39357e", "type": "char" }, { "chars": "`", "keyId": "`.S", "mod": "S ", "raw": "1b5b32373b323b39367e", "type": "char" }, { "chars": "d", "keyId": "d.A", "mod": " A ", "raw": "1b5b32373b333b3130307e", "type": "char" }, { "chars": "e", "keyId": "e.A", "mod": " A ", "raw": "1b5b32373b333b3130317e", "type": "char" }, { "chars": "f", "keyId": "f.A", "mod": " A ", "raw": "1b5b32373b333b3130327e", "type": "char" }, { "chars": "g", "keyId": "g.A", "mod": " A ", "raw": "1b5b32373b333b3130337e", "type": "char" }, { "chars": "h", "keyId": "h.A", "mod": " A ", "raw": "1b5b32373b333b3130347e", "type": "char" }, { "chars": "i", "keyId": "i.A", "mod": " A ", "raw": "1b5b32373b333b3130357e", "type": "char" }, { "chars": "j", "keyId": "j.A", "mod": " A ", "raw": "1b5b32373b333b3130367e", "type": "char" }, { "chars": "k", "keyId": "k.A", "mod": " A ", "raw": "1b5b32373b333b3130377e", "type": "char" }, { "chars": "l", "keyId": "l.A", "mod": " A ", "raw": "1b5b32373b333b3130387e", "type": "char" }, { "chars": "m", "keyId": "m.A", "mod": " A ", "raw": "1b5b32373b333b3130397e", "type": "char" }, { "chars": "n", "keyId": "n.A", "mod": " A ", "raw": "1b5b32373b333b3131307e", "type": "char" }, { "chars": "o", "keyId": "o.A", "mod": " A ", "raw": "1b5b32373b333b3131317e", "type": "char" }, { "chars": "p", "keyId": "p.A", "mod": " A ", "raw": "1b5b32373b333b3131327e", "type": "char" }, { "chars": "q", "keyId": "q.A", "mod": " A ", "raw": "1b5b32373b333b3131337e", "type": "char" }, { "chars": "r", "keyId": "r.A", "mod": " A ", "raw": "1b5b32373b333b3131347e", "type": "char" }, { "chars": "s", "keyId": "s.A", "mod": " A ", "raw": "1b5b32373b333b3131357e", "type": "char" }, { "chars": "t", "keyId": "t.A", "mod": " A ", "raw": "1b5b32373b333b3131367e", "type": "char" }, { "chars": "u", "keyId": "u.A", "mod": " A ", "raw": "1b5b32373b333b3131377e", "type": "char" }, { "chars": "v", "keyId": "v.A", "mod": " A ", "raw": "1b5b32373b333b3131387e", "type": "char" }, { "chars": "w", "keyId": "w.A", "mod": " A ", "raw": "1b5b32373b333b3131397e", "type": "char" }, { "chars": "x", "keyId": "x.A", "mod": " A ", "raw": "1b5b32373b333b3132307e", "type": "char" }, { "chars": "y", "keyId": "y.A", "mod": " A ", "raw": "1b5b32373b333b3132317e", "type": "char" }, { "chars": "z", "keyId": "z.A", "mod": " A ", "raw": "1b5b32373b333b3132327e", "type": "char" }, { "key": "Backspace", "keyId": "Backspace.A", "mod": " A ", "raw": "1b5b32373b333b3132377e", "type": "key" }, { "chars": "\u00b4", "keyId": "\u00b4.A", "mod": " A ", "raw": "1b5b32373b333b3138307e", "type": "char" }, { "chars": "\u00df", "keyId": "\u00df.A", "mod": " A ", "raw": "1b5b32373b333b3232337e", "type": "char" }, { "chars": "\u00e4", "keyId": "\u00e4.A", "mod": " A ", "raw": "1b5b32373b333b3232387e", "type": "char" }, { "chars": "\u00f6", "keyId": "\u00f6.A", "mod": " A ", "raw": "1b5b32373b333b3234367e", "type": "char" }, { "chars": "\u00fc", "keyId": "\u00fc.A", "mod": " A ", "raw": "1b5b32373b333b3235327e", "type": "char" }, { "key": "Escape", "keyId": "Escape.A", "mod": " A ", "raw": "1b5b32373b333b32377e", "type": "key" }, { "key": "Space", "keyId": "Space.A", "mod": " A ", "raw": "1b5b32373b333b33327e", "type": "key" }, { "chars": "#", "keyId": "#.A", "mod": " A ", "raw": "1b5b32373b333b33357e", "type": "char" }, { "chars": "+", "keyId": "+.A", "mod": " A ", "raw": "1b5b32373b333b34337e", "type": "char" }, { "chars": ",", "keyId": ",.A", "mod": " A ", "raw": "1b5b32373b333b34347e", "type": "char" }, { "chars": "-", "keyId": "-.A", "mod": " A ", "raw": "1b5b32373b333b34357e", "type": "char" }, { "chars": ".", "keyId": "..A", "mod": " A ", "raw": "1b5b32373b333b34367e", "type": "char" }, { "chars": "0", "keyId": "0.A", "mod": " A ", "raw": "1b5b32373b333b34387e", "type": "char" }, { "chars": "1", "keyId": "1.A", "mod": " A ", "raw": "1b5b32373b333b34397e", "type": "char" }, { "chars": "2", "keyId": "2.A", "mod": " A ", "raw": "1b5b32373b333b35307e", "type": "char" }, { "chars": "3", "keyId": "3.A", "mod": " A ", "raw": "1b5b32373b333b35317e", "type": "char" }, { "chars": "4", "keyId": "4.A", "mod": " A ", "raw": "1b5b32373b333b35327e", "type": "char" }, { "chars": "5", "keyId": "5.A", "mod": " A ", "raw": "1b5b32373b333b35337e", "type": "char" }, { "chars": "6", "keyId": "6.A", "mod": " A ", "raw": "1b5b32373b333b35347e", "type": "char" }, { "chars": "7", "keyId": "7.A", "mod": " A ", "raw": "1b5b32373b333b35357e", "type": "char" }, { "chars": "8", "keyId": "8.A", "mod": " A ", "raw": "1b5b32373b333b35367e", "type": "char" }, { "chars": "9", "keyId": "9.A", "mod": " A ", "raw": "1b5b32373b333b35377e", "type": "char" }, { "chars": "<", "keyId": "<.A", "mod": " A ", "raw": "1b5b32373b333b36307e", "type": "char" }, { "chars": "^", "keyId": "^.A", "mod": " A ", "raw": "1b5b32373b333b39347e", "type": "char" }, { "chars": "a", "keyId": "a.A", "mod": " A ", "raw": "1b5b32373b333b39377e", "type": "char" }, { "chars": "b", "keyId": "b.A", "mod": " A ", "raw": "1b5b32373b333b39387e", "type": "char" }, { "chars": "c", "keyId": "c.A", "mod": " A ", "raw": "1b5b32373b333b39397e", "type": "char" }, { "key": "Escape", "keyId": "Escape.A", "mod": " A ", "raw": "1b5b32373b3375", "type": "key" }, { "key": "Backspace", "keyId": "Backspace.SA", "mod": "SA ", "raw": "1b5b32373b343b3132377e", "type": "key" }, { "key": "Enter", "keyId": "Enter.SA", "mod": "SA ", "raw": "1b5b32373b343b31337e", "type": "key" }, { "chars": "\u00a7", "keyId": "\u00a7.SA", "mod": "SA ", "raw": "1b5b32373b343b3136377e", "type": "char" }, { "chars": "\u00b0", "keyId": "\u00b0.SA", "mod": "SA ", "raw": "1b5b32373b343b3137367e", "type": "char" }, { "chars": "\u00c4", "keyId": "\u00c4.SA", "mod": "SA ", "raw": "1b5b32373b343b3139367e", "type": "char" }, { "chars": "\u00d6", "keyId": "\u00d6.SA", "mod": "SA ", "raw": "1b5b32373b343b3231347e", "type": "char" }, { "chars": "\u00dc", "keyId": "\u00dc.SA", "mod": "SA ", "raw": "1b5b32373b343b3232307e", "type": "char" }, { "key": "Escape", "keyId": "Escape.SA", "mod": "SA ", "raw": "1b5b32373b343b32377e", "type": "key" }, { "key": "Space", "keyId": "Space.SA", "mod": "SA ", "raw": "1b5b32373b343b33327e", "type": "key" }, { "chars": "!", "keyId": "!.SA", "mod": "SA ", "raw": "1b5b32373b343b33337e", "type": "char" }, { "chars": "\"", "keyId": "\".SA", "mod": "SA ", "raw": "1b5b32373b343b33347e", "type": "char" }, { "chars": "$", "keyId": "$.SA", "mod": "SA ", "raw": "1b5b32373b343b33367e", "type": "char" }, { "chars": "%", "keyId": "%.SA", "mod": "SA ", "raw": "1b5b32373b343b33377e", "type": "char" }, { "chars": "&", "keyId": "&.SA", "mod": "SA ", "raw": "1b5b32373b343b33387e", "type": "char" }, { "chars": "'", "keyId": "'.SA", "mod": "SA ", "raw": "1b5b32373b343b33397e", "type": "char" }, { "chars": "(", "keyId": "(.SA", "mod": "SA ", "raw": "1b5b32373b343b34307e", "type": "char" }, { "chars": ")", "keyId": ").SA", "mod": "SA ", "raw": "1b5b32373b343b34317e", "type": "char" }, { "chars": "*", "keyId": "*.SA", "mod": "SA ", "raw": "1b5b32373b343b34327e", "type": "char" }, { "chars": "/", "keyId": "/.SA", "mod": "SA ", "raw": "1b5b32373b343b34377e", "type": "char" }, { "chars": ":", "keyId": ":.SA", "mod": "SA ", "raw": "1b5b32373b343b35387e", "type": "char" }, { "chars": ";", "keyId": ";.SA", "mod": "SA ", "raw": "1b5b32373b343b35397e", "type": "char" }, { "chars": "=", "keyId": "=.SA", "mod": "SA ", "raw": "1b5b32373b343b36317e", "type": "char" }, { "chars": ">", "keyId": ">.SA", "mod": "SA ", "raw": "1b5b32373b343b36327e", "type": "char" }, { "chars": "?", "keyId": "?.SA", "mod": "SA ", "raw": "1b5b32373b343b36337e", "type": "char" }, { "chars": "A", "keyId": "A.SA", "mod": "SA ", "raw": "1b5b32373b343b36357e", "type": "char" }, { "chars": "B", "keyId": "B.SA", "mod": "SA ", "raw": "1b5b32373b343b36367e", "type": "char" }, { "chars": "C", "keyId": "C.SA", "mod": "SA ", "raw": "1b5b32373b343b36377e", "type": "char" }, { "chars": "D", "keyId": "D.SA", "mod": "SA ", "raw": "1b5b32373b343b36387e", "type": "char" }, { "chars": "E", "keyId": "E.SA", "mod": "SA ", "raw": "1b5b32373b343b36397e", "type": "char" }, { "chars": "F", "keyId": "F.SA", "mod": "SA ", "raw": "1b5b32373b343b37307e", "type": "char" }, { "chars": "G", "keyId": "G.SA", "mod": "SA ", "raw": "1b5b32373b343b37317e", "type": "char" }, { "chars": "H", "keyId": "H.SA", "mod": "SA ", "raw": "1b5b32373b343b37327e", "type": "char" }, { "chars": "I", "keyId": "I.SA", "mod": "SA ", "raw": "1b5b32373b343b37337e", "type": "char" }, { "chars": "J", "keyId": "J.SA", "mod": "SA ", "raw": "1b5b32373b343b37347e", "type": "char" }, { "chars": "K", "keyId": "K.SA", "mod": "SA ", "raw": "1b5b32373b343b37357e", "type": "char" }, { "chars": "L", "keyId": "L.SA", "mod": "SA ", "raw": "1b5b32373b343b37367e", "type": "char" }, { "chars": "M", "keyId": "M.SA", "mod": "SA ", "raw": "1b5b32373b343b37377e", "type": "char" }, { "chars": "N", "keyId": "N.SA", "mod": "SA ", "raw": "1b5b32373b343b37387e", "type": "char" }, { "chars": "O", "keyId": "O.SA", "mod": "SA ", "raw": "1b5b32373b343b37397e", "type": "char" }, { "chars": "P", "keyId": "P.SA", "mod": "SA ", "raw": "1b5b32373b343b38307e", "type": "char" }, { "chars": "Q", "keyId": "Q.SA", "mod": "SA ", "raw": "1b5b32373b343b38317e", "type": "char" }, { "chars": "R", "keyId": "R.SA", "mod": "SA ", "raw": "1b5b32373b343b38327e", "type": "char" }, { "chars": "S", "keyId": "S.SA", "mod": "SA ", "raw": "1b5b32373b343b38337e", "type": "char" }, { "chars": "T", "keyId": "T.SA", "mod": "SA ", "raw": "1b5b32373b343b38347e", "type": "char" }, { "chars": "U", "keyId": "U.SA", "mod": "SA ", "raw": "1b5b32373b343b38357e", "type": "char" }, { "chars": "V", "keyId": "V.SA", "mod": "SA ", "raw": "1b5b32373b343b38367e", "type": "char" }, { "chars": "W", "keyId": "W.SA", "mod": "SA ", "raw": "1b5b32373b343b38377e", "type": "char" }, { "chars": "X", "keyId": "X.SA", "mod": "SA ", "raw": "1b5b32373b343b38387e", "type": "char" }, { "chars": "Y", "keyId": "Y.SA", "mod": "SA ", "raw": "1b5b32373b343b38397e", "type": "char" }, { "chars": "Z", "keyId": "Z.SA", "mod": "SA ", "raw": "1b5b32373b343b39307e", "type": "char" }, { "chars": "_", "keyId": "_.SA", "mod": "SA ", "raw": "1b5b32373b343b39357e", "type": "char" }, { "chars": "`", "keyId": "`.SA", "mod": "SA ", "raw": "1b5b32373b343b39367e", "type": "char" }, { "key": "Tab", "keyId": "Tab.SA", "mod": "SA ", "raw": "1b5b32373b343b397e", "type": "key" }, { "key": "Escape", "keyId": "Escape.SA", "mod": "SA ", "raw": "1b5b32373b3475", "type": "key" }, { "chars": "d", "keyId": "d.C", "mod": " C", "raw": "1b5b32373b353b3130307e", "type": "char" }, { "chars": "e", "keyId": "e.C", "mod": " C", "raw": "1b5b32373b353b3130317e", "type": "char" }, { "chars": "f", "keyId": "f.C", "mod": " C", "raw": "1b5b32373b353b3130327e", "type": "char" }, { "chars": "g", "keyId": "g.C", "mod": " C", "raw": "1b5b32373b353b3130337e", "type": "char" }, { "chars": "h", "keyId": "h.C", "mod": " C", "raw": "1b5b32373b353b3130347e", "type": "char" }, { "chars": "i", "keyId": "i.C", "mod": " C", "raw": "1b5b32373b353b3130357e", "type": "char" }, { "chars": "j", "keyId": "j.C", "mod": " C", "raw": "1b5b32373b353b3130367e", "type": "char" }, { "chars": "k", "keyId": "k.C", "mod": " C", "raw": "1b5b32373b353b3130377e", "type": "char" }, { "chars": "l", "keyId": "l.C", "mod": " C", "raw": "1b5b32373b353b3130387e", "type": "char" }, { "chars": "m", "keyId": "m.C", "mod": " C", "raw": "1b5b32373b353b3130397e", "type": "char" }, { "chars": "n", "keyId": "n.C", "mod": " C", "raw": "1b5b32373b353b3131307e", "type": "char" }, { "chars": "o", "keyId": "o.C", "mod": " C", "raw": "1b5b32373b353b3131317e", "type": "char" }, { "chars": "p", "keyId": "p.C", "mod": " C", "raw": "1b5b32373b353b3131327e", "type": "char" }, { "chars": "q", "keyId": "q.C", "mod": " C", "raw": "1b5b32373b353b3131337e", "type": "char" }, { "chars": "r", "keyId": "r.C", "mod": " C", "raw": "1b5b32373b353b3131347e", "type": "char" }, { "chars": "s", "keyId": "s.C", "mod": " C", "raw": "1b5b32373b353b3131357e", "type": "char" }, { "chars": "t", "keyId": "t.C", "mod": " C", "raw": "1b5b32373b353b3131367e", "type": "char" }, { "chars": "u", "keyId": "u.C", "mod": " C", "raw": "1b5b32373b353b3131377e", "type": "char" }, { "chars": "v", "keyId": "v.C", "mod": " C", "raw": "1b5b32373b353b3131387e", "type": "char" }, { "chars": "w", "keyId": "w.C", "mod": " C", "raw": "1b5b32373b353b3131397e", "type": "char" }, { "chars": "x", "keyId": "x.C", "mod": " C", "raw": "1b5b32373b353b3132307e", "type": "char" }, { "chars": "y", "keyId": "y.C", "mod": " C", "raw": "1b5b32373b353b3132317e", "type": "char" }, { "key": "Enter", "keyId": "Enter.C", "mod": " C", "raw": "1b5b32373b353b31337e", "type": "key" }, { "chars": "\u00b4", "keyId": "\u00b4.C", "mod": " C", "raw": "1b5b32373b353b3138307e", "type": "char" }, { "chars": "\u00df", "keyId": "\u00df.C", "mod": " C", "raw": "1b5b32373b353b3232337e", "type": "char" }, { "chars": "\u00e4", "keyId": "\u00e4.C", "mod": " C", "raw": "1b5b32373b353b3232387e", "type": "char" }, { "chars": "\u00f6", "keyId": "\u00f6.C", "mod": " C", "raw": "1b5b32373b353b3234367e", "type": "char" }, { "chars": "\u00fc", "keyId": "\u00fc.C", "mod": " C", "raw": "1b5b32373b353b3235327e", "type": "char" }, { "key": "Escape", "keyId": "Escape.C", "mod": " C", "raw": "1b5b32373b353b32377e", "type": "key" }, { "key": "Space", "keyId": "Space.C", "mod": " C", "raw": "1b5b32373b353b33327e", "type": "key" }, { "chars": "#", "keyId": "#.C", "mod": " C", "raw": "1b5b32373b353b33357e", "type": "char" }, { "chars": "+", "keyId": "+.C", "mod": " C", "raw": "1b5b32373b353b34337e", "type": "char" }, { "chars": ",", "keyId": ",.C", "mod": " C", "raw": "1b5b32373b353b34347e", "type": "char" }, { "chars": "-", "keyId": "-.C", "mod": " C", "raw": "1b5b32373b353b34357e", "type": "char" }, { "chars": ".", "keyId": "..C", "mod": " C", "raw": "1b5b32373b353b34367e", "type": "char" }, { "chars": "0", "keyId": "0.C", "mod": " C", "raw": "1b5b32373b353b34387e", "type": "char" }, { "chars": "1", "keyId": "1.C", "mod": " C", "raw": "1b5b32373b353b34397e", "type": "char" }, { "chars": "2", "keyId": "2.C", "mod": " C", "raw": "1b5b32373b353b35307e", "type": "char" }, { "chars": "3", "keyId": "3.C", "mod": " C", "raw": "1b5b32373b353b35317e", "type": "char" }, { "chars": "4", "keyId": "4.C", "mod": " C", "raw": "1b5b32373b353b35327e", "type": "char" }, { "chars": "5", "keyId": "5.C", "mod": " C", "raw": "1b5b32373b353b35337e", "type": "char" }, { "chars": "6", "keyId": "6.C", "mod": " C", "raw": "1b5b32373b353b35347e", "type": "char" }, { "chars": "7", "keyId": "7.C", "mod": " C", "raw": "1b5b32373b353b35357e", "type": "char" }, { "chars": "8", "keyId": "8.C", "mod": " C", "raw": "1b5b32373b353b35367e", "type": "char" }, { "chars": "9", "keyId": "9.C", "mod": " C", "raw": "1b5b32373b353b35377e", "type": "char" }, { "chars": "<", "keyId": "<.C", "mod": " C", "raw": "1b5b32373b353b36307e", "type": "char" }, { "chars": "^", "keyId": "^.C", "mod": " C", "raw": "1b5b32373b353b39347e", "type": "char" }, { "chars": "a", "keyId": "a.C", "mod": " C", "raw": "1b5b32373b353b39377e", "type": "char" }, { "chars": "b", "keyId": "b.C", "mod": " C", "raw": "1b5b32373b353b39387e", "type": "char" }, { "key": "Tab", "keyId": "Tab.C", "mod": " C", "raw": "1b5b32373b353b397e", "type": "key" }, { "key": "Escape", "keyId": "Escape.C", "mod": " C", "raw": "1b5b32373b3575", "type": "key" }, { "key": "Enter", "keyId": "Enter.SC", "mod": "S C", "raw": "1b5b32373b363b31337e", "type": "key" }, { "chars": "\u00a7", "keyId": "\u00a7.SC", "mod": "S C", "raw": "1b5b32373b363b3136377e", "type": "char" }, { "chars": "\u00b0", "keyId": "\u00b0.SC", "mod": "S C", "raw": "1b5b32373b363b3137367e", "type": "char" }, { "chars": "\u00c4", "keyId": "\u00c4.SC", "mod": "S C", "raw": "1b5b32373b363b3139367e", "type": "char" }, { "chars": "\u00d6", "keyId": "\u00d6.SC", "mod": "S C", "raw": "1b5b32373b363b3231347e", "type": "char" }, { "chars": "\u00dc", "keyId": "\u00dc.SC", "mod": "S C", "raw": "1b5b32373b363b3232307e", "type": "char" }, { "key": "Escape", "keyId": "Escape.SC", "mod": "S C", "raw": "1b5b32373b363b32377e", "type": "key" }, { "key": "Space", "keyId": "Space.SC", "mod": "S C", "raw": "1b5b32373b363b33327e", "type": "key" }, { "chars": "!", "keyId": "!.SC", "mod": "S C", "raw": "1b5b32373b363b33337e", "type": "char" }, { "chars": "\"", "keyId": "\".SC", "mod": "S C", "raw": "1b5b32373b363b33347e", "type": "char" }, { "chars": "$", "keyId": "$.SC", "mod": "S C", "raw": "1b5b32373b363b33367e", "type": "char" }, { "chars": "%", "keyId": "%.SC", "mod": "S C", "raw": "1b5b32373b363b33377e", "type": "char" }, { "chars": "&", "keyId": "&.SC", "mod": "S C", "raw": "1b5b32373b363b33387e", "type": "char" }, { "chars": "'", "keyId": "'.SC", "mod": "S C", "raw": "1b5b32373b363b33397e", "type": "char" }, { "chars": "(", "keyId": "(.SC", "mod": "S C", "raw": "1b5b32373b363b34307e", "type": "char" }, { "chars": ")", "keyId": ").SC", "mod": "S C", "raw": "1b5b32373b363b34317e", "type": "char" }, { "chars": "*", "keyId": "*.SC", "mod": "S C", "raw": "1b5b32373b363b34327e", "type": "char" }, { "chars": "/", "keyId": "/.SC", "mod": "S C", "raw": "1b5b32373b363b34377e", "type": "char" }, { "chars": ":", "keyId": ":.SC", "mod": "S C", "raw": "1b5b32373b363b35387e", "type": "char" }, { "chars": ";", "keyId": ";.SC", "mod": "S C", "raw": "1b5b32373b363b35397e", "type": "char" }, { "chars": "=", "keyId": "=.SC", "mod": "S C", "raw": "1b5b32373b363b36317e", "type": "char" }, { "chars": ">", "keyId": ">.SC", "mod": "S C", "raw": "1b5b32373b363b36327e", "type": "char" }, { "chars": "?", "keyId": "?.SC", "mod": "S C", "raw": "1b5b32373b363b36337e", "type": "char" }, { "chars": "A", "keyId": "A.SC", "mod": "S C", "raw": "1b5b32373b363b36357e", "type": "char" }, { "chars": "B", "keyId": "B.SC", "mod": "S C", "raw": "1b5b32373b363b36367e", "type": "char" }, { "chars": "C", "keyId": "C.SC", "mod": "S C", "raw": "1b5b32373b363b36377e", "type": "char" }, { "chars": "D", "keyId": "D.SC", "mod": "S C", "raw": "1b5b32373b363b36387e", "type": "char" }, { "chars": "E", "keyId": "E.SC", "mod": "S C", "raw": "1b5b32373b363b36397e", "type": "char" }, { "chars": "F", "keyId": "F.SC", "mod": "S C", "raw": "1b5b32373b363b37307e", "type": "char" }, { "chars": "G", "keyId": "G.SC", "mod": "S C", "raw": "1b5b32373b363b37317e", "type": "char" }, { "chars": "H", "keyId": "H.SC", "mod": "S C", "raw": "1b5b32373b363b37327e", "type": "char" }, { "chars": "I", "keyId": "I.SC", "mod": "S C", "raw": "1b5b32373b363b37337e", "type": "char" }, { "chars": "J", "keyId": "J.SC", "mod": "S C", "raw": "1b5b32373b363b37347e", "type": "char" }, { "chars": "K", "keyId": "K.SC", "mod": "S C", "raw": "1b5b32373b363b37357e", "type": "char" }, { "chars": "L", "keyId": "L.SC", "mod": "S C", "raw": "1b5b32373b363b37367e", "type": "char" }, { "chars": "M", "keyId": "M.SC", "mod": "S C", "raw": "1b5b32373b363b37377e", "type": "char" }, { "chars": "N", "keyId": "N.SC", "mod": "S C", "raw": "1b5b32373b363b37387e", "type": "char" }, { "chars": "O", "keyId": "O.SC", "mod": "S C", "raw": "1b5b32373b363b37397e", "type": "char" }, { "chars": "P", "keyId": "P.SC", "mod": "S C", "raw": "1b5b32373b363b38307e", "type": "char" }, { "chars": "Q", "keyId": "Q.SC", "mod": "S C", "raw": "1b5b32373b363b38317e", "type": "char" }, { "chars": "R", "keyId": "R.SC", "mod": "S C", "raw": "1b5b32373b363b38327e", "type": "char" }, { "chars": "S", "keyId": "S.SC", "mod": "S C", "raw": "1b5b32373b363b38337e", "type": "char" }, { "chars": "T", "keyId": "T.SC", "mod": "S C", "raw": "1b5b32373b363b38347e", "type": "char" }, { "chars": "U", "keyId": "U.SC", "mod": "S C", "raw": "1b5b32373b363b38357e", "type": "char" }, { "chars": "V", "keyId": "V.SC", "mod": "S C", "raw": "1b5b32373b363b38367e", "type": "char" }, { "chars": "W", "keyId": "W.SC", "mod": "S C", "raw": "1b5b32373b363b38377e", "type": "char" }, { "chars": "X", "keyId": "X.SC", "mod": "S C", "raw": "1b5b32373b363b38387e", "type": "char" }, { "chars": "Y", "keyId": "Y.SC", "mod": "S C", "raw": "1b5b32373b363b38397e", "type": "char" }, { "key": "Backspace", "keyId": "Backspace.SC", "mod": "S C", "raw": "1b5b32373b363b387e", "type": "key" }, { "chars": "Z", "keyId": "Z.SC", "mod": "S C", "raw": "1b5b32373b363b39307e", "type": "char" }, { "chars": "_", "keyId": "_.SC", "mod": "S C", "raw": "1b5b32373b363b39357e", "type": "char" }, { "chars": "`", "keyId": "`.SC", "mod": "S C", "raw": "1b5b32373b363b39367e", "type": "char" }, { "key": "Tab", "keyId": "Tab.SC", "mod": "S C", "raw": "1b5b32373b363b397e", "type": "key" }, { "key": "Escape", "keyId": "Escape.SC", "mod": "S C", "raw": "1b5b32373b3675", "type": "key" }, { "chars": "d", "keyId": "d.AC", "mod": " AC", "raw": "1b5b32373b373b3130307e", "type": "char" }, { "chars": "e", "keyId": "e.AC", "mod": " AC", "raw": "1b5b32373b373b3130317e", "type": "char" }, { "chars": "f", "keyId": "f.AC", "mod": " AC", "raw": "1b5b32373b373b3130327e", "type": "char" }, { "chars": "g", "keyId": "g.AC", "mod": " AC", "raw": "1b5b32373b373b3130337e", "type": "char" }, { "chars": "h", "keyId": "h.AC", "mod": " AC", "raw": "1b5b32373b373b3130347e", "type": "char" }, { "chars": "i", "keyId": "i.AC", "mod": " AC", "raw": "1b5b32373b373b3130357e", "type": "char" }, { "chars": "j", "keyId": "j.AC", "mod": " AC", "raw": "1b5b32373b373b3130367e", "type": "char" }, { "chars": "k", "keyId": "k.AC", "mod": " AC", "raw": "1b5b32373b373b3130377e", "type": "char" }, { "chars": "l", "keyId": "l.AC", "mod": " AC", "raw": "1b5b32373b373b3130387e", "type": "char" }, { "chars": "m", "keyId": "m.AC", "mod": " AC", "raw": "1b5b32373b373b3130397e", "type": "char" }, { "chars": "n", "keyId": "n.AC", "mod": " AC", "raw": "1b5b32373b373b3131307e", "type": "char" }, { "chars": "o", "keyId": "o.AC", "mod": " AC", "raw": "1b5b32373b373b3131317e", "type": "char" }, { "chars": "p", "keyId": "p.AC", "mod": " AC", "raw": "1b5b32373b373b3131327e", "type": "char" }, { "chars": "q", "keyId": "q.AC", "mod": " AC", "raw": "1b5b32373b373b3131337e", "type": "char" }, { "chars": "r", "keyId": "r.AC", "mod": " AC", "raw": "1b5b32373b373b3131347e", "type": "char" }, { "chars": "s", "keyId": "s.AC", "mod": " AC", "raw": "1b5b32373b373b3131357e", "type": "char" }, { "chars": "t", "keyId": "t.AC", "mod": " AC", "raw": "1b5b32373b373b3131367e", "type": "char" }, { "chars": "u", "keyId": "u.AC", "mod": " AC", "raw": "1b5b32373b373b3131377e", "type": "char" }, { "chars": "v", "keyId": "v.AC", "mod": " AC", "raw": "1b5b32373b373b3131387e", "type": "char" }, { "chars": "w", "keyId": "w.AC", "mod": " AC", "raw": "1b5b32373b373b3131397e", "type": "char" }, { "chars": "x", "keyId": "x.AC", "mod": " AC", "raw": "1b5b32373b373b3132307e", "type": "char" }, { "chars": "y", "keyId": "y.AC", "mod": " AC", "raw": "1b5b32373b373b3132317e", "type": "char" }, { "chars": "z", "keyId": "z.AC", "mod": " AC", "raw": "1b5b32373b373b3132327e", "type": "char" }, { "key": "Enter", "keyId": "Enter.AC", "mod": " AC", "raw": "1b5b32373b373b31337e", "type": "key" }, { "chars": "\u00b4", "keyId": "\u00b4.AC", "mod": " AC", "raw": "1b5b32373b373b3138307e", "type": "char" }, { "chars": "\u00df", "keyId": "\u00df.AC", "mod": " AC", "raw": "1b5b32373b373b3232337e", "type": "char" }, { "chars": "\u00e4", "keyId": "\u00e4.AC", "mod": " AC", "raw": "1b5b32373b373b3232387e", "type": "char" }, { "chars": "\u00f6", "keyId": "\u00f6.AC", "mod": " AC", "raw": "1b5b32373b373b3234367e", "type": "char" }, { "chars": "\u00fc", "keyId": "\u00fc.AC", "mod": " AC", "raw": "1b5b32373b373b3235327e", "type": "char" }, { "key": "Space", "keyId": "Space.AC", "mod": " AC", "raw": "1b5b32373b373b33327e", "type": "key" }, { "chars": "#", "keyId": "#.AC", "mod": " AC", "raw": "1b5b32373b373b33357e", "type": "char" }, { "chars": "+", "keyId": "+.AC", "mod": " AC", "raw": "1b5b32373b373b34337e", "type": "char" }, { "chars": ",", "keyId": ",.AC", "mod": " AC", "raw": "1b5b32373b373b34347e", "type": "char" }, { "chars": "-", "keyId": "-.AC", "mod": " AC", "raw": "1b5b32373b373b34357e", "type": "char" }, { "chars": ".", "keyId": "..AC", "mod": " AC", "raw": "1b5b32373b373b34367e", "type": "char" }, { "chars": "0", "keyId": "0.AC", "mod": " AC", "raw": "1b5b32373b373b34387e", "type": "char" }, { "chars": "1", "keyId": "1.AC", "mod": " AC", "raw": "1b5b32373b373b34397e", "type": "char" }, { "chars": "2", "keyId": "2.AC", "mod": " AC", "raw": "1b5b32373b373b35307e", "type": "char" }, { "chars": "3", "keyId": "3.AC", "mod": " AC", "raw": "1b5b32373b373b35317e", "type": "char" }, { "chars": "4", "keyId": "4.AC", "mod": " AC", "raw": "1b5b32373b373b35327e", "type": "char" }, { "chars": "5", "keyId": "5.AC", "mod": " AC", "raw": "1b5b32373b373b35337e", "type": "char" }, { "chars": "6", "keyId": "6.AC", "mod": " AC", "raw": "1b5b32373b373b35347e", "type": "char" }, { "chars": "7", "keyId": "7.AC", "mod": " AC", "raw": "1b5b32373b373b35357e", "type": "char" }, { "chars": "8", "keyId": "8.AC", "mod": " AC", "raw": "1b5b32373b373b35367e", "type": "char" }, { "chars": "9", "keyId": "9.AC", "mod": " AC", "raw": "1b5b32373b373b35377e", "type": "char" }, { "chars": "<", "keyId": "<.AC", "mod": " AC", "raw": "1b5b32373b373b36307e", "type": "char" }, { "chars": "^", "keyId": "^.AC", "mod": " AC", "raw": "1b5b32373b373b39347e", "type": "char" }, { "chars": "a", "keyId": "a.AC", "mod": " AC", "raw": "1b5b32373b373b39377e", "type": "char" }, { "chars": "b", "keyId": "b.AC", "mod": " AC", "raw": "1b5b32373b373b39387e", "type": "char" }, { "chars": "c", "keyId": "c.AC", "mod": " AC", "raw": "1b5b32373b373b39397e", "type": "char" }, { "key": "Tab", "keyId": "Tab.AC", "mod": " AC", "raw": "1b5b32373b373b397e", "type": "key" }, { "key": "Enter", "keyId": "Enter.SAC", "mod": "SAC", "raw": "1b5b32373b383b31337e", "type": "key" }, { "chars": "\u00a7", "keyId": "\u00a7.SAC", "mod": "SAC", "raw": "1b5b32373b383b3136377e", "type": "char" }, { "chars": "\u00b0", "keyId": "\u00b0.SAC", "mod": "SAC", "raw": "1b5b32373b383b3137367e", "type": "char" }, { "chars": "\u00c4", "keyId": "\u00c4.SAC", "mod": "SAC", "raw": "1b5b32373b383b3139367e", "type": "char" }, { "chars": "\u00d6", "keyId": "\u00d6.SAC", "mod": "SAC", "raw": "1b5b32373b383b3231347e", "type": "char" }, { "chars": "\u00dc", "keyId": "\u00dc.SAC", "mod": "SAC", "raw": "1b5b32373b383b3232307e", "type": "char" }, { "key": "Escape", "keyId": "Escape.SAC", "mod": "SAC", "raw": "1b5b32373b383b32377e", "type": "key" }, { "key": "Space", "keyId": "Space.SAC", "mod": "SAC", "raw": "1b5b32373b383b33327e", "type": "key" }, { "chars": "!", "keyId": "!.SAC", "mod": "SAC", "raw": "1b5b32373b383b33337e", "type": "char" }, { "chars": "\"", "keyId": "\".SAC", "mod": "SAC", "raw": "1b5b32373b383b33347e", "type": "char" }, { "chars": "$", "keyId": "$.SAC", "mod": "SAC", "raw": "1b5b32373b383b33367e", "type": "char" }, { "chars": "%", "keyId": "%.SAC", "mod": "SAC", "raw": "1b5b32373b383b33377e", "type": "char" }, { "chars": "&", "keyId": "&.SAC", "mod": "SAC", "raw": "1b5b32373b383b33387e", "type": "char" }, { "chars": "(", "keyId": "(.SAC", "mod": "SAC", "raw": "1b5b32373b383b34307e", "type": "char" }, { "chars": ")", "keyId": ").SAC", "mod": "SAC", "raw": "1b5b32373b383b34317e", "type": "char" }, { "chars": "*", "keyId": "*.SAC", "mod": "SAC", "raw": "1b5b32373b383b34327e", "type": "char" }, { "chars": "/", "keyId": "/.SAC", "mod": "SAC", "raw": "1b5b32373b383b34377e", "type": "char" }, { "chars": ":", "keyId": ":.SAC", "mod": "SAC", "raw": "1b5b32373b383b35387e", "type": "char" }, { "chars": ";", "keyId": ";.SAC", "mod": "SAC", "raw": "1b5b32373b383b35397e", "type": "char" }, { "chars": "=", "keyId": "=.SAC", "mod": "SAC", "raw": "1b5b32373b383b36317e", "type": "char" }, { "chars": ">", "keyId": ">.SAC", "mod": "SAC", "raw": "1b5b32373b383b36327e", "type": "char" }, { "chars": "?", "keyId": "?.SAC", "mod": "SAC", "raw": "1b5b32373b383b36337e", "type": "char" }, { "chars": "A", "keyId": "A.SAC", "mod": "SAC", "raw": "1b5b32373b383b36357e", "type": "char" }, { "chars": "B", "keyId": "B.SAC", "mod": "SAC", "raw": "1b5b32373b383b36367e", "type": "char" }, { "chars": "C", "keyId": "C.SAC", "mod": "SAC", "raw": "1b5b32373b383b36377e", "type": "char" }, { "chars": "D", "keyId": "D.SAC", "mod": "SAC", "raw": "1b5b32373b383b36387e", "type": "char" }, { "chars": "E", "keyId": "E.SAC", "mod": "SAC", "raw": "1b5b32373b383b36397e", "type": "char" }, { "chars": "F", "keyId": "F.SAC", "mod": "SAC", "raw": "1b5b32373b383b37307e", "type": "char" }, { "chars": "G", "keyId": "G.SAC", "mod": "SAC", "raw": "1b5b32373b383b37317e", "type": "char" }, { "chars": "H", "keyId": "H.SAC", "mod": "SAC", "raw": "1b5b32373b383b37327e", "type": "char" }, { "chars": "I", "keyId": "I.SAC", "mod": "SAC", "raw": "1b5b32373b383b37337e", "type": "char" }, { "chars": "J", "keyId": "J.SAC", "mod": "SAC", "raw": "1b5b32373b383b37347e", "type": "char" }, { "chars": "K", "keyId": "K.SAC", "mod": "SAC", "raw": "1b5b32373b383b37357e", "type": "char" }, { "chars": "L", "keyId": "L.SAC", "mod": "SAC", "raw": "1b5b32373b383b37367e", "type": "char" }, { "chars": "M", "keyId": "M.SAC", "mod": "SAC", "raw": "1b5b32373b383b37377e", "type": "char" }, { "chars": "N", "keyId": "N.SAC", "mod": "SAC", "raw": "1b5b32373b383b37387e", "type": "char" }, { "chars": "O", "keyId": "O.SAC", "mod": "SAC", "raw": "1b5b32373b383b37397e", "type": "char" }, { "chars": "P", "keyId": "P.SAC", "mod": "SAC", "raw": "1b5b32373b383b38307e", "type": "char" }, { "chars": "Q", "keyId": "Q.SAC", "mod": "SAC", "raw": "1b5b32373b383b38317e", "type": "char" }, { "chars": "R", "keyId": "R.SAC", "mod": "SAC", "raw": "1b5b32373b383b38327e", "type": "char" }, { "chars": "S", "keyId": "S.SAC", "mod": "SAC", "raw": "1b5b32373b383b38337e", "type": "char" }, { "chars": "T", "keyId": "T.SAC", "mod": "SAC", "raw": "1b5b32373b383b38347e", "type": "char" }, { "chars": "U", "keyId": "U.SAC", "mod": "SAC", "raw": "1b5b32373b383b38357e", "type": "char" }, { "chars": "V", "keyId": "V.SAC", "mod": "SAC", "raw": "1b5b32373b383b38367e", "type": "char" }, { "chars": "W", "keyId": "W.SAC", "mod": "SAC", "raw": "1b5b32373b383b38377e", "type": "char" }, { "chars": "X", "keyId": "X.SAC", "mod": "SAC", "raw": "1b5b32373b383b38387e", "type": "char" }, { "chars": "Y", "keyId": "Y.SAC", "mod": "SAC", "raw": "1b5b32373b383b38397e", "type": "char" }, { "key": "Backspace", "keyId": "Backspace.SAC", "mod": "SAC", "raw": "1b5b32373b383b387e", "type": "key" }, { "chars": "Z", "keyId": "Z.SAC", "mod": "SAC", "raw": "1b5b32373b383b39307e", "type": "char" }, { "chars": "`", "keyId": "`.SAC", "mod": "SAC", "raw": "1b5b32373b383b39367e", "type": "char" }, { "key": "Tab", "keyId": "Tab.SAC", "mod": "SAC", "raw": "1b5b32373b383b397e", "type": "key" }, { "key": "Escape", "keyId": "Escape.SAC", "mod": "SAC", "raw": "1b5b32373b3875", "type": "key" }, { "key": "F3", "keyId": "F3.SC", "mod": "S C", "raw": "1b5b32385e", "type": "key" }, { "key": "F3", "keyId": "F3.S", "mod": "S ", "raw": "1b5b32387e", "type": "key" }, { "key": "F4", "keyId": "F4.SC", "mod": "S C", "raw": "1b5b32395e", "type": "key" }, { "key": "ContextMenu", "keyId": "ContextMenu.", "mod": " ", "raw": "1b5b32397e", "type": "key" }, { "key": "Insert", "keyId": "Insert.S", "mod": "S ", "raw": "1b5b323b327e", "type": "key" }, { "key": "Insert", "keyId": "Insert.A", "mod": " A ", "raw": "1b5b323b337e", "type": "key" }, { "key": "Insert", "keyId": "Insert.SA", "mod": "SA ", "raw": "1b5b323b347e", "type": "key" }, { "key": "Insert", "keyId": "Insert.C", "mod": " C", "raw": "1b5b323b357e", "type": "key" }, { "key": "Insert", "keyId": "Insert.SC", "mod": "S C", "raw": "1b5b323b367e", "type": "key" }, { "key": "Insert", "keyId": "Insert.AC", "mod": " AC", "raw": "1b5b323b377e", "type": "key" }, { "key": "Insert", "keyId": "Insert.SAC", "mod": "SAC", "raw": "1b5b323b387e", "type": "key" }, { "key": "Insert", "keyId": "Insert.SC", "mod": "S C", "raw": "1b5b3240", "type": "key" }, { "key": "Insert", "keyId": "Insert.C", "mod": " C", "raw": "1b5b325e", "type": "key" }, { "key": "Insert", "keyId": "Insert.", "mod": " ", "raw": "1b5b327e", "type": "key" }, { "key": "Delete", "keyId": "Delete.S", "mod": "S ", "raw": "1b5b3324", "type": "key" }, { "key": "F5", "keyId": "F5.SC", "mod": "S C", "raw": "1b5b33315e", "type": "key" }, { "key": "F5", "keyId": "F5.S", "mod": "S ", "raw": "1b5b33317e", "type": "key" }, { "key": "Space", "keyId": "Space.S", "mod": "S ", "raw": "1b5b33323b3275", "type": "key" }, { "key": "Space", "keyId": "Space.A", "mod": " A ", "raw": "1b5b33323b3375", "type": "key" }, { "key": "Space", "keyId": "Space.SA", "mod": "SA ", "raw": "1b5b33323b3475", "type": "key" }, { "key": "Space", "keyId": "Space.C", "mod": " C", "raw": "1b5b33323b3575", "type": "key" }, { "key": "Space", "keyId": "Space.SC", "mod": "S C", "raw": "1b5b33323b3675", "type": "key" }, { "key": "Space", "keyId": "Space.AC", "mod": " AC", "raw": "1b5b33323b3775", "type": "key" }, { "key": "Space", "keyId": "Space.SAC", "mod": "SAC", "raw": "1b5b33323b3875", "type": "key" }, { "key": "F6", "keyId": "F6.SC", "mod": "S C", "raw": "1b5b33325e", "type": "key" }, { "key": "F6", "keyId": "F6.S", "mod": "S ", "raw": "1b5b33327e", "type": "key" }, { "chars": "!", "keyId": "!.SA", "mod": "SA ", "raw": "1b5b33333b3475", "type": "char" }, { "chars": "!", "keyId": "!.SC", "mod": "S C", "raw": "1b5b33333b3675", "type": "char" }, { "chars": "!", "keyId": "!.SAC", "mod": "SAC", "raw": "1b5b33333b3875", "type": "char" }, { "key": "F7", "keyId": "F7.SC", "mod": "S C", "raw": "1b5b33335e", "type": "key" }, { "key": "F7", "keyId": "F7.S", "mod": "S ", "raw": "1b5b33337e", "type": "key" }, { "chars": "\"", "keyId": "\".SA", "mod": "SA ", "raw": "1b5b33343b3475", "type": "char" }, { "chars": "\"", "keyId": "\".SC", "mod": "S C", "raw": "1b5b33343b3675", "type": "char" }, { "chars": "\"", "keyId": "\".SAC", "mod": "SAC", "raw": "1b5b33343b3875", "type": "char" }, { "key": "F8", "keyId": "F8.SC", "mod": "S C", "raw": "1b5b33345e", "type": "key" }, { "key": "F8", "keyId": "F8.S", "mod": "S ", "raw": "1b5b33347e", "type": "key" }, { "chars": "#", "keyId": "#.A", "mod": " A ", "raw": "1b5b33353b3375", "type": "char" }, { "chars": "#", "keyId": "#.C", "mod": " C", "raw": "1b5b33353b3575", "type": "char" }, { "chars": "#", "keyId": "#.AC", "mod": " AC", "raw": "1b5b33353b3775", "type": "char" }, { "chars": "$", "keyId": "$.SA", "mod": "SA ", "raw": "1b5b33363b3475", "type": "char" }, { "chars": "$", "keyId": "$.SC", "mod": "S C", "raw": "1b5b33363b3675", "type": "char" }, { "chars": "$", "keyId": "$.SAC", "mod": "SAC", "raw": "1b5b33363b3875", "type": "char" }, { "chars": "%", "keyId": "%.SA", "mod": "SA ", "raw": "1b5b33373b3475", "type": "char" }, { "chars": "%", "keyId": "%.SC", "mod": "S C", "raw": "1b5b33373b3675", "type": "char" }, { "chars": "%", "keyId": "%.SAC", "mod": "SAC", "raw": "1b5b33373b3875", "type": "char" }, { "chars": "&", "keyId": "&.SA", "mod": "SA ", "raw": "1b5b33383b3475", "type": "char" }, { "chars": "&", "keyId": "&.SC", "mod": "S C", "raw": "1b5b33383b3675", "type": "char" }, { "chars": "&", "keyId": "&.SAC", "mod": "SAC", "raw": "1b5b33383b3875", "type": "char" }, { "chars": "'", "keyId": "'.SA", "mod": "SA ", "raw": "1b5b33393b3475", "type": "char" }, { "chars": "'", "keyId": "'.SC", "mod": "S C", "raw": "1b5b33393b3675", "type": "char" }, { "chars": "'", "keyId": "'.SAC", "mod": "SAC", "raw": "1b5b33393b3875", "type": "char" }, { "key": "Delete", "keyId": "Delete.S", "mod": "S ", "raw": "1b5b333b327e", "type": "key" }, { "key": "Delete", "keyId": "Delete.A", "mod": " A ", "raw": "1b5b333b337e", "type": "key" }, { "key": "Delete", "keyId": "Delete.SA", "mod": "SA ", "raw": "1b5b333b347e", "type": "key" }, { "key": "Delete", "keyId": "Delete.C", "mod": " C", "raw": "1b5b333b357e", "type": "key" }, { "key": "Delete", "keyId": "Delete.SC", "mod": "S C", "raw": "1b5b333b367e", "type": "key" }, { "key": "Delete", "keyId": "Delete.AC", "mod": " AC", "raw": "1b5b333b377e", "type": "key" }, { "key": "Delete", "keyId": "Delete.SAC", "mod": "SAC", "raw": "1b5b333b387e", "type": "key" }, { "key": "Delete", "keyId": "Delete.SC", "mod": "S C", "raw": "1b5b3340", "type": "key" }, { "key": "Delete", "keyId": "Delete.C", "mod": " C", "raw": "1b5b335e", "type": "key" }, { "key": "Delete", "keyId": "Delete.", "mod": " ", "raw": "1b5b337e", "type": "key" }, { "chars": "(", "keyId": "(.SA", "mod": "SA ", "raw": "1b5b34303b3475", "type": "char" }, { "chars": "(", "keyId": "(.SC", "mod": "S C", "raw": "1b5b34303b3675", "type": "char" }, { "chars": "(", "keyId": "(.SAC", "mod": "SAC", "raw": "1b5b34303b3875", "type": "char" }, { "chars": ")", "keyId": ").SA", "mod": "SA ", "raw": "1b5b34313b3475", "type": "char" }, { "chars": ")", "keyId": ").SC", "mod": "S C", "raw": "1b5b34313b3675", "type": "char" }, { "chars": ")", "keyId": ").SAC", "mod": "SAC", "raw": "1b5b34313b3875", "type": "char" }, { "chars": "*", "keyId": "*.SA", "mod": "SA ", "raw": "1b5b34323b3475", "type": "char" }, { "chars": "*", "keyId": "*.SC", "mod": "S C", "raw": "1b5b34323b3675", "type": "char" }, { "chars": "*", "keyId": "*.SAC", "mod": "SAC", "raw": "1b5b34323b3875", "type": "char" }, { "chars": "+", "keyId": "+.A", "mod": " A ", "raw": "1b5b34333b3375", "type": "char" }, { "chars": "+", "keyId": "+.C", "mod": " C", "raw": "1b5b34333b3575", "type": "char" }, { "chars": "+", "keyId": "+.AC", "mod": " AC", "raw": "1b5b34333b3775", "type": "char" }, { "chars": ",", "keyId": ",.A", "mod": " A ", "raw": "1b5b34343b3375", "type": "char" }, { "chars": ",", "keyId": ",.C", "mod": " C", "raw": "1b5b34343b3575", "type": "char" }, { "chars": ",", "keyId": ",.AC", "mod": " AC", "raw": "1b5b34343b3775", "type": "char" }, { "chars": "-", "keyId": "-.A", "mod": " A ", "raw": "1b5b34353b3375", "type": "char" }, { "chars": "-", "keyId": "-.C", "mod": " C", "raw": "1b5b34353b3575", "type": "char" }, { "chars": "-", "keyId": "-.AC", "mod": " AC", "raw": "1b5b34353b3775", "type": "char" }, { "chars": ".", "keyId": "..A", "mod": " A ", "raw": "1b5b34363b3375", "type": "char" }, { "chars": ".", "keyId": "..C", "mod": " C", "raw": "1b5b34363b3575", "type": "char" }, { "chars": ".", "keyId": "..AC", "mod": " AC", "raw": "1b5b34363b3775", "type": "char" }, { "chars": "/", "keyId": "/.SA", "mod": "SA ", "raw": "1b5b34373b3475", "type": "char" }, { "chars": "/", "keyId": "/.SC", "mod": "S C", "raw": "1b5b34373b3675", "type": "char" }, { "chars": "/", "keyId": "/.SAC", "mod": "SAC", "raw": "1b5b34373b3875", "type": "char" }, { "chars": "0", "keyId": "0.A", "mod": " A ", "raw": "1b5b34383b3375", "type": "char" }, { "chars": "0", "keyId": "0.C", "mod": " C", "raw": "1b5b34383b3575", "type": "char" }, { "chars": "0", "keyId": "0.AC", "mod": " AC", "raw": "1b5b34383b3775", "type": "char" }, { "chars": "1", "keyId": "1.A", "mod": " A ", "raw": "1b5b34393b3375", "type": "char" }, { "chars": "1", "keyId": "1.C", "mod": " C", "raw": "1b5b34393b3575", "type": "char" }, { "chars": "1", "keyId": "1.AC", "mod": " AC", "raw": "1b5b34393b3775", "type": "char" }, { "key": "End", "keyId": "End.", "mod": " ", "raw": "1b5b347e", "type": "key" }, { "key": "PageUp", "keyId": "PageUp.S", "mod": "S ", "raw": "1b5b3524", "type": "key" }, { "chars": "2", "keyId": "2.A", "mod": " A ", "raw": "1b5b35303b3375", "type": "char" }, { "chars": "2", "keyId": "2.C", "mod": " C", "raw": "1b5b35303b3575", "type": "char" }, { "chars": "2", "keyId": "2.AC", "mod": " AC", "raw": "1b5b35303b3775", "type": "char" }, { "chars": "3", "keyId": "3.A", "mod": " A ", "raw": "1b5b35313b3375", "type": "char" }, { "chars": "3", "keyId": "3.C", "mod": " C", "raw": "1b5b35313b3575", "type": "char" }, { "chars": "3", "keyId": "3.AC", "mod": " AC", "raw": "1b5b35313b3775", "type": "char" }, { "chars": "4", "keyId": "4.A", "mod": " A ", "raw": "1b5b35323b3375", "type": "char" }, { "chars": "4", "keyId": "4.C", "mod": " C", "raw": "1b5b35323b3575", "type": "char" }, { "chars": "4", "keyId": "4.AC", "mod": " AC", "raw": "1b5b35323b3775", "type": "char" }, { "chars": "5", "keyId": "5.A", "mod": " A ", "raw": "1b5b35333b3375", "type": "char" }, { "chars": "5", "keyId": "5.C", "mod": " C", "raw": "1b5b35333b3575", "type": "char" }, { "chars": "5", "keyId": "5.AC", "mod": " AC", "raw": "1b5b35333b3775", "type": "char" }, { "chars": "6", "keyId": "6.A", "mod": " A ", "raw": "1b5b35343b3375", "type": "char" }, { "chars": "6", "keyId": "6.C", "mod": " C", "raw": "1b5b35343b3575", "type": "char" }, { "chars": "6", "keyId": "6.AC", "mod": " AC", "raw": "1b5b35343b3775", "type": "char" }, { "chars": "7", "keyId": "7.A", "mod": " A ", "raw": "1b5b35353b3375", "type": "char" }, { "chars": "7", "keyId": "7.C", "mod": " C", "raw": "1b5b35353b3575", "type": "char" }, { "chars": "7", "keyId": "7.AC", "mod": " AC", "raw": "1b5b35353b3775", "type": "char" }, { "chars": "8", "keyId": "8.A", "mod": " A ", "raw": "1b5b35363b3375", "type": "char" }, { "chars": "8", "keyId": "8.C", "mod": " C", "raw": "1b5b35363b3575", "type": "char" }, { "chars": "8", "keyId": "8.AC", "mod": " AC", "raw": "1b5b35363b3775", "type": "char" }, { "chars": "9", "keyId": "9.A", "mod": " A ", "raw": "1b5b35373b3375", "type": "char" }, { "chars": "9", "keyId": "9.C", "mod": " C", "raw": "1b5b35373b3575", "type": "char" }, { "chars": "9", "keyId": "9.AC", "mod": " AC", "raw": "1b5b35373b3775", "type": "char" }, { "chars": ":", "keyId": ":.SA", "mod": "SA ", "raw": "1b5b35383b3475", "type": "char" }, { "chars": ":", "keyId": ":.SC", "mod": "S C", "raw": "1b5b35383b3675", "type": "char" }, { "chars": ":", "keyId": ":.SAC", "mod": "SAC", "raw": "1b5b35383b3875", "type": "char" }, { "chars": ";", "keyId": ";.SA", "mod": "SA ", "raw": "1b5b35393b3475", "type": "char" }, { "chars": ";", "keyId": ";.SC", "mod": "S C", "raw": "1b5b35393b3675", "type": "char" }, { "chars": ";", "keyId": ";.SAC", "mod": "SAC", "raw": "1b5b35393b3875", "type": "char" }, { "key": "PageUp", "keyId": "PageUp.S", "mod": "S ", "raw": "1b5b353b327e", "type": "key" }, { "key": "PageUp", "keyId": "PageUp.A", "mod": " A ", "raw": "1b5b353b337e", "type": "key" }, { "key": "PageUp", "keyId": "PageUp.SA", "mod": "SA ", "raw": "1b5b353b347e", "type": "key" }, { "key": "PageUp", "keyId": "PageUp.C", "mod": " C", "raw": "1b5b353b357e", "type": "key" }, { "key": "PageUp", "keyId": "PageUp.SC", "mod": "S C", "raw": "1b5b353b367e", "type": "key" }, { "key": "PageUp", "keyId": "PageUp.AC", "mod": " AC", "raw": "1b5b353b377e", "type": "key" }, { "key": "PageUp", "keyId": "PageUp.SAC", "mod": "SAC", "raw": "1b5b353b387e", "type": "key" }, { "key": "PageUp", "keyId": "PageUp.SC", "mod": "S C", "raw": "1b5b3540", "type": "key" }, { "key": "PageUp", "keyId": "PageUp.C", "mod": " C", "raw": "1b5b355e", "type": "key" }, { "key": "PageUp", "keyId": "PageUp.", "mod": " ", "raw": "1b5b357e", "type": "key" }, { "key": "PageDown", "keyId": "PageDown.S", "mod": "S ", "raw": "1b5b3624", "type": "key" }, { "chars": "<", "keyId": "<.A", "mod": " A ", "raw": "1b5b36303b3375", "type": "char" }, { "chars": "<", "keyId": "<.C", "mod": " C", "raw": "1b5b36303b3575", "type": "char" }, { "chars": "<", "keyId": "<.AC", "mod": " AC", "raw": "1b5b36303b3775", "type": "char" }, { "chars": "=", "keyId": "=.SA", "mod": "SA ", "raw": "1b5b36313b3475", "type": "char" }, { "chars": "=", "keyId": "=.SC", "mod": "S C", "raw": "1b5b36313b3675", "type": "char" }, { "chars": "=", "keyId": "=.SAC", "mod": "SAC", "raw": "1b5b36313b3875", "type": "char" }, { "chars": ">", "keyId": ">.SA", "mod": "SA ", "raw": "1b5b36323b3475", "type": "char" }, { "chars": ">", "keyId": ">.SC", "mod": "S C", "raw": "1b5b36323b3675", "type": "char" }, { "chars": ">", "keyId": ">.SAC", "mod": "SAC", "raw": "1b5b36323b3875", "type": "char" }, { "chars": "?", "keyId": "?.SA", "mod": "SA ", "raw": "1b5b36333b3475", "type": "char" }, { "chars": "?", "keyId": "?.SC", "mod": "S C", "raw": "1b5b36333b3675", "type": "char" }, { "chars": "?", "keyId": "?.SAC", "mod": "SAC", "raw": "1b5b36333b3875", "type": "char" }, { "chars": "A", "keyId": "A.S", "mod": "S ", "raw": "1b5b36353b3275", "type": "char" }, { "chars": "A", "keyId": "A.SA", "mod": "SA ", "raw": "1b5b36353b3475", "type": "char" }, { "chars": "A", "keyId": "A.SC", "mod": "S C", "raw": "1b5b36353b3675", "type": "char" }, { "chars": "A", "keyId": "A.SAC", "mod": "SAC", "raw": "1b5b36353b3875", "type": "char" }, { "chars": "B", "keyId": "B.S", "mod": "S ", "raw": "1b5b36363b3275", "type": "char" }, { "chars": "B", "keyId": "B.SA", "mod": "SA ", "raw": "1b5b36363b3475", "type": "char" }, { "chars": "B", "keyId": "B.SC", "mod": "S C", "raw": "1b5b36363b3675", "type": "char" }, { "chars": "B", "keyId": "B.SAC", "mod": "SAC", "raw": "1b5b36363b3875", "type": "char" }, { "chars": "C", "keyId": "C.S", "mod": "S ", "raw": "1b5b36373b3275", "type": "char" }, { "chars": "C", "keyId": "C.SA", "mod": "SA ", "raw": "1b5b36373b3475", "type": "char" }, { "chars": "C", "keyId": "C.SC", "mod": "S C", "raw": "1b5b36373b3675", "type": "char" }, { "chars": "C", "keyId": "C.SAC", "mod": "SAC", "raw": "1b5b36373b3875", "type": "char" }, { "chars": "D", "keyId": "D.S", "mod": "S ", "raw": "1b5b36383b3275", "type": "char" }, { "chars": "D", "keyId": "D.SA", "mod": "SA ", "raw": "1b5b36383b3475", "type": "char" }, { "chars": "D", "keyId": "D.SC", "mod": "S C", "raw": "1b5b36383b3675", "type": "char" }, { "chars": "D", "keyId": "D.SAC", "mod": "SAC", "raw": "1b5b36383b3875", "type": "char" }, { "chars": "E", "keyId": "E.S", "mod": "S ", "raw": "1b5b36393b3275", "type": "char" }, { "chars": "E", "keyId": "E.SA", "mod": "SA ", "raw": "1b5b36393b3475", "type": "char" }, { "chars": "E", "keyId": "E.SC", "mod": "S C", "raw": "1b5b36393b3675", "type": "char" }, { "chars": "E", "keyId": "E.SAC", "mod": "SAC", "raw": "1b5b36393b3875", "type": "char" }, { "key": "PageDown", "keyId": "PageDown.S", "mod": "S ", "raw": "1b5b363b327e", "type": "key" }, { "key": "PageDown", "keyId": "PageDown.A", "mod": " A ", "raw": "1b5b363b337e", "type": "key" }, { "key": "PageDown", "keyId": "PageDown.SA", "mod": "SA ", "raw": "1b5b363b347e", "type": "key" }, { "key": "PageDown", "keyId": "PageDown.C", "mod": " C", "raw": "1b5b363b357e", "type": "key" }, { "key": "PageDown", "keyId": "PageDown.SC", "mod": "S C", "raw": "1b5b363b367e", "type": "key" }, { "key": "PageDown", "keyId": "PageDown.AC", "mod": " AC", "raw": "1b5b363b377e", "type": "key" }, { "key": "PageDown", "keyId": "PageDown.SAC", "mod": "SAC", "raw": "1b5b363b387e", "type": "key" }, { "key": "PageDown", "keyId": "PageDown.SC", "mod": "S C", "raw": "1b5b3640", "type": "key" }, { "key": "PageDown", "keyId": "PageDown.C", "mod": " C", "raw": "1b5b365e", "type": "key" }, { "key": "PageDown", "keyId": "PageDown.", "mod": " ", "raw": "1b5b367e", "type": "key" }, { "key": "Home", "keyId": "Home.S", "mod": "S ", "raw": "1b5b3724", "type": "key" }, { "chars": "F", "keyId": "F.S", "mod": "S ", "raw": "1b5b37303b3275", "type": "char" }, { "chars": "F", "keyId": "F.SA", "mod": "SA ", "raw": "1b5b37303b3475", "type": "char" }, { "chars": "F", "keyId": "F.SC", "mod": "S C", "raw": "1b5b37303b3675", "type": "char" }, { "chars": "F", "keyId": "F.SAC", "mod": "SAC", "raw": "1b5b37303b3875", "type": "char" }, { "chars": "G", "keyId": "G.S", "mod": "S ", "raw": "1b5b37313b3275", "type": "char" }, { "chars": "G", "keyId": "G.SA", "mod": "SA ", "raw": "1b5b37313b3475", "type": "char" }, { "chars": "G", "keyId": "G.SC", "mod": "S C", "raw": "1b5b37313b3675", "type": "char" }, { "chars": "G", "keyId": "G.SAC", "mod": "SAC", "raw": "1b5b37313b3875", "type": "char" }, { "chars": "H", "keyId": "H.S", "mod": "S ", "raw": "1b5b37323b3275", "type": "char" }, { "chars": "H", "keyId": "H.SA", "mod": "SA ", "raw": "1b5b37323b3475", "type": "char" }, { "chars": "H", "keyId": "H.SC", "mod": "S C", "raw": "1b5b37323b3675", "type": "char" }, { "chars": "H", "keyId": "H.SAC", "mod": "SAC", "raw": "1b5b37323b3875", "type": "char" }, { "chars": "I", "keyId": "I.S", "mod": "S ", "raw": "1b5b37333b3275", "type": "char" }, { "chars": "I", "keyId": "I.SA", "mod": "SA ", "raw": "1b5b37333b3475", "type": "char" }, { "chars": "I", "keyId": "I.SC", "mod": "S C", "raw": "1b5b37333b3675", "type": "char" }, { "chars": "I", "keyId": "I.SAC", "mod": "SAC", "raw": "1b5b37333b3875", "type": "char" }, { "chars": "J", "keyId": "J.S", "mod": "S ", "raw": "1b5b37343b3275", "type": "char" }, { "chars": "J", "keyId": "J.SA", "mod": "SA ", "raw": "1b5b37343b3475", "type": "char" }, { "chars": "J", "keyId": "J.SC", "mod": "S C", "raw": "1b5b37343b3675", "type": "char" }, { "chars": "J", "keyId": "J.SAC", "mod": "SAC", "raw": "1b5b37343b3875", "type": "char" }, { "chars": "K", "keyId": "K.S", "mod": "S ", "raw": "1b5b37353b3275", "type": "char" }, { "chars": "K", "keyId": "K.SA", "mod": "SA ", "raw": "1b5b37353b3475", "type": "char" }, { "chars": "K", "keyId": "K.SC", "mod": "S C", "raw": "1b5b37353b3675", "type": "char" }, { "chars": "K", "keyId": "K.SAC", "mod": "SAC", "raw": "1b5b37353b3875", "type": "char" }, { "chars": "L", "keyId": "L.S", "mod": "S ", "raw": "1b5b37363b3275", "type": "char" }, { "chars": "L", "keyId": "L.SA", "mod": "SA ", "raw": "1b5b37363b3475", "type": "char" }, { "chars": "L", "keyId": "L.SC", "mod": "S C", "raw": "1b5b37363b3675", "type": "char" }, { "chars": "L", "keyId": "L.SAC", "mod": "SAC", "raw": "1b5b37363b3875", "type": "char" }, { "chars": "M", "keyId": "M.S", "mod": "S ", "raw": "1b5b37373b3275", "type": "char" }, { "chars": "M", "keyId": "M.SA", "mod": "SA ", "raw": "1b5b37373b3475", "type": "char" }, { "chars": "M", "keyId": "M.SC", "mod": "S C", "raw": "1b5b37373b3675", "type": "char" }, { "chars": "M", "keyId": "M.SAC", "mod": "SAC", "raw": "1b5b37373b3875", "type": "char" }, { "chars": "N", "keyId": "N.S", "mod": "S ", "raw": "1b5b37383b3275", "type": "char" }, { "chars": "N", "keyId": "N.SA", "mod": "SA ", "raw": "1b5b37383b3475", "type": "char" }, { "chars": "N", "keyId": "N.SC", "mod": "S C", "raw": "1b5b37383b3675", "type": "char" }, { "chars": "N", "keyId": "N.SAC", "mod": "SAC", "raw": "1b5b37383b3875", "type": "char" }, { "chars": "O", "keyId": "O.S", "mod": "S ", "raw": "1b5b37393b3275", "type": "char" }, { "chars": "O", "keyId": "O.SA", "mod": "SA ", "raw": "1b5b37393b3475", "type": "char" }, { "chars": "O", "keyId": "O.SC", "mod": "S C", "raw": "1b5b37393b3675", "type": "char" }, { "chars": "O", "keyId": "O.SAC", "mod": "SAC", "raw": "1b5b37393b3875", "type": "char" }, { "key": "Home", "keyId": "Home.SC", "mod": "S C", "raw": "1b5b3740", "type": "key" }, { "key": "Home", "keyId": "Home.C", "mod": " C", "raw": "1b5b375e", "type": "key" }, { "key": "Home", "keyId": "Home.", "mod": " ", "raw": "1b5b377e", "type": "key" }, { "key": "End", "keyId": "End.S", "mod": "S ", "raw": "1b5b3824", "type": "key" }, { "chars": "P", "keyId": "P.S", "mod": "S ", "raw": "1b5b38303b3275", "type": "char" }, { "chars": "P", "keyId": "P.SA", "mod": "SA ", "raw": "1b5b38303b3475", "type": "char" }, { "chars": "P", "keyId": "P.SC", "mod": "S C", "raw": "1b5b38303b3675", "type": "char" }, { "chars": "P", "keyId": "P.SAC", "mod": "SAC", "raw": "1b5b38303b3875", "type": "char" }, { "chars": "Q", "keyId": "Q.S", "mod": "S ", "raw": "1b5b38313b3275", "type": "char" }, { "chars": "Q", "keyId": "Q.SA", "mod": "SA ", "raw": "1b5b38313b3475", "type": "char" }, { "chars": "Q", "keyId": "Q.SC", "mod": "S C", "raw": "1b5b38313b3675", "type": "char" }, { "chars": "Q", "keyId": "Q.SAC", "mod": "SAC", "raw": "1b5b38313b3875", "type": "char" }, { "chars": "R", "keyId": "R.S", "mod": "S ", "raw": "1b5b38323b3275", "type": "char" }, { "chars": "R", "keyId": "R.SA", "mod": "SA ", "raw": "1b5b38323b3475", "type": "char" }, { "chars": "R", "keyId": "R.SC", "mod": "S C", "raw": "1b5b38323b3675", "type": "char" }, { "chars": "R", "keyId": "R.SAC", "mod": "SAC", "raw": "1b5b38323b3875", "type": "char" }, { "chars": "S", "keyId": "S.S", "mod": "S ", "raw": "1b5b38333b3275", "type": "char" }, { "chars": "S", "keyId": "S.SA", "mod": "SA ", "raw": "1b5b38333b3475", "type": "char" }, { "chars": "S", "keyId": "S.SC", "mod": "S C", "raw": "1b5b38333b3675", "type": "char" }, { "chars": "S", "keyId": "S.SAC", "mod": "SAC", "raw": "1b5b38333b3875", "type": "char" }, { "chars": "T", "keyId": "T.S", "mod": "S ", "raw": "1b5b38343b3275", "type": "char" }, { "chars": "T", "keyId": "T.SA", "mod": "SA ", "raw": "1b5b38343b3475", "type": "char" }, { "chars": "T", "keyId": "T.SC", "mod": "S C", "raw": "1b5b38343b3675", "type": "char" }, { "chars": "T", "keyId": "T.SAC", "mod": "SAC", "raw": "1b5b38343b3875", "type": "char" }, { "chars": "U", "keyId": "U.S", "mod": "S ", "raw": "1b5b38353b3275", "type": "char" }, { "chars": "U", "keyId": "U.SA", "mod": "SA ", "raw": "1b5b38353b3475", "type": "char" }, { "chars": "U", "keyId": "U.SC", "mod": "S C", "raw": "1b5b38353b3675", "type": "char" }, { "chars": "U", "keyId": "U.SAC", "mod": "SAC", "raw": "1b5b38353b3875", "type": "char" }, { "chars": "V", "keyId": "V.S", "mod": "S ", "raw": "1b5b38363b3275", "type": "char" }, { "chars": "V", "keyId": "V.SA", "mod": "SA ", "raw": "1b5b38363b3475", "type": "char" }, { "chars": "V", "keyId": "V.SC", "mod": "S C", "raw": "1b5b38363b3675", "type": "char" }, { "chars": "V", "keyId": "V.SAC", "mod": "SAC", "raw": "1b5b38363b3875", "type": "char" }, { "chars": "W", "keyId": "W.S", "mod": "S ", "raw": "1b5b38373b3275", "type": "char" }, { "chars": "W", "keyId": "W.SA", "mod": "SA ", "raw": "1b5b38373b3475", "type": "char" }, { "chars": "W", "keyId": "W.SC", "mod": "S C", "raw": "1b5b38373b3675", "type": "char" }, { "chars": "W", "keyId": "W.SAC", "mod": "SAC", "raw": "1b5b38373b3875", "type": "char" }, { "chars": "X", "keyId": "X.S", "mod": "S ", "raw": "1b5b38383b3275", "type": "char" }, { "chars": "X", "keyId": "X.SA", "mod": "SA ", "raw": "1b5b38383b3475", "type": "char" }, { "chars": "X", "keyId": "X.SC", "mod": "S C", "raw": "1b5b38383b3675", "type": "char" }, { "chars": "X", "keyId": "X.SAC", "mod": "SAC", "raw": "1b5b38383b3875", "type": "char" }, { "chars": "Y", "keyId": "Y.S", "mod": "S ", "raw": "1b5b38393b3275", "type": "char" }, { "chars": "Y", "keyId": "Y.SA", "mod": "SA ", "raw": "1b5b38393b3475", "type": "char" }, { "chars": "Y", "keyId": "Y.SC", "mod": "S C", "raw": "1b5b38393b3675", "type": "char" }, { "chars": "Y", "keyId": "Y.SAC", "mod": "SAC", "raw": "1b5b38393b3875", "type": "char" }, { "key": "Backspace", "keyId": "Backspace.SC", "mod": "S C", "raw": "1b5b383b3675", "type": "key" }, { "key": "Backspace", "keyId": "Backspace.SAC", "mod": "SAC", "raw": "1b5b383b3875", "type": "key" }, { "key": "End", "keyId": "End.SC", "mod": "S C", "raw": "1b5b3840", "type": "key" }, { "key": "End", "keyId": "End.C", "mod": " C", "raw": "1b5b385e", "type": "key" }, { "key": "End", "keyId": "End.", "mod": " ", "raw": "1b5b387e", "type": "key" }, { "chars": "Z", "keyId": "Z.S", "mod": "S ", "raw": "1b5b39303b3275", "type": "char" }, { "chars": "Z", "keyId": "Z.SA", "mod": "SA ", "raw": "1b5b39303b3475", "type": "char" }, { "chars": "Z", "keyId": "Z.SC", "mod": "S C", "raw": "1b5b39303b3675", "type": "char" }, { "chars": "Z", "keyId": "Z.SAC", "mod": "SAC", "raw": "1b5b39303b3875", "type": "char" }, { "chars": "^", "keyId": "^.A", "mod": " A ", "raw": "1b5b39343b3375", "type": "char" }, { "chars": "^", "keyId": "^.C", "mod": " C", "raw": "1b5b39343b3575", "type": "char" }, { "chars": "^", "keyId": "^.AC", "mod": " AC", "raw": "1b5b39343b3775", "type": "char" }, { "chars": "_", "keyId": "_.S", "mod": "S ", "raw": "1b5b39353b3275", "type": "char" }, { "chars": "_", "keyId": "_.SA", "mod": "SA ", "raw": "1b5b39353b3475", "type": "char" }, { "chars": "_", "keyId": "_.SC", "mod": "S C", "raw": "1b5b39353b3675", "type": "char" }, { "chars": "_", "keyId": "_.SAC", "mod": "SAC", "raw": "1b5b39353b3875", "type": "char" }, { "chars": "`", "keyId": "`.S", "mod": "S ", "raw": "1b5b39363b3275", "type": "char" }, { "chars": "`", "keyId": "`.SA", "mod": "SA ", "raw": "1b5b39363b3475", "type": "char" }, { "chars": "`", "keyId": "`.SC", "mod": "S C", "raw": "1b5b39363b3675", "type": "char" }, { "chars": "`", "keyId": "`.SAC", "mod": "SAC", "raw": "1b5b39363b3875", "type": "char" }, { "chars": "a", "keyId": "a.A", "mod": " A ", "raw": "1b5b39373b3375", "type": "char" }, { "chars": "a", "keyId": "a.C", "mod": " C", "raw": "1b5b39373b3575", "type": "char" }, { "chars": "a", "keyId": "a.AC", "mod": " AC", "raw": "1b5b39373b3775", "type": "char" }, { "chars": "b", "keyId": "b.A", "mod": " A ", "raw": "1b5b39383b3375", "type": "char" }, { "chars": "b", "keyId": "b.C", "mod": " C", "raw": "1b5b39383b3575", "type": "char" }, { "chars": "b", "keyId": "b.AC", "mod": " AC", "raw": "1b5b39383b3775", "type": "char" }, { "chars": "c", "keyId": "c.A", "mod": " A ", "raw": "1b5b39393b3375", "type": "char" }, { "chars": "c", "keyId": "c.AC", "mod": " AC", "raw": "1b5b39393b3775", "type": "char" }, { "key": "Tab", "keyId": "Tab.SA", "mod": "SA ", "raw": "1b5b393b3475", "type": "key" }, { "key": "Tab", "keyId": "Tab.C", "mod": " C", "raw": "1b5b393b3575", "type": "key" }, { "key": "Tab", "keyId": "Tab.SC", "mod": "S C", "raw": "1b5b393b3675", "type": "key" }, { "key": "Tab", "keyId": "Tab.AC", "mod": " AC", "raw": "1b5b393b3775", "type": "key" }, { "key": "Tab", "keyId": "Tab.SAC", "mod": "SAC", "raw": "1b5b393b3875", "type": "key" }, { "key": "ArrowUp", "keyId": "ArrowUp.", "mod": " ", "raw": "1b5b41", "type": "key" }, { "key": "ArrowDown", "keyId": "ArrowDown.", "mod": " ", "raw": "1b5b42", "type": "key" }, { "key": "ArrowRight", "keyId": "ArrowRight.", "mod": " ", "raw": "1b5b43", "type": "key" }, { "key": "ArrowLeft", "keyId": "ArrowLeft.", "mod": " ", "raw": "1b5b44", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.", "mod": " ", "raw": "1b5b45", "type": "key" }, { "key": "End", "keyId": "End.", "mod": " ", "raw": "1b5b46", "type": "key" }, { "key": "Numpad5", "keyId": "Numpad5.", "mod": " ", "raw": "1b5b47", "type": "key" }, { "key": "Home", "keyId": "Home.", "mod": " ", "raw": "1b5b48", "type": "key" }, { "key": "Tab", "keyId": "Tab.S", "mod": "S ", "raw": "1b5b5a", "type": "key" }, { "key": "F1", "keyId": "F1.", "mod": " ", "raw": "1b5b5b41", "type": "key" }, { "key": "F2", "keyId": "F2.", "mod": " ", "raw": "1b5b5b42", "type": "key" }, { "key": "F3", "keyId": "F3.", "mod": " ", "raw": "1b5b5b43", "type": "key" }, { "key": "F4", "keyId": "F4.", "mod": " ", "raw": "1b5b5b44", "type": "key" }, { "key": "F5", "keyId": "F5.", "mod": " ", "raw": "1b5b5b45", "type": "key" }, { "key": "ArrowUp", "keyId": "ArrowUp.S", "mod": "S ", "raw": "1b5b61", "type": "key" }, { "key": "ArrowDown", "keyId": "ArrowDown.S", "mod": "S ", "raw": "1b5b62", "type": "key" }, { "key": "ArrowRight", "keyId": "ArrowRight.S", "mod": "S ", "raw": "1b5b63", "type": "key" }, { "key": "ArrowLeft", "keyId": "ArrowLeft.S", "mod": "S ", "raw": "1b5b64", "type": "key" }, { "chars": "^", "keyId": "^.A", "mod": " A ", "raw": "1b5e", "type": "char" }, { "chars": "_", "keyId": "_.A", "mod": " A ", "raw": "1b5f", "type": "char" }, { "chars": "`", "keyId": "`.A", "mod": " A ", "raw": "1b60", "type": "char" }, { "chars": "a", "keyId": "a.A", "mod": " A ", "raw": "1b61", "type": "char" }, { "chars": "b", "keyId": "b.A", "mod": " A ", "raw": "1b62", "type": "char" }, { "chars": "c", "keyId": "c.A", "mod": " A ", "raw": "1b63", "type": "char" }, { "chars": "d", "keyId": "d.A", "mod": " A ", "raw": "1b64", "type": "char" }, { "chars": "e", "keyId": "e.A", "mod": " A ", "raw": "1b65", "type": "char" }, { "chars": "f", "keyId": "f.A", "mod": " A ", "raw": "1b66", "type": "char" }, { "chars": "g", "keyId": "g.A", "mod": " A ", "raw": "1b67", "type": "char" }, { "chars": "h", "keyId": "h.A", "mod": " A ", "raw": "1b68", "type": "char" }, { "chars": "i", "keyId": "i.A", "mod": " A ", "raw": "1b69", "type": "char" }, { "chars": "j", "keyId": "j.A", "mod": " A ", "raw": "1b6a", "type": "char" }, { "chars": "k", "keyId": "k.A", "mod": " A ", "raw": "1b6b", "type": "char" }, { "chars": "l", "keyId": "l.A", "mod": " A ", "raw": "1b6c", "type": "char" }, { "chars": "m", "keyId": "m.A", "mod": " A ", "raw": "1b6d", "type": "char" }, { "chars": "n", "keyId": "n.A", "mod": " A ", "raw": "1b6e", "type": "char" }, { "chars": "o", "keyId": "o.A", "mod": " A ", "raw": "1b6f", "type": "char" }, { "chars": "p", "keyId": "p.A", "mod": " A ", "raw": "1b70", "type": "char" }, { "chars": "q", "keyId": "q.A", "mod": " A ", "raw": "1b71", "type": "char" }, { "chars": "r", "keyId": "r.A", "mod": " A ", "raw": "1b72", "type": "char" }, { "chars": "s", "keyId": "s.A", "mod": " A ", "raw": "1b73", "type": "char" }, { "chars": "t", "keyId": "t.A", "mod": " A ", "raw": "1b74", "type": "char" }, { "chars": "u", "keyId": "u.A", "mod": " A ", "raw": "1b75", "type": "char" }, { "chars": "v", "keyId": "v.A", "mod": " A ", "raw": "1b76", "type": "char" }, { "chars": "w", "keyId": "w.A", "mod": " A ", "raw": "1b77", "type": "char" }, { "chars": "x", "keyId": "x.A", "mod": " A ", "raw": "1b78", "type": "char" }, { "chars": "y", "keyId": "y.A", "mod": " A ", "raw": "1b79", "type": "char" }, { "chars": "z", "keyId": "z.A", "mod": " A ", "raw": "1b7a", "type": "char" }, { "key": "Backspace", "keyId": "Backspace.A", "mod": " A ", "raw": "1b7f", "type": "key" }, { "chars": "\u00a7", "keyId": "\u00a7.A", "mod": " A ", "raw": "1bc2a7", "type": "char" }, { "chars": "\u00b0", "keyId": "\u00b0.A", "mod": " A ", "raw": "1bc2b0", "type": "char" }, { "chars": "\u00b4", "keyId": "\u00b4.A", "mod": " A ", "raw": "1bc2b4", "type": "char" }, { "chars": "\u00c4", "keyId": "\u00c4.A", "mod": " A ", "raw": "1bc384", "type": "char" }, { "chars": "\u00d6", "keyId": "\u00d6.A", "mod": " A ", "raw": "1bc396", "type": "char" }, { "chars": "\u00dc", "keyId": "\u00dc.A", "mod": " A ", "raw": "1bc39c", "type": "char" }, { "chars": "\u00df", "keyId": "\u00df.A", "mod": " A ", "raw": "1bc39f", "type": "char" }, { "chars": "\u00e4", "keyId": "\u00e4.A", "mod": " A ", "raw": "1bc3a4", "type": "char" }, { "chars": "\u00f6", "keyId": "\u00f6.A", "mod": " A ", "raw": "1bc3b6", "type": "char" }, { "chars": "\u00fc", "keyId": "\u00fc.A", "mod": " A ", "raw": "1bc3bc", "type": "char" }, { "chars": "\\", "keyId": "\\.C", "mod": " C", "raw": "1c", "type": "char" }, { "chars": "]", "keyId": "].C", "mod": " C", "raw": "1d", "type": "char" }, { "chars": "~", "keyId": "~.C", "mod": " C", "raw": "1e", "type": "char" }, { "chars": "?", "keyId": "?.C", "mod": " C", "raw": "1f", "type": "char" }, { "key": "Space", "keyId": "Space.", "mod": " ", "raw": "20", "type": "key" }, { "chars": "!", "keyId": "!.", "mod": " ", "raw": "21", "type": "char" }, { "chars": "\"", "keyId": "\".", "mod": " ", "raw": "22", "type": "char" }, { "chars": "#", "keyId": "#.", "mod": " ", "raw": "23", "type": "char" }, { "chars": "$", "keyId": "$.", "mod": " ", "raw": "24", "type": "char" }, { "chars": "%", "keyId": "%.", "mod": " ", "raw": "25", "type": "char" }, { "chars": "&", "keyId": "&.", "mod": " ", "raw": "26", "type": "char" }, { "chars": "'", "keyId": "'.", "mod": " ", "raw": "27", "type": "char" }, { "chars": "(", "keyId": "(.", "mod": " ", "raw": "28", "type": "char" }, { "chars": ")", "keyId": ").", "mod": " ", "raw": "29", "type": "char" }, { "chars": "*", "keyId": "*.", "mod": " ", "raw": "2a", "type": "char" }, { "chars": "+", "keyId": "+.", "mod": " ", "raw": "2b", "type": "char" }, { "chars": ",", "keyId": ",.", "mod": " ", "raw": "2c", "type": "char" }, { "chars": "-", "keyId": "-.", "mod": " ", "raw": "2d", "type": "char" }, { "chars": ".", "keyId": "..", "mod": " ", "raw": "2e", "type": "char" }, { "chars": "/", "keyId": "/.", "mod": " ", "raw": "2f", "type": "char" }, { "chars": "0", "keyId": "0.", "mod": " ", "raw": "30", "type": "char" }, { "chars": "1", "keyId": "1.", "mod": " ", "raw": "31", "type": "char" }, { "chars": "2", "keyId": "2.", "mod": " ", "raw": "32", "type": "char" }, { "chars": "3", "keyId": "3.", "mod": " ", "raw": "33", "type": "char" }, { "chars": "4", "keyId": "4.", "mod": " ", "raw": "34", "type": "char" }, { "chars": "5", "keyId": "5.", "mod": " ", "raw": "35", "type": "char" }, { "chars": "6", "keyId": "6.", "mod": " ", "raw": "36", "type": "char" }, { "chars": "7", "keyId": "7.", "mod": " ", "raw": "37", "type": "char" }, { "chars": "8", "keyId": "8.", "mod": " ", "raw": "38", "type": "char" }, { "chars": "9", "keyId": "9.", "mod": " ", "raw": "39", "type": "char" }, { "chars": ":", "keyId": ":.", "mod": " ", "raw": "3a", "type": "char" }, { "chars": ";", "keyId": ";.", "mod": " ", "raw": "3b", "type": "char" }, { "chars": "<", "keyId": "<.", "mod": " ", "raw": "3c", "type": "char" }, { "chars": "=", "keyId": "=.", "mod": " ", "raw": "3d", "type": "char" }, { "chars": ">", "keyId": ">.", "mod": " ", "raw": "3e", "type": "char" }, { "chars": "?", "keyId": "?.", "mod": " ", "raw": "3f", "type": "char" }, { "chars": "A", "keyId": "A.", "mod": " ", "raw": "41", "type": "char" }, { "chars": "B", "keyId": "B.", "mod": " ", "raw": "42", "type": "char" }, { "chars": "C", "keyId": "C.", "mod": " ", "raw": "43", "type": "char" }, { "chars": "D", "keyId": "D.", "mod": " ", "raw": "44", "type": "char" }, { "chars": "E", "keyId": "E.", "mod": " ", "raw": "45", "type": "char" }, { "chars": "F", "keyId": "F.", "mod": " ", "raw": "46", "type": "char" }, { "chars": "G", "keyId": "G.", "mod": " ", "raw": "47", "type": "char" }, { "chars": "H", "keyId": "H.", "mod": " ", "raw": "48", "type": "char" }, { "chars": "I", "keyId": "I.", "mod": " ", "raw": "49", "type": "char" }, { "chars": "J", "keyId": "J.", "mod": " ", "raw": "4a", "type": "char" }, { "chars": "K", "keyId": "K.", "mod": " ", "raw": "4b", "type": "char" }, { "chars": "L", "keyId": "L.", "mod": " ", "raw": "4c", "type": "char" }, { "chars": "M", "keyId": "M.", "mod": " ", "raw": "4d", "type": "char" }, { "chars": "N", "keyId": "N.", "mod": " ", "raw": "4e", "type": "char" }, { "chars": "O", "keyId": "O.", "mod": " ", "raw": "4f", "type": "char" }, { "chars": "P", "keyId": "P.", "mod": " ", "raw": "50", "type": "char" }, { "chars": "Q", "keyId": "Q.", "mod": " ", "raw": "51", "type": "char" }, { "chars": "R", "keyId": "R.", "mod": " ", "raw": "52", "type": "char" }, { "chars": "S", "keyId": "S.", "mod": " ", "raw": "53", "type": "char" }, { "chars": "T", "keyId": "T.", "mod": " ", "raw": "54", "type": "char" }, { "chars": "U", "keyId": "U.", "mod": " ", "raw": "55", "type": "char" }, { "chars": "V", "keyId": "V.", "mod": " ", "raw": "56", "type": "char" }, { "chars": "W", "keyId": "W.", "mod": " ", "raw": "57", "type": "char" }, { "chars": "X", "keyId": "X.", "mod": " ", "raw": "58", "type": "char" }, { "chars": "Y", "keyId": "Y.", "mod": " ", "raw": "59", "type": "char" }, { "chars": "Z", "keyId": "Z.", "mod": " ", "raw": "5a", "type": "char" }, { "chars": "^", "keyId": "^.", "mod": " ", "raw": "5e", "type": "char" }, { "chars": "_", "keyId": "_.", "mod": " ", "raw": "5f", "type": "char" }, { "chars": "`", "keyId": "`.", "mod": " ", "raw": "60", "type": "char" }, { "chars": "a", "keyId": "a.", "mod": " ", "raw": "61", "type": "char" }, { "chars": "b", "keyId": "b.", "mod": " ", "raw": "62", "type": "char" }, { "chars": "c", "keyId": "c.", "mod": " ", "raw": "63", "type": "char" }, { "chars": "d", "keyId": "d.", "mod": " ", "raw": "64", "type": "char" }, { "chars": "e", "keyId": "e.", "mod": " ", "raw": "65", "type": "char" }, { "chars": "f", "keyId": "f.", "mod": " ", "raw": "66", "type": "char" }, { "chars": "g", "keyId": "g.", "mod": " ", "raw": "67", "type": "char" }, { "chars": "h", "keyId": "h.", "mod": " ", "raw": "68", "type": "char" }, { "chars": "i", "keyId": "i.", "mod": " ", "raw": "69", "type": "char" }, { "chars": "j", "keyId": "j.", "mod": " ", "raw": "6a", "type": "char" }, { "chars": "k", "keyId": "k.", "mod": " ", "raw": "6b", "type": "char" }, { "chars": "l", "keyId": "l.", "mod": " ", "raw": "6c", "type": "char" }, { "chars": "m", "keyId": "m.", "mod": " ", "raw": "6d", "type": "char" }, { "chars": "n", "keyId": "n.", "mod": " ", "raw": "6e", "type": "char" }, { "chars": "o", "keyId": "o.", "mod": " ", "raw": "6f", "type": "char" }, { "chars": "p", "keyId": "p.", "mod": " ", "raw": "70", "type": "char" }, { "chars": "q", "keyId": "q.", "mod": " ", "raw": "71", "type": "char" }, { "chars": "r", "keyId": "r.", "mod": " ", "raw": "72", "type": "char" }, { "chars": "s", "keyId": "s.", "mod": " ", "raw": "73", "type": "char" }, { "chars": "t", "keyId": "t.", "mod": " ", "raw": "74", "type": "char" }, { "chars": "u", "keyId": "u.", "mod": " ", "raw": "75", "type": "char" }, { "chars": "v", "keyId": "v.", "mod": " ", "raw": "76", "type": "char" }, { "chars": "w", "keyId": "w.", "mod": " ", "raw": "77", "type": "char" }, { "chars": "x", "keyId": "x.", "mod": " ", "raw": "78", "type": "char" }, { "chars": "y", "keyId": "y.", "mod": " ", "raw": "79", "type": "char" }, { "chars": "z", "keyId": "z.", "mod": " ", "raw": "7a", "type": "char" }, { "key": "Backspace", "keyId": "Backspace.", "mod": " ", "raw": "7f", "type": "key" }, { "chars": "\u00a7", "keyId": "\u00a7.", "mod": " ", "raw": "c2a7", "type": "char" }, { "chars": "\u00b0", "keyId": "\u00b0.", "mod": " ", "raw": "c2b0", "type": "char" }, { "chars": "\u00b4", "keyId": "\u00b4.", "mod": " ", "raw": "c2b4", "type": "char" }, { "chars": "\u00c4", "keyId": "\u00c4.", "mod": " ", "raw": "c384", "type": "char" }, { "chars": "\u00d6", "keyId": "\u00d6.", "mod": " ", "raw": "c396", "type": "char" }, { "chars": "\u00dc", "keyId": "\u00dc.", "mod": " ", "raw": "c39c", "type": "char" }, { "chars": "\u00df", "keyId": "\u00df.", "mod": " ", "raw": "c39f", "type": "char" }, { "chars": "\u00e4", "keyId": "\u00e4.", "mod": " ", "raw": "c3a4", "type": "char" }, { "chars": "\u00f6", "keyId": "\u00f6.", "mod": " ", "raw": "c3b6", "type": "char" }, { "chars": "\u00fc", "keyId": "\u00fc.", "mod": " ", "raw": "c3bc", "type": "char" } ]termpaint-0.3.1/tests/input_tests_pin.py000077500000000000000000000042161477303547200205030ustar00rootroot00000000000000#! /usr/bin/python3 # SPDX-License-Identifier: BSL-1.0 """ Updates input_tests.json from a set of input capture files This files is usef for test [pin-recorded] """ import json files = [ 'xterm-nobind-compat-105_de_full-2017-10-22:23-fixed.json', 'xterm-nobind-modother-105_de_full-2017-10-23:21.json', 'gnome-terminal-compat-105_de_full-2017-10-23:23.json', 'konsole-compat-105_de_full-2017-10-27:00.json', 'linuxvt-compat-105_de_full-2017-10-26:21.json', 'urxvt-nobind-compat-105_de_full-2017-11-22:00.json', 'xterm-compat+1035_keypad_2019-01-07.json', 'xterm-nobind--app arrow + app np + esc for alt-105_de_full-2018-05-33:21.json', 'xterm-nobind-modother-csiu-105_de_full-2019-07-19.json', ] sequences = {} for filename in files: f = open('../tools/miscdata/' + filename, 'r') source = json.load(f) source_sequences = source['sequences'] added = False for seq in source_sequences: if seq['type'] in ('N/A', 'TODO'): continue if seq['raw'] not in sequences: sequences[seq['raw']] = seq added = True else: existing_seq = sequences[seq['raw']] if (existing_seq['type'], existing_seq['mod'], existing_seq.get('key'), existing_seq.get('chars')) != (seq['type'], seq['mod'], seq.get('key'), seq.get('chars')): print('{} has different interpretations.'.format(seq['raw'])) if not added: print('file {} did not add anything'.format(filename)) output = [] for key in sorted(sequences.keys()): output_sequence = dict(sequences[key]) try: for k in list(output_sequence.keys()): if k not in ('raw', 'type', 'mod', 'key', 'chars'): del output_sequence[k] if output_sequence['type'] == 'key': keyId = output_sequence['key'] else: keyId = output_sequence['chars'] keyId += '.' + output_sequence['mod'].replace(' ', '') except: print(output_sequence) raise output_sequence['keyId'] = keyId output.append(output_sequence) outfile = open('input_tests.json', 'w') json.dump(output, outfile, indent=4, sort_keys=True) termpaint-0.3.1/tests/libfuzz_input.c000066400000000000000000000036131477303547200177470ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include #include #include "../termpaint_input.h" __attribute__((optnone)) void null_callback(void *data, termpaint_event *event) { // read all memory referenced switch (event->type) { case TERMPAINT_EV_INVALID_UTF8: case TERMPAINT_EV_CHAR: for (unsigned i = 0; i < event->c.length; i++) { (void)event->c.string[i]; } (void)event->c.modifier; break; case TERMPAINT_EV_KEY: for (unsigned i = 0; i < event->key.length; i++) { (void)event->key.atom[i]; } (void)event->key.modifier; break; case TERMPAINT_EV_AUTO_DETECT_FINISHED: case TERMPAINT_EV_OVERFLOW: break; case TERMPAINT_EV_CURSOR_POSITION: (void)event->cursor_position.x; (void)event->cursor_position.y; (void)event->cursor_position.safe; break; case TERMPAINT_EV_MODE_REPORT: (void)event->mode.kind; (void)event->mode.number; (void)event->mode.status; break; case TERMPAINT_EV_COLOR_SLOT_REPORT: case TERMPAINT_EV_REPAINT_REQUESTED: case TERMPAINT_EV_MOUSE: break; case TERMPAINT_EV_RAW_PRI_DEV_ATTRIB: case TERMPAINT_EV_RAW_SEC_DEV_ATTRIB: case TERMPAINT_EV_RAW_3RD_DEV_ATTRIB: case TERMPAINT_EV_RAW_DECREQTPARM: for (unsigned i = 0; i < event->raw.length; i++) { (void)event->raw.string[i]; } } } int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { termpaint_input *input_ctx = termpaint_input_new(); termpaint_input_set_event_cb(input_ctx, null_callback, 0); termpaint_input_add_data(input_ctx, (const char*)data, size); termpaint_input_free(input_ctx); return 0; } termpaint-0.3.1/tests/measurement_tests.cpp000066400000000000000000001110521477303547200211470ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include "termpaint.h" #include #include #include #ifndef BUNDLED_CATCH2 #ifdef CATCH3 #include "catch2/catch_all.hpp" #else #include "catch2/catch.hpp" #endif #else #include "../third-party/catch.hpp" #endif // NOTE: This file assumes that the compiler uses utf-8 for string constants. #ifndef __has_cpp_attribute #define __has_cpp_attribute(name) 0 #endif #if __has_cpp_attribute(maybe_unused) && __cplusplus >= 201703L #define MAYBE_UNUSED [[maybe_unused]] #elif __has_cpp_attribute(gnu::unused) #define MAYBE_UNUSED [[gnu::unused]] #else #define MAYBE_UNUSED #endif // sanitizer builds report spurious failures with codecvt and libstdc++, work around using iconv #ifndef __GLIBCXX__ std::u16string toUtf16(std::string data) { return std::wstring_convert, char16_t>{}.from_bytes(data); } std::u32string toUtf32(std::string data) { return std::wstring_convert, char32_t>{}.from_bytes(data); } std::u32string toUtf32(std::u16string data) { return std::wstring_convert, char32_t>{}.from_bytes( reinterpret_cast(data.c_str()), reinterpret_cast(data.c_str() + data.size())); } #else #include class iconv_cache { // keep one context to avoid repeatedly loading/unloading in glibc public: iconv_cache(const char *tocode, const char *fromcode) { cache = iconv_open(tocode, fromcode); } ~iconv_cache() { iconv_close(cache); } private: iconv_t cache; }; #ifdef __NetBSD__ #include #if __NetBSD_Version__ < 1000000000 // NetBSD < 10 does not follow posix and takes a const. // See https://man.netbsd.org/iconv.3#STANDARDS #define ICONV_INCAST(p) (p) #endif #endif #ifndef ICONV_INCAST #define ICONV_INCAST(p) const_cast(p) #endif std::u16string toUtf16(std::string data) { static auto cached = iconv_cache("UTF-16LE", "UTF-8"); static auto cachedBE = iconv_cache("UTF-16BE", "UTF-8"); iconv_t conv; const char16_t endiannessTest = 1; if (*((const char*)&endiannessTest) == 1) { conv = iconv_open("UTF-16LE", "UTF-8"); } else { conv = iconv_open("UTF-16BE", "UTF-8"); } REQUIRE( conv != reinterpret_cast(-1)); const char *pin = data.data(); std::vector outbuffer; outbuffer.resize(2 + data.size()); // zero termination plus BOM char *pout = reinterpret_cast(outbuffer.data()); size_t inlen = data.size() + 1; size_t outlen = outbuffer.size() * sizeof (char16_t); size_t retval = iconv(conv, ICONV_INCAST(&pin), &inlen, &pout, &outlen); REQUIRE(retval != static_cast(-1)); iconv_close(conv); if (outlen > 0 && outbuffer[0] == 0xFEFF /* BOM */) { return std::u16string { outbuffer.data() + 1 }; } else { return std::u16string { outbuffer.data()}; } } std::u32string toUtf32(std::string data) { static auto cached = iconv_cache("UTF-32LE", "UTF-8"); // keep one context to avoid repeatedly loading/unloading in glibc static auto cachedBe = iconv_cache("UTF-32BE", "UTF-8"); // keep one context to avoid repeatedly loading/unloading in glibc iconv_t conv; const char32_t endiannessTest = 1; if (*((const char*)&endiannessTest) == 1) { conv = iconv_open("UTF-32LE", "UTF-8"); } else { conv = iconv_open("UTF-32BE", "UTF-8"); } REQUIRE( conv != reinterpret_cast(-1)); const char *pin = data.data(); std::vector outbuffer; outbuffer.resize(2 + data.size()); // zero termination plus BOM char *pout = reinterpret_cast(outbuffer.data()); size_t inlen = data.size() + 1; size_t outlen = outbuffer.size() * sizeof (char32_t); size_t retval = iconv(conv, ICONV_INCAST(&pin), &inlen, &pout, &outlen); REQUIRE(retval != static_cast(-1)); iconv_close(conv); if (outlen > 0 && outbuffer[0] == 0xFEFF /* BOM */) { return std::u32string { outbuffer.data() + 1 }; } else { return std::u32string { outbuffer.data() }; } } std::u32string toUtf32(std::u16string data) { static auto cached = iconv_cache("UTF-32LE", "UTF-8"); // keep one context to avoid repeatedly loading/unloading in glibc static auto cachedBe = iconv_cache("UTF-32BE", "UTF-8"); // keep one context to avoid repeatedly loading/unloading in glibc iconv_t conv; const char32_t endiannessTest = 1; if (*((const char*)&endiannessTest) == 1) { conv = iconv_open("UTF-32LE", "UTF-16LE"); } else { conv = iconv_open("UTF-32BE", "UTF-16BE"); } REQUIRE( conv != reinterpret_cast(-1)); const char *pin = reinterpret_cast(data.data()); std::vector outbuffer; outbuffer.resize(2 + data.size()); // zero termination plus BOM char *pout = reinterpret_cast(outbuffer.data()); size_t inlen = (data.size() + 1) * 2; size_t outlen = outbuffer.size() * sizeof (char32_t); size_t retval = iconv(conv, ICONV_INCAST(&pin), &inlen, &pout, &outlen); REQUIRE(retval != static_cast(-1)); iconv_close(conv); if (outlen > 0 && outbuffer[0] == 0xFEFF /* BOM */) { return std::u32string { outbuffer.data() + 1 }; } else { return std::u32string { outbuffer.data() }; } } #endif template static int toInt(T x); static int toInt(size_t x) { if (x > std::numeric_limits::max()) { throw std::runtime_error("out of range in conversion to int"); } return static_cast(x); } template static unsigned toUInt(T x) { const unsigned result = static_cast(x); if (result != x) { throw std::runtime_error("out of range in conversion to int"); } return x; } template class PartitionGenerator : public Catch::Generators::IGenerator> { public: PartitionGenerator(STR str) : str(str) { lengths.push_back(toInt(str.length())); refreshCache(); } const std::vector& get() const override { return resultCache; } bool next() override { if (std::all_of(begin(lengths), end(lengths), [](int x) {return x == 1;} )) { return false; } unsigned i; for (i = toUInt(lengths.size() - 1); i > 0; i--) { if (lengths[i] != 1) break; } if (i == 0 && lengths[i] == 1) return false; lengths.resize(i + 1); lengths[i] -= 1; lengths.push_back(str.length() - std::accumulate(begin(lengths), begin(lengths) + i + 1, 0)); refreshCache(); return true; } private: void refreshCache() { resultCache.clear(); int current = 0; for (int len: lengths) { resultCache.push_back(str.substr(current, len)); current += len; } } private: STR str; std::vector lengths; std::vector resultCache; }; template Catch::Generators::GeneratorWrapper> partitions(STR str) { #ifdef CATCH3 return Catch::Generators::GeneratorWrapper>(new PartitionGenerator(str)); #else return Catch::Generators::GeneratorWrapper>( std::unique_ptr>>(new PartitionGenerator(str))); #endif } TEST_CASE("test for PartitionGenerator", "[measurement]") { struct TestCase { std::string str; std::vector> splits; }; const auto testCase = GENERATE( TestCase{"a", {{"a"}} }, TestCase{"ab", {{"ab"}, {"a", "b"}} }, TestCase{"abc", {{"abc"}, {"ab", "c"}, {"a", "bc"}, {"a", "b", "c"}} }, TestCase{"abcd", {{"abcd"}, {"abc", "d"}, {"ab", "cd"}, {"ab", "c", "d"}, {"a", "bcd"}, {"a", "bc", "d"}, {"a", "b", "cd"}, {"a", "b", "c", "d"}} } ); PartitionGenerator gen(testCase.str); unsigned int idx = 0; do { REQUIRE(idx < testCase.splits.size()); REQUIRE(gen.get() == testCase.splits[idx]); idx += 1; } while (gen.next()); REQUIRE(idx == testCase.splits.size()); } static std::string char_to_string(char ch) { auto uch = static_cast(ch); return std::to_string(uch); } static std::string char_to_string(char16_t ch) { return std::to_string(ch); } static std::string char_to_string(char32_t ch) { return std::to_string(ch); } template static std::string printPartition(const std::vector &value) { std::string ret = "["; for (const auto& part: value) { if (ret.size() > 1) { ret += ", "; } std::string partInNumbers; for (auto ch: part) { if (partInNumbers.size()) { partInNumbers += ", "; } partInNumbers += char_to_string(ch); } ret += "(" + partInNumbers + ")"; } ret += "]"; return ret; } struct Result { int codeunits; int codepoints; int columns; int clusters; bool limitReached; }; class MeasurementWrapper { public: MeasurementWrapper() { auto free = [](termpaint_integration*){}; auto flush = [](termpaint_integration*){}; auto write = [](termpaint_integration*, const char*, int){}; termpaint_integration_init(&integration, free, write, flush); terminal = termpaint_terminal_new(&integration); measurement = termpaint_text_measurement_new(termpaint_terminal_get_surface(terminal)); } ~MeasurementWrapper() { termpaint_text_measurement_free(measurement); termpaint_terminal_free(terminal); termpaint_integration_deinit(&integration); } termpaint_text_measurement* get() { return measurement; } termpaint_text_measurement* operator->() { return measurement; } termpaint_integration integration; termpaint_terminal *terminal; termpaint_text_measurement* measurement; }; Result measureOneCluster(const std::vector& partition) { MeasurementWrapper tm; termpaint_text_measurement_set_limit_clusters(tm.get(), 1); Result result; for (unsigned i = 0; i < partition.size(); i++) { const std::string &part = partition[i]; const bool last = i == partition.size() - 1; result.limitReached = termpaint_text_measurement_feed_utf8(tm.get(), part.data(), toInt(part.length()), last); if (!last) { REQUIRE(!result.limitReached); } } result.codepoints = termpaint_text_measurement_last_codepoints(tm.get()); result.codeunits = termpaint_text_measurement_last_ref(tm.get()); result.columns = termpaint_text_measurement_last_width(tm.get()); result.clusters = termpaint_text_measurement_last_clusters(tm.get()); return result; } Result measureOneCluster(const std::vector& partition) { MeasurementWrapper tm; termpaint_text_measurement_set_limit_clusters(tm.get(), 1); Result result; for (unsigned i = 0; i < partition.size(); i++) { const std::u16string &part = partition[i]; const bool last = i == partition.size() - 1; result.limitReached = termpaint_text_measurement_feed_utf16(tm.get(), reinterpret_cast(part.data()), toInt(part.length()), last); if (!last) { REQUIRE(!result.limitReached); } } result.codepoints = termpaint_text_measurement_last_codepoints(tm.get()); result.codeunits = termpaint_text_measurement_last_ref(tm.get()); result.columns = termpaint_text_measurement_last_width(tm.get()); result.clusters = termpaint_text_measurement_last_clusters(tm.get()); return result; } Result measureOneCluster(const std::vector& partition) { MeasurementWrapper tm; termpaint_text_measurement_set_limit_clusters(tm.get(), 1); Result result; for (unsigned i = 0; i < partition.size(); i++) { const std::u32string &part = partition[i]; const bool last = i == partition.size() - 1; result.limitReached = termpaint_text_measurement_feed_utf32(tm.get(), reinterpret_cast(part.data()), toInt(part.length()), last); if (!last) { REQUIRE(!result.limitReached); } } result.codepoints = termpaint_text_measurement_last_codepoints(tm.get()); result.codeunits = termpaint_text_measurement_last_ref(tm.get()); result.columns = termpaint_text_measurement_last_width(tm.get()); result.clusters = termpaint_text_measurement_last_clusters(tm.get()); return result; } TEST_CASE( "Measurements for single clusters", "[measurement]") { Result result; struct TestCase { const std::string str; int columns; std::string desc; }; const auto testCase = GENERATE( TestCase{"A", 1, "plain latin letter"}, TestCase{"が", 2, "plain hiragana"}, TestCase{"\xcc\x88", 1, "isolated U+0308 combining diaeresis"}, TestCase{"a\xcc\x88", 1, "'a' + U+0308 combining diaeresis"}, TestCase{"a\xcc\x88\xcc\xa4", 1, "'a' + U+0308 combining diaeresis + U+0324 combining diaeresis below"}, TestCase{"a\xf3\xa0\x84\x80\xf3\xa0\x84\x81", 1, "'a' + U+E0100 variation selector-17 + U+E0101 variation selector-18 (nonsense)"}, TestCase{"\x7f", 1, "erase marker"} ); std::u32string utf32 = toUtf32(testCase.str); SECTION("parse as utf8") { auto partition = GENERATE_COPY(partitions(testCase.str)); INFO(testCase.desc); INFO("Partition: " << printPartition(partition)); INFO("Checking for string " << testCase.str); result = measureOneCluster(partition); CHECK(result.limitReached == true); CHECK(result.columns == testCase.columns); CHECK(result.codeunits == toInt(testCase.str.length())); CHECK(result.codepoints == toInt(utf32.length())); } SECTION("parse as utf16") { INFO(testCase.desc); INFO("Checking for string " << testCase.str); std::u16string utf16 = toUtf16(testCase.str); auto partition = GENERATE_COPY(partitions(utf16)); INFO("Partition: " << printPartition(partition)); result = measureOneCluster(partition); CHECK(result.limitReached == true); CHECK(result.columns == testCase.columns); CHECK(result.codeunits == toInt(utf16.length())); CHECK(result.codepoints == toInt(utf32.length())); } SECTION("parse as utf32") { INFO(testCase.desc); INFO("Checking for string " << testCase.str); auto partition = GENERATE_COPY(partitions(utf32)); INFO("Partition: " << printPartition(partition)); result = measureOneCluster(partition); CHECK(result.limitReached == true); CHECK(result.columns == testCase.columns); CHECK(result.codeunits == toInt(utf32.length())); CHECK(result.codepoints == toInt(utf32.length())); } } Result measureTest(const std::vector& partition, int limCodepoints, int limClusters, int limWidth, int limCodeunits) { MeasurementWrapper tm; termpaint_text_measurement_set_limit_codepoints(tm.get(), limCodepoints); termpaint_text_measurement_set_limit_clusters(tm.get(), limClusters); termpaint_text_measurement_set_limit_width(tm.get(), limWidth); termpaint_text_measurement_set_limit_ref(tm.get(), limCodeunits); Result result; for (unsigned i = 0; i < partition.size(); i++) { const std::string &part = partition[i]; const bool last = i == partition.size() - 1; result.limitReached = termpaint_text_measurement_feed_utf8(tm.get(), part.data(), toInt(part.length()), last); if (result.limitReached) { break; } } result.codepoints = termpaint_text_measurement_last_codepoints(tm.get()); result.codeunits = termpaint_text_measurement_last_ref(tm.get()); result.columns = termpaint_text_measurement_last_width(tm.get()); result.clusters = termpaint_text_measurement_last_clusters(tm.get()); return result; } Result measureTest(const std::vector& partition, int limCodepoints, int limClusters, int limWidth, int limCodeunits) { MeasurementWrapper tm; termpaint_text_measurement_set_limit_codepoints(tm.get(), limCodepoints); termpaint_text_measurement_set_limit_clusters(tm.get(), limClusters); termpaint_text_measurement_set_limit_width(tm.get(), limWidth); termpaint_text_measurement_set_limit_ref(tm.get(), limCodeunits); Result result; for (unsigned i = 0; i < partition.size(); i++) { const std::u16string &part = partition[i]; const bool last = i == partition.size() - 1; result.limitReached = termpaint_text_measurement_feed_utf16(tm.get(), reinterpret_cast(part.data()), toInt(part.length()), last); if (result.limitReached) { break; } } result.codepoints = termpaint_text_measurement_last_codepoints(tm.get()); result.codeunits = termpaint_text_measurement_last_ref(tm.get()); result.columns = termpaint_text_measurement_last_width(tm.get()); result.clusters = termpaint_text_measurement_last_clusters(tm.get()); return result; } Result measureTest(const std::vector& partition, int limCodepoints, int limClusters, int limWidth, int limCodeunits) { MeasurementWrapper tm; termpaint_text_measurement_set_limit_codepoints(tm.get(), limCodepoints); termpaint_text_measurement_set_limit_clusters(tm.get(), limClusters); termpaint_text_measurement_set_limit_width(tm.get(), limWidth); termpaint_text_measurement_set_limit_ref(tm.get(), limCodeunits); Result result; for (unsigned i = 0; i < partition.size(); i++) { const std::u32string &part = partition[i]; const bool last = i == partition.size() - 1; result.limitReached = termpaint_text_measurement_feed_utf32(tm.get(), reinterpret_cast(part.data()), toInt(part.length()), last); if (result.limitReached) { break; } } result.codepoints = termpaint_text_measurement_last_codepoints(tm.get()); result.codeunits = termpaint_text_measurement_last_ref(tm.get()); result.columns = termpaint_text_measurement_last_width(tm.get()); result.clusters = termpaint_text_measurement_last_clusters(tm.get()); return result; } struct ExpectedMeasures { int codeunits = 0; int width = 0; int codepoints = 0; int clusters = 0; template void addCluster(C cluster) { codeunits += cluster.str.size(); width += cluster.columns; codepoints += toUtf32(cluster.str).size(); ++clusters; } template void addClusterUtf16(C cluster) { codeunits += toUtf16(cluster.str).size(); width += cluster.columns; codepoints += toUtf32(cluster.str).size(); ++clusters; } template void addClusterUtf32(C cluster) { int sizeInUtf32 = toUtf32(cluster.str).size(); codeunits += sizeInUtf32; codepoints += sizeInUtf32; width += cluster.columns; ++clusters; } }; TEST_CASE( "Measurements for strings", "[measurement]") { struct C { const std::string str; int columns; }; struct TestCase { const std::vector data; std::string desc; }; const auto testCase = GENERATE( TestCase{ {C{"A", 1}, C{"b", 1}, C{"c", 1}, C{"d", 1} }, "Latin Abcde"}, TestCase{ {C{"A", 1}, C{"b\xcc\x88", 1}, C{"c", 1}, C{"d", 1} }, "Latin Abcde with U+0308 combining diaeresis after b"}, TestCase{ {C{"A", 1}, C{"b", 1}, C{"c\xcc\x88\xcc\xa4", 1}, C{"d", 1} }, "Latin Abcde with U+0308 combining diaeresis + U+0324 combining diaeresis below after c"}, TestCase{ {C{"\xcc\x88", 1} } , "isolated U+0308 combining diaeresis"}, TestCase{ {C{"A", 1}, C{"が", 2}, C{"c", 1}, C{"d", 1} }, "Latin A followed by plain hiragana and latin cde"}, TestCase{ {C{"A", 1}, C{"\xF0\x9B\x80\x80", 2}, C{"d", 1} }, "Latin A followed by U+1B000 katakana letter archaic e and latin cde"}, TestCase{ {C{"A", 1}, C{"\xF0\x9F\x8D\x92", 2}, C{"d", 1} }, "Latin A followed by U+1F352 cherries and latin cde"}, TestCase{ {C{"\x7f", 1}, C{"b", 1} }, "erase marker plus b"}, TestCase{ {C{"a", 1}, C{"\x7f", 1}, C{"b", 1} }, "a plus erase marker plus b"}, TestCase{ {C{"\x7f", 1}, C{"\xcc\x88", 1} } , "erase marker plus U+0308 combining diaeresis"} ); INFO(testCase.desc); SECTION("utf8 - codeunits") { std::string all; for (const C& cluster: testCase.data) { all += cluster.str; } const int size = toInt(all.size()); const int len = GENERATE_COPY(range(0, size)); auto partition = GENERATE_COPY(partitions(all)); INFO("len: " << len); INFO("Partition: " << printPartition(partition)); ExpectedMeasures expected; for (const C& cluster: testCase.data) { if (expected.codeunits + toInt(cluster.str.size()) > len) break; expected.addCluster(cluster); } Result result = measureTest(partition, -1, -1, -1, len); CHECK(result.columns == expected.width); CHECK(result.codeunits == expected.codeunits); CHECK(result.codepoints == expected.codepoints); CHECK(result.clusters == expected.clusters); } SECTION("utf8 - codepoints") { std::string all; for (const C& cluster: testCase.data) { all += cluster.str; } int maxCodepoints = toUtf32(all).size(); const int codepointsLimit = GENERATE_COPY(range(0, maxCodepoints)); auto partition = GENERATE_COPY(partitions(all)); INFO("codepointsLimit: " << codepointsLimit); INFO("Partition: " << printPartition(partition)); ExpectedMeasures expected; for (const C& cluster: testCase.data) { if (expected.codepoints + toInt(toUtf32(cluster.str).size()) > codepointsLimit) break; expected.addCluster(cluster); } Result result = measureTest(partition, codepointsLimit, -1, -1, -1); CHECK(result.codeunits == expected.codeunits); CHECK(result.columns == expected.width); CHECK(result.codepoints == expected.codepoints); CHECK(result.clusters == expected.clusters); } SECTION("utf8 - width") { std::string all; int maxWidth = 0; for (const C& cluster: testCase.data) { all += cluster.str; maxWidth += cluster.columns; } const int widthLimit = GENERATE_COPY(range(0, maxWidth)); auto partition = GENERATE_COPY(partitions(all)); CAPTURE(widthLimit); INFO("Partition: " << printPartition(partition)); ExpectedMeasures expected; for (const C& cluster: testCase.data) { if (expected.width + cluster.columns > widthLimit) break; expected.addCluster(cluster); } Result result = measureTest(partition, -1, -1, widthLimit, -1); CHECK(result.codeunits == expected.codeunits); CHECK(result.columns == expected.width); CHECK(result.codepoints == expected.codepoints); CHECK(result.clusters == expected.clusters); } SECTION("utf8 - clusters") { std::string all; int maxClusters = 0; for (const C& cluster: testCase.data) { all += cluster.str; ++maxClusters; } const int clusterLimit = GENERATE_COPY(range(0, maxClusters)); auto partition = GENERATE_COPY(partitions(all)); CAPTURE(clusterLimit); INFO("Partition: " << printPartition(partition)); ExpectedMeasures expected; for (const C& cluster: testCase.data) { if (expected.clusters + 1 > clusterLimit) break; expected.addCluster(cluster); } Result result = measureTest(partition, -1, clusterLimit, -1, -1); CHECK(result.codeunits == expected.codeunits); CHECK(result.columns == expected.width); CHECK(result.codepoints == expected.codepoints); CHECK(result.clusters == expected.clusters); } // ---------------------- SECTION("utf16 - codeunits") { std::u16string all; for (const C& cluster: testCase.data) { all += toUtf16(cluster.str); } const int size = toInt(all.size()); const int len = GENERATE_COPY(range(0, size)); auto partition = GENERATE_COPY(partitions(all)); CAPTURE(len); INFO("Partition: " << printPartition(partition)); ExpectedMeasures expected; for (const C& cluster: testCase.data) { if (expected.codeunits + toInt(toUtf16(cluster.str).size()) > len) break; expected.addClusterUtf16(cluster); } Result result = measureTest(partition, -1, -1, -1, len); CHECK(result.columns == expected.width); CHECK(result.codeunits == expected.codeunits); CHECK(result.codepoints == expected.codepoints); CHECK(result.clusters == expected.clusters); } SECTION("utf16 - codepoints") { std::u16string all; for (const C& cluster: testCase.data) { all += toUtf16(cluster.str); } int maxCodepoints = toUtf32(all).size(); const int codepointsLimit = GENERATE_COPY(range(0, maxCodepoints)); auto partition = GENERATE_COPY(partitions(all)); CAPTURE(codepointsLimit); INFO("Partition: " << printPartition(partition)); ExpectedMeasures expected; for (const C& cluster: testCase.data) { if (expected.codepoints + toInt(toUtf32(cluster.str).size()) > codepointsLimit) break; expected.addClusterUtf16(cluster); } Result result = measureTest(partition, codepointsLimit, -1, -1, -1); CHECK(result.codeunits == expected.codeunits); CHECK(result.columns == expected.width); CHECK(result.codepoints == expected.codepoints); CHECK(result.clusters == expected.clusters); } SECTION("utf16 - width") { std::u16string all; int maxWidth = 0; for (const C& cluster: testCase.data) { all += toUtf16(cluster.str); maxWidth += cluster.columns; } const int widthLimit = GENERATE_COPY(range(0, maxWidth)); auto partition = GENERATE_COPY(partitions(all)); CAPTURE(widthLimit); INFO("Partition: " << printPartition(partition)); ExpectedMeasures expected; for (const C& cluster: testCase.data) { if (expected.width + cluster.columns > widthLimit) break; expected.addClusterUtf16(cluster); } Result result = measureTest(partition, -1, -1, widthLimit, -1); CHECK(result.codeunits == expected.codeunits); CHECK(result.columns == expected.width); CHECK(result.codepoints == expected.codepoints); CHECK(result.clusters == expected.clusters); } SECTION("utf16 - clusters") { std::u16string all; int maxClusters = 0; for (const C& cluster: testCase.data) { all += toUtf16(cluster.str); ++maxClusters; } const int clusterLimit = GENERATE_COPY(range(0, maxClusters)); auto partition = GENERATE_COPY(partitions(all)); CAPTURE(clusterLimit); INFO("Partition: " << printPartition(partition)); ExpectedMeasures expected; for (const C& cluster: testCase.data) { if (expected.clusters + 1 > clusterLimit) break; expected.addClusterUtf16(cluster); } Result result = measureTest(partition, -1, clusterLimit, -1, -1); CHECK(result.codeunits == expected.codeunits); CHECK(result.columns == expected.width); CHECK(result.codepoints == expected.codepoints); CHECK(result.clusters == expected.clusters); } // ---------------------- SECTION("utf32 - codeunits") { std::u32string all; for (const C& cluster: testCase.data) { all += toUtf32(cluster.str); } const int size = toInt(all.size()); const int len = GENERATE_COPY(range(0, size)); auto partition = GENERATE_COPY(partitions(all)); CAPTURE(len); INFO("Partition: " << printPartition(partition)); ExpectedMeasures expected; for (const C& cluster: testCase.data) { if (expected.codeunits + toInt(toUtf32(cluster.str).size()) > len) break; expected.addClusterUtf32(cluster); } Result result = measureTest(partition, -1, -1, -1, len); CHECK(result.columns == expected.width); CHECK(result.codeunits == expected.codeunits); CHECK(result.codepoints == expected.codepoints); CHECK(result.clusters == expected.clusters); } SECTION("utf32 - codepoints") { std::u32string all; for (const C& cluster: testCase.data) { all += toUtf32(cluster.str); } int maxCodepoints = all.size(); const int codepointsLimit = GENERATE_COPY(range(0, maxCodepoints)); auto partition = GENERATE_COPY(partitions(all)); CAPTURE(codepointsLimit); INFO("Partition: " << printPartition(partition)); ExpectedMeasures expected; for (const C& cluster: testCase.data) { if (expected.codepoints + toInt(toUtf32(cluster.str).size()) > codepointsLimit) break; expected.addClusterUtf32(cluster); } Result result = measureTest(partition, codepointsLimit, -1, -1, -1); CHECK(result.codeunits == expected.codeunits); CHECK(result.columns == expected.width); CHECK(result.codepoints == expected.codepoints); CHECK(result.clusters == expected.clusters); } SECTION("utf32 - width") { std::u32string all; int maxWidth = 0; for (const C& cluster: testCase.data) { all += toUtf32(cluster.str); maxWidth += cluster.columns; } const int widthLimit = GENERATE_COPY(range(0, maxWidth)); auto partition = GENERATE_COPY(partitions(all)); CAPTURE(widthLimit); INFO("Partition: " << printPartition(partition)); ExpectedMeasures expected; for (const C& cluster: testCase.data) { if (expected.width + cluster.columns > widthLimit) break; expected.addClusterUtf32(cluster); } Result result = measureTest(partition, -1, -1, widthLimit, -1); CHECK(result.codeunits == expected.codeunits); CHECK(result.columns == expected.width); CHECK(result.codepoints == expected.codepoints); CHECK(result.clusters == expected.clusters); } SECTION("utf32 - clusters") { std::u32string all; int maxClusters = 0; for (const C& cluster: testCase.data) { all += toUtf32(cluster.str); ++maxClusters; } const int clusterLimit = GENERATE_COPY(range(0, maxClusters)); auto partition = GENERATE_COPY(partitions(all)); CAPTURE(clusterLimit); INFO("Partition: " << printPartition(partition)); ExpectedMeasures expected; for (const C& cluster: testCase.data) { if (expected.clusters + 1 > clusterLimit) break; expected.addClusterUtf32(cluster); } Result result = measureTest(partition, -1, clusterLimit, -1, -1); CHECK(result.codeunits == expected.codeunits); CHECK(result.columns == expected.width); CHECK(result.codepoints == expected.codepoints); CHECK(result.clusters == expected.clusters); } } TEST_CASE( "Continue measurements for strings", "[measurement]") { struct S { const std::string str; int columns; }; struct TestCase { std::vector segs; std::string desc; }; const auto testCase = GENERATE( TestCase{ { {"Ab", 2}, {"c", 1}, {"de", 2} }, "Latin Abcde"} ); INFO(testCase.desc); SECTION("utf8 - width") { std::string all; for (const S& segment: testCase.segs) { all += segment.str; } MeasurementWrapper tm; int limWidth = 0; int expectedCodeunits = 0; int previousRef = 0; for (const S& segment: testCase.segs) { limWidth += segment.columns; expectedCodeunits += segment.str.size(); termpaint_text_measurement_set_limit_width(tm.get(), limWidth); bool limitReached = termpaint_text_measurement_feed_utf8(tm.get(), all.data() + previousRef, toInt(all.length()) - previousRef, true); int codeunits = termpaint_text_measurement_last_ref(tm.get()); int columns = termpaint_text_measurement_last_width(tm.get()); CHECK(limitReached); CHECK(codeunits == expectedCodeunits); CHECK(columns == limWidth); previousRef = codeunits; } } SECTION("utf16 - width") { std::u16string all; for (const S& segment: testCase.segs) { all += toUtf16(segment.str); } MeasurementWrapper tm; int limWidth = 0; int expectedCodeunits = 0; int previousRef = 0; for (const S& segment: testCase.segs) { limWidth += segment.columns; expectedCodeunits += toUtf16(segment.str).size(); termpaint_text_measurement_set_limit_width(tm.get(), limWidth); bool limitReached = termpaint_text_measurement_feed_utf16(tm.get(), reinterpret_cast(all.data()) + previousRef, toInt(all.length()) - previousRef, true); int codeunits = termpaint_text_measurement_last_ref(tm.get()); int columns = termpaint_text_measurement_last_width(tm.get()); CHECK(limitReached); CHECK(codeunits == expectedCodeunits); CHECK(columns == limWidth); previousRef = codeunits; } } SECTION("utf32 - width") { std::u32string all; for (const S& segment: testCase.segs) { all += toUtf32(segment.str); } MeasurementWrapper tm; int limWidth = 0; int expectedCodeunits = 0; int previousRef = 0; for (const S& segment: testCase.segs) { limWidth += segment.columns; expectedCodeunits += toUtf32(segment.str).size(); termpaint_text_measurement_set_limit_width(tm.get(), limWidth); bool limitReached = termpaint_text_measurement_feed_utf32(tm.get(), reinterpret_cast(all.data()) + previousRef, toInt(all.length()) - previousRef, true); int codeunits = termpaint_text_measurement_last_ref(tm.get()); int columns = termpaint_text_measurement_last_width(tm.get()); CHECK(limitReached); CHECK(codeunits == expectedCodeunits); CHECK(columns == limWidth); previousRef = codeunits; } } } termpaint-0.3.1/tests/stdin_input.c000066400000000000000000000007551477303547200174070ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include #include #include #include "../termpaint_input.h" void null_callback(void *data, termpaint_event *event) { } int main() { char buff[10000]; int amount = read(0, buff, 10000); termpaint_input *input_ctx = termpaint_input_new(); termpaint_input_set_event_cb(input_ctx, null_callback, 0); termpaint_input_add_data(input_ctx, buff, amount); termpaint_input_free(input_ctx); return 0; } termpaint-0.3.1/tests/surface.cpp000066400000000000000000004652401477303547200170430ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include #include #include #ifndef BUNDLED_CATCH2 #ifdef CATCH3 #include "catch2/catch_all.hpp" #else #include "catch2/catch.hpp" #endif #else #include "../third-party/catch.hpp" #endif #include namespace { template using DEL = void(T*); template del> struct Deleter{ void operator()(T* t) { del(t); } }; template del> struct unique_cptr : public std::unique_ptr> { operator T*() { return this->get(); } static unique_cptr take_ownership(T* owning_raw_ptr) { unique_cptr ret; ret.reset(owning_raw_ptr); return ret; } }; using uattr_ptr = unique_cptr; using usurface_ptr = unique_cptr; struct Fixture { Fixture(int width, int height) { auto free = [] (termpaint_integration* ptr) { termpaint_integration_deinit(ptr); }; auto write = [] (termpaint_integration* ptr, const char *data, int length) { (void)ptr; (void)data; (void)length; }; auto flush = [] (termpaint_integration* ptr) { (void)ptr; }; termpaint_integration_init(&integration, free, write, flush); terminal.reset(termpaint_terminal_new(&integration)); surface = termpaint_terminal_get_surface(terminal); termpaint_surface_resize(surface, width, height); termpaint_terminal_set_event_cb(terminal, [](void *, termpaint_event *) {}, nullptr); } unique_cptr terminal; termpaint_surface *surface; termpaint_integration integration; }; class Cell { public: std::string data; uint32_t fg = TERMPAINT_DEFAULT_COLOR; uint32_t bg = TERMPAINT_DEFAULT_COLOR; uint32_t deco = TERMPAINT_DEFAULT_COLOR; int style = 0; int width = 1; std::string setup; std::string cleanup; bool optimize = false; bool softWrapMarker = false; public: Cell withFg(uint32_t val) { auto r = *this; r.fg = val; return r; } Cell withBg(uint32_t val) { auto r = *this; r.bg = val; return r; } Cell withDeco(uint32_t val) { auto r = *this; r.deco = val; return r; } Cell withStyle(int val) { auto r = *this; r.style = val; return r; } Cell withPatch(bool o, std::string s, std::string c) { auto r = *this; r.setup = s; r.cleanup = c; r.optimize = o; return r; } Cell withSoftWrapMarker() { auto r = *this; r.softWrapMarker = true; return r; } }; Cell singleWideChar(std::string ch) { Cell c; c.data = ch; return c; } Cell doubleWideChar(std::string ch) { Cell c; c.data = ch; c.width = 2; return c; } Cell readCell(termpaint_surface *surface, int x, int y) { Cell cell; int len, left, right; const char *text = termpaint_surface_peek_text(surface, x, y, &len, &left, &right); if (left != x) { std::terminate(); } cell.data = std::string(text, len); cell.width = right - left + 1; cell.fg = termpaint_surface_peek_fg_color(surface, x, y); cell.bg = termpaint_surface_peek_bg_color(surface, x, y); cell.deco = termpaint_surface_peek_deco_color(surface, x, y); cell.style = termpaint_surface_peek_style(surface, x, y); cell.softWrapMarker = termpaint_surface_peek_softwrap_marker(surface, x, y); const char* setup; const char* cleanup; bool optimize; termpaint_surface_peek_patch(surface, x, y, &setup, &cleanup, &optimize); if (setup || cleanup) { cell.setup = setup; cell.cleanup = cleanup; cell.optimize = optimize; } return cell; } static void checkEmptyPlusSome(termpaint_surface *surface, const std::map, Cell> &some, Cell empty = singleWideChar(TERMPAINT_ERASED)) { const int width = termpaint_surface_width(surface); const int height = termpaint_surface_height(surface); for (int y = 0; y < height; y++) { for (int x = 0; x < width; /* see loop body */ ) { Cell cell = readCell(surface, x, y); CAPTURE(x); CAPTURE(y); const Cell *expected;; if (some.count({x, y})) { expected = &some.at({x, y}); } else { expected = ∅ } CHECK(cell.bg == expected->bg); CHECK(cell.fg == expected->fg); CHECK(cell.deco == expected->deco); CHECK(cell.data == expected->data); CHECK(cell.style == expected->style); CHECK(cell.width == expected->width); CHECK(cell.setup == expected->setup); CHECK(cell.cleanup == expected->cleanup); CHECK(cell.optimize == expected->optimize); CHECK(cell.softWrapMarker == expected->softWrapMarker); x += cell.width; } } } } TEST_CASE("simple") { Fixture f{80, 24}; CHECK(termpaint_surface_width(f.surface) == 80); CHECK(termpaint_surface_height(f.surface) == 24); checkEmptyPlusSome(f.surface, {}); } TEST_CASE("resize") { Fixture f{80, 24}; CHECK(termpaint_surface_width(f.surface) == 80); CHECK(termpaint_surface_height(f.surface) == 24); checkEmptyPlusSome(f.surface, {}); termpaint_surface_resize(f.surface, 120, 40); CHECK(termpaint_surface_width(f.surface) == 120); CHECK(termpaint_surface_height(f.surface) == 40); checkEmptyPlusSome(f.surface, {}); } TEST_CASE("resize - oversized") { Fixture f{80, 24}; termpaint_surface_resize(f.surface, std::numeric_limits::max() / 2, std::numeric_limits::max() / 2); CHECK(termpaint_surface_width(f.surface) == 0); CHECK(termpaint_surface_height(f.surface) == 0); } TEST_CASE("simple text") { Fixture f{80, 6}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 10, 3, "Sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); checkEmptyPlusSome(f.surface, { {{ 10, 3 }, singleWideChar("S")}, {{ 11, 3 }, singleWideChar("a")}, {{ 12, 3 }, singleWideChar("m")}, {{ 13, 3 }, singleWideChar("p")}, {{ 14, 3 }, singleWideChar("l")}, {{ 15, 3 }, singleWideChar("e")}, }); } TEST_CASE("simple text - with len and color") { Fixture f{80, 6}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_len_colors(f.surface, 10, 3, "SampleX", 6, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLACK); checkEmptyPlusSome(f.surface, { {{ 10, 3 }, singleWideChar("S").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK)}, {{ 11, 3 }, singleWideChar("a").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK)}, {{ 12, 3 }, singleWideChar("m").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK)}, {{ 13, 3 }, singleWideChar("p").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK)}, {{ 14, 3 }, singleWideChar("l").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK)}, {{ 15, 3 }, singleWideChar("e").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK)}, }); } TEST_CASE("simple text - with len, color and clipped") { Fixture f{80, 6}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_len_colors_clipped(f.surface, 10, 3, "SampleX", 6, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLACK, 12, 80); checkEmptyPlusSome(f.surface, { {{ 12, 3 }, singleWideChar("m").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK)}, {{ 13, 3 }, singleWideChar("p").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK)}, {{ 14, 3 }, singleWideChar("l").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK)}, {{ 15, 3 }, singleWideChar("e").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK)}, }); } TEST_CASE("simple text - with wide chars, len, color and clipped") { Fixture f{80, 6}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_len_colors_clipped(f.surface, -1, 3, "あえ", strlen("あえ"), TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLACK, -1, 1); checkEmptyPlusSome(f.surface, { {{ 0, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK)}, {{ 1, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK)}, }); } TEST_CASE("simple text - with len and attr") { Fixture f{80, 6}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_attr *attr = termpaint_attr_new(TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLACK); termpaint_attr_set_style(attr, TERMPAINT_STYLE_BOLD); termpaint_surface_write_with_len_attr_clipped(f.surface, 10, 3, "SampleX", 6, attr, 0, 80); checkEmptyPlusSome(f.surface, { {{ 10, 3 }, singleWideChar("S").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK).withStyle(TERMPAINT_STYLE_BOLD)}, {{ 11, 3 }, singleWideChar("a").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK).withStyle(TERMPAINT_STYLE_BOLD)}, {{ 12, 3 }, singleWideChar("m").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK).withStyle(TERMPAINT_STYLE_BOLD)}, {{ 13, 3 }, singleWideChar("p").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK).withStyle(TERMPAINT_STYLE_BOLD)}, {{ 14, 3 }, singleWideChar("l").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK).withStyle(TERMPAINT_STYLE_BOLD)}, {{ 15, 3 }, singleWideChar("e").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK).withStyle(TERMPAINT_STYLE_BOLD)}, }); termpaint_attr_free(attr); } TEST_CASE("simple text - with len, attr and clipped") { Fixture f{80, 6}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_attr *attr = termpaint_attr_new(TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLACK); termpaint_attr_set_style(attr, TERMPAINT_STYLE_BOLD); termpaint_surface_write_with_len_attr_clipped(f.surface, 10, 3, "SampleX", 6, attr, 12, 80); checkEmptyPlusSome(f.surface, { {{ 12, 3 }, singleWideChar("m").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK).withStyle(TERMPAINT_STYLE_BOLD)}, {{ 13, 3 }, singleWideChar("p").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK).withStyle(TERMPAINT_STYLE_BOLD)}, {{ 14, 3 }, singleWideChar("l").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK).withStyle(TERMPAINT_STYLE_BOLD)}, {{ 15, 3 }, singleWideChar("e").withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLACK).withStyle(TERMPAINT_STYLE_BOLD)}, }); termpaint_attr_free(attr); } TEST_CASE("double width") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 3, 3, "あえ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); checkEmptyPlusSome(f.surface, { {{ 3, 3 }, doubleWideChar("あ")}, {{ 5, 3 }, doubleWideChar("え")}, }); int len, left, right; const char* data = termpaint_surface_peek_text(f.surface, 3, 3, &len, &left, &right); CHECK(left == 3); CHECK(right == 4); CHECK(len == strlen("あ")); CHECK(std::string(data, len) == std::string("あ")); data = termpaint_surface_peek_text(f.surface, 4, 3, &len, &left, &right); CHECK(left == 3); CHECK(right == 4); CHECK(len == strlen("あ")); CHECK(std::string(data, len) == std::string("あ")); } TEST_CASE("chars that get substituted") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 3, 3, "a\004\u00ad\u0088x", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); checkEmptyPlusSome(f.surface, { {{ 3, 3 }, singleWideChar("a")}, {{ 4, 3 }, singleWideChar(" ")}, {{ 5, 3 }, singleWideChar("-")}, {{ 6, 3 }, singleWideChar(" ")}, {{ 7, 3 }, singleWideChar("x")}, }); } TEST_CASE("write clear char") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 3, 3, "a\x7fx", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 3, 4, "\x7fx", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 3, 5, "\x7f\u0308", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); checkEmptyPlusSome(f.surface, { {{ 3, 3 }, singleWideChar("a")}, {{ 4, 3 }, singleWideChar(TERMPAINT_ERASED)}, {{ 5, 3 }, singleWideChar("x")}, {{ 3, 4 }, singleWideChar(TERMPAINT_ERASED)}, {{ 4, 4 }, singleWideChar("x")}, {{ 3, 5 }, singleWideChar(TERMPAINT_ERASED)}, {{ 4, 5 }, singleWideChar("\u00a0\u0308")}, }); } TEST_CASE("vanish chars") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 3, 3, "あえ", TERMPAINT_COLOR_RED, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors(f.surface, 4, 3, "ab", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_BLUE); checkEmptyPlusSome(f.surface, { {{ 3, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_GREEN).withFg(TERMPAINT_COLOR_RED)}, {{ 4, 3 }, singleWideChar("a").withBg(TERMPAINT_COLOR_BLUE).withFg(TERMPAINT_COLOR_YELLOW)}, {{ 5, 3 }, singleWideChar("b").withBg(TERMPAINT_COLOR_BLUE).withFg(TERMPAINT_COLOR_YELLOW)}, {{ 6, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_GREEN).withFg(TERMPAINT_COLOR_RED)}, }); } TEST_CASE("vanish chars - clipping right") { // regression test for consistency violation in termpaint_surface_write_with_len_attr_clipped // leading to crash when rendering Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 3, 3, "あえ", TERMPAINT_COLOR_RED, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors_clipped(f.surface, 3, 3, "あえ", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_BLUE, 0, 5); checkEmptyPlusSome(f.surface, { {{ 3, 3 }, doubleWideChar("あ").withBg(TERMPAINT_COLOR_BLUE).withFg(TERMPAINT_COLOR_YELLOW)}, {{ 5, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BLUE).withFg(TERMPAINT_COLOR_YELLOW)}, {{ 6, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_GREEN).withFg(TERMPAINT_COLOR_RED)}, }); } TEST_CASE("vanish chars - clipping left") { // regression test for consistency violation in termpaint_surface_write_with_len_attr_clipped // leading to out of range cell access in termpaint_surface_vanish_char Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 2, 3, "abあ", TERMPAINT_COLOR_RED, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors_clipped(f.surface, 3, 3, "あえ", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_BLUE, 4, 6); checkEmptyPlusSome(f.surface, { {{ 2, 3 }, singleWideChar("a").withBg(TERMPAINT_COLOR_GREEN).withFg(TERMPAINT_COLOR_RED)}, {{ 3, 3 }, singleWideChar("b").withBg(TERMPAINT_COLOR_GREEN).withFg(TERMPAINT_COLOR_RED)}, {{ 4, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BLUE).withFg(TERMPAINT_COLOR_YELLOW)}, {{ 5, 3 }, doubleWideChar("え").withBg(TERMPAINT_COLOR_BLUE).withFg(TERMPAINT_COLOR_YELLOW)}, }); } TEST_CASE("vanish chars - misaligned wide") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 3, 3, "あえ", TERMPAINT_COLOR_RED, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors(f.surface, 4, 3, "ア", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_BLUE); checkEmptyPlusSome(f.surface, { {{ 3, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_GREEN).withFg(TERMPAINT_COLOR_RED)}, {{ 4, 3 }, doubleWideChar("ア").withBg(TERMPAINT_COLOR_BLUE).withFg(TERMPAINT_COLOR_YELLOW)}, {{ 6, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_GREEN).withFg(TERMPAINT_COLOR_RED)}, }); } TEST_CASE("rgb colors") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 3, 3, "r", TERMPAINT_RGB_COLOR(255, 128, 128), TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 4, 3, "g", TERMPAINT_RGB_COLOR(128, 255, 128), TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 5, 3, "b", TERMPAINT_RGB_COLOR(128, 128, 255), TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 3, 4, "r", TERMPAINT_DEFAULT_COLOR, TERMPAINT_RGB_COLOR(255, 128, 128)); termpaint_surface_write_with_colors(f.surface, 4, 4, "g", TERMPAINT_DEFAULT_COLOR, TERMPAINT_RGB_COLOR(128, 255, 128)); termpaint_surface_write_with_colors(f.surface, 5, 4, "b", TERMPAINT_DEFAULT_COLOR, TERMPAINT_RGB_COLOR(128, 128, 255)); checkEmptyPlusSome(f.surface, { {{ 3, 3 }, singleWideChar("r").withFg(TERMPAINT_RGB_COLOR(0xff, 0x80, 0x80))}, {{ 4, 3 }, singleWideChar("g").withFg(TERMPAINT_RGB_COLOR(0x80, 0xff, 0x80))}, {{ 5, 3 }, singleWideChar("b").withFg(TERMPAINT_RGB_COLOR(0x80, 0x80, 0xff))}, {{ 3, 4 }, singleWideChar("r").withBg(TERMPAINT_RGB_COLOR(0xff, 0x80, 0x80))}, {{ 4, 4 }, singleWideChar("g").withBg(TERMPAINT_RGB_COLOR(0x80, 0xff, 0x80))}, {{ 5, 4 }, singleWideChar("b").withBg(TERMPAINT_RGB_COLOR(0x80, 0x80, 0xff))}, }); } TEST_CASE("named fg colors") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 3, 3, " ", TERMPAINT_COLOR_BLACK, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 4, 3, " ", TERMPAINT_COLOR_RED, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 5, 3, " ", TERMPAINT_COLOR_GREEN, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 6, 3, " ", TERMPAINT_COLOR_YELLOW, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 7, 3, " ", TERMPAINT_COLOR_BLUE, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 8, 3, " ", TERMPAINT_COLOR_MAGENTA, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 9, 3, " ", TERMPAINT_COLOR_CYAN, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 10, 3, " ", TERMPAINT_COLOR_LIGHT_GREY, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 3, 4, " ", TERMPAINT_COLOR_DARK_GREY, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 4, 4, " ", TERMPAINT_COLOR_BRIGHT_RED, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 5, 4, " ", TERMPAINT_COLOR_BRIGHT_GREEN, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 6, 4, " ", TERMPAINT_COLOR_BRIGHT_YELLOW, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 7, 4, " ", TERMPAINT_COLOR_BRIGHT_BLUE, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 8, 4, " ", TERMPAINT_COLOR_BRIGHT_MAGENTA, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 9, 4, " ", TERMPAINT_COLOR_BRIGHT_CYAN, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 10, 4, " ", TERMPAINT_COLOR_WHITE, TERMPAINT_DEFAULT_COLOR); checkEmptyPlusSome(f.surface, { {{ 3, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BLACK)}, {{ 3, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_DARK_GREY)}, {{ 4, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_RED)}, {{ 4, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_RED)}, {{ 5, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_GREEN)}, {{ 5, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 6, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW)}, {{ 6, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_YELLOW)}, {{ 7, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BLUE)}, {{ 7, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_BLUE)}, {{ 8, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_MAGENTA)}, {{ 8, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_MAGENTA)}, {{ 9, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_CYAN)}, {{ 9, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN)}, {{ 10, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_LIGHT_GREY)}, {{ 10, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_WHITE)}, }); } TEST_CASE("named bg colors") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 3, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BLACK); termpaint_surface_write_with_colors(f.surface, 4, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_RED); termpaint_surface_write_with_colors(f.surface, 5, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors(f.surface, 6, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_YELLOW); termpaint_surface_write_with_colors(f.surface, 7, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BLUE); termpaint_surface_write_with_colors(f.surface, 8, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_MAGENTA); termpaint_surface_write_with_colors(f.surface, 9, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_CYAN); termpaint_surface_write_with_colors(f.surface, 10, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_LIGHT_GREY); termpaint_surface_write_with_colors(f.surface, 3, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_DARK_GREY); termpaint_surface_write_with_colors(f.surface, 4, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_RED); termpaint_surface_write_with_colors(f.surface, 5, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_GREEN); termpaint_surface_write_with_colors(f.surface, 6, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_YELLOW); termpaint_surface_write_with_colors(f.surface, 7, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_BLUE); termpaint_surface_write_with_colors(f.surface, 8, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_MAGENTA); termpaint_surface_write_with_colors(f.surface, 9, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_CYAN); termpaint_surface_write_with_colors(f.surface, 10, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_WHITE); checkEmptyPlusSome(f.surface, { {{ 3, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BLACK)}, {{ 3, 4 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_DARK_GREY)}, {{ 4, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_RED)}, {{ 4, 4 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BRIGHT_RED)}, {{ 5, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_GREEN)}, {{ 5, 4 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 6, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_YELLOW)}, {{ 6, 4 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BRIGHT_YELLOW)}, {{ 7, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BLUE)}, {{ 7, 4 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BRIGHT_BLUE)}, {{ 8, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 8, 4 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BRIGHT_MAGENTA)}, {{ 9, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_CYAN)}, {{ 9, 4 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BRIGHT_CYAN)}, {{ 10, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_LIGHT_GREY)}, {{ 10, 4 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_WHITE)}, }); } TEST_CASE("indexed colors") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 3, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 16); termpaint_surface_write_with_colors(f.surface, 4, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 51); termpaint_surface_write_with_colors(f.surface, 5, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 70); termpaint_surface_write_with_colors(f.surface, 6, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 110); termpaint_surface_write_with_colors(f.surface, 7, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 123); termpaint_surface_write_with_colors(f.surface, 8, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 213); termpaint_surface_write_with_colors(f.surface, 9, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 232); termpaint_surface_write_with_colors(f.surface, 10, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 255); termpaint_surface_write_with_colors(f.surface, 3, 4, " ", TERMPAINT_INDEXED_COLOR + 16, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 4, 4, " ", TERMPAINT_INDEXED_COLOR + 51, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 5, 4, " ", TERMPAINT_INDEXED_COLOR + 70, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 6, 4, " ", TERMPAINT_INDEXED_COLOR + 110, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 7, 4, " ", TERMPAINT_INDEXED_COLOR + 123, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 8, 4, " ", TERMPAINT_INDEXED_COLOR + 213, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 9, 4, " ", TERMPAINT_INDEXED_COLOR + 232, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 10, 4, " ", TERMPAINT_INDEXED_COLOR + 255, TERMPAINT_DEFAULT_COLOR); checkEmptyPlusSome(f.surface, { {{ 3, 3 }, singleWideChar(" ").withBg(TERMPAINT_INDEXED_COLOR + 16)}, {{ 3, 4 }, singleWideChar(" ").withFg(TERMPAINT_INDEXED_COLOR + 16)}, {{ 4, 3 }, singleWideChar(" ").withBg(TERMPAINT_INDEXED_COLOR + 51)}, {{ 4, 4 }, singleWideChar(" ").withFg(TERMPAINT_INDEXED_COLOR + 51)}, {{ 5, 3 }, singleWideChar(" ").withBg(TERMPAINT_INDEXED_COLOR + 70)}, {{ 5, 4 }, singleWideChar(" ").withFg(TERMPAINT_INDEXED_COLOR + 70)}, {{ 6, 3 }, singleWideChar(" ").withBg(TERMPAINT_INDEXED_COLOR + 110)}, {{ 6, 4 }, singleWideChar(" ").withFg(TERMPAINT_INDEXED_COLOR + 110)}, {{ 7, 3 }, singleWideChar(" ").withBg(TERMPAINT_INDEXED_COLOR + 123)}, {{ 7, 4 }, singleWideChar(" ").withFg(TERMPAINT_INDEXED_COLOR + 123)}, {{ 8, 3 }, singleWideChar(" ").withBg(TERMPAINT_INDEXED_COLOR + 213)}, {{ 8, 4 }, singleWideChar(" ").withFg(TERMPAINT_INDEXED_COLOR + 213)}, {{ 9, 3 }, singleWideChar(" ").withBg(TERMPAINT_INDEXED_COLOR + 232)}, {{ 9, 4 }, singleWideChar(" ").withFg(TERMPAINT_INDEXED_COLOR + 232)}, {{ 10, 3 }, singleWideChar(" ").withBg(TERMPAINT_INDEXED_COLOR + 255)}, {{ 10, 4 }, singleWideChar(" ").withFg(TERMPAINT_INDEXED_COLOR + 255)}, }); } TEST_CASE("attributes") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); uattr_ptr attr; attr.reset(termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR)); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_BOLD); termpaint_surface_write_with_attr(f.surface, 3, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_ITALIC); termpaint_surface_write_with_attr(f.surface, 4, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_BLINK); termpaint_surface_write_with_attr(f.surface, 5, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_INVERSE); termpaint_surface_write_with_attr(f.surface, 6, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_STRIKE); termpaint_surface_write_with_attr(f.surface, 7, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE); termpaint_surface_write_with_attr(f.surface, 8, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE_DBL); termpaint_surface_write_with_attr(f.surface, 9, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE_CURLY); termpaint_surface_write_with_attr(f.surface, 10, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_OVERLINE); termpaint_surface_write_with_attr(f.surface, 11, 3, "X", attr.get()); checkEmptyPlusSome(f.surface, { {{ 3, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_BOLD)}, {{ 4, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_ITALIC)}, {{ 5, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_BLINK)}, {{ 6, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_INVERSE)}, {{ 7, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_STRIKE)}, {{ 8, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_UNDERLINE)}, {{ 9, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_UNDERLINE_DBL)}, {{ 10, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_UNDERLINE_CURLY)}, {{ 11, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_OVERLINE)}, }); } TEST_CASE("simple patch") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_attr* attr_url = termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_attr_set_patch(attr_url, true, "\033]8;;http://example.com\033\\", "\033]8;;\033\\"); termpaint_surface_write_with_attr(f.surface, 3, 3, "ABC", attr_url); termpaint_attr_free(attr_url); checkEmptyPlusSome(f.surface, { {{ 3, 3 }, singleWideChar("A").withPatch(true, "\033]8;;http://example.com\033\\", "\033]8;;\033\\")}, {{ 4, 3 }, singleWideChar("B").withPatch(true, "\033]8;;http://example.com\033\\", "\033]8;;\033\\")}, {{ 5, 3 }, singleWideChar("C").withPatch(true, "\033]8;;http://example.com\033\\", "\033]8;;\033\\")}, }); } TEST_CASE("too many patches") { // white-box: There is an internal limit of 255 different patch settings at one time. Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_attr* attr_url = termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); using namespace std::literals; std::map, Cell> expected; for (int i = 0; i < 256; i++) { const std::string setup = "\033]8;;http://example.com\033\\"s + std::to_string(i); termpaint_attr_set_patch(attr_url, true, setup.data(), "\033]8;;\033\\"); termpaint_surface_write_with_attr(f.surface, i % 80, i / 80, "x", attr_url); expected[{i % 80, i / 80}] = singleWideChar("x").withPatch(true, setup, "\033]8;;\033\\"); } termpaint_attr_free(attr_url); expected[{255 % 80, 255 / 80}] = singleWideChar("x"); checkEmptyPlusSome(f.surface, expected); } TEST_CASE("many patches - sequential") { // white-box: There is an internal limit of 255 different patch settings at one time. Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_attr* attr_url = termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); using namespace std::literals; for (int i = 0; i < 512; i++) { const std::string setup = "\033]8;;http://example.com\033\\"s + std::to_string(i); termpaint_attr_set_patch(attr_url, true, setup.data(), "\033]8;;\033\\"); termpaint_surface_write_with_attr(f.surface, 0, 0, "x", attr_url); checkEmptyPlusSome(f.surface, { {{0, 0}, singleWideChar("x").withPatch(true, setup, "\033]8;;\033\\")} }); } termpaint_attr_free(attr_url); } TEST_CASE("write with right clipping") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 75, 3, "Sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); checkEmptyPlusSome(f.surface, { {{ 75, 3 }, singleWideChar("S")}, {{ 76, 3 }, singleWideChar("a")}, {{ 77, 3 }, singleWideChar("m")}, {{ 78, 3 }, singleWideChar("p")}, {{ 79, 3 }, singleWideChar("l")}, }); } TEST_CASE("write with right clipping - double width complete") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 78, 3, "あえ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); checkEmptyPlusSome(f.surface, { {{ 78, 3 }, doubleWideChar("あ")}, }); } TEST_CASE("write with right clipping - double width partial") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 77, 3, "あえ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_RED); checkEmptyPlusSome(f.surface, { {{ 77, 3 }, doubleWideChar("あ").withBg(TERMPAINT_COLOR_RED)}, {{ 79, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_RED)}, }); } TEST_CASE("write with overlong clip_x1") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors_clipped(f.surface, 75, 3, "Sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR, 0, 90); checkEmptyPlusSome(f.surface, { {{ 75, 3 }, singleWideChar("S")}, {{ 76, 3 }, singleWideChar("a")}, {{ 77, 3 }, singleWideChar("m")}, {{ 78, 3 }, singleWideChar("p")}, {{ 79, 3 }, singleWideChar("l")}, }); } TEST_CASE("write with negative y") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors_clipped(f.surface, 3, -1, "Sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR, 0, 90); checkEmptyPlusSome(f.surface, { }); } TEST_CASE("write with y below surface") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors_clipped(f.surface, 3, 80, "Sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR, 0, 90); checkEmptyPlusSome(f.surface, { }); } TEST_CASE("write with left clipping") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors_clipped(f.surface, -2, 3, "Sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR, 0, 90); checkEmptyPlusSome(f.surface, { {{ 0, 3 }, singleWideChar("m")}, {{ 1, 3 }, singleWideChar("p")}, {{ 2, 3 }, singleWideChar("l")}, {{ 3, 3 }, singleWideChar("e")}, }); } TEST_CASE("write with left clipping - double width complete") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors_clipped(f.surface, -2, 3, "あえ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR, 0, 90); checkEmptyPlusSome(f.surface, { {{ 0, 3 }, doubleWideChar("え")}, }); } TEST_CASE("write with left clipping - double width partial") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors_clipped(f.surface, -1, 3, "あえ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_RED, 0, 90); checkEmptyPlusSome(f.surface, { {{ 0, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_RED)}, {{ 1, 3 }, doubleWideChar("え").withBg(TERMPAINT_COLOR_RED)}, }); } TEST_CASE("write with negative clip_x0") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors_clipped(f.surface, -2, 3, "Sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR, -2, 79); checkEmptyPlusSome(f.surface, { {{ 0, 3 }, singleWideChar("m")}, {{ 1, 3 }, singleWideChar("p")}, {{ 2, 3 }, singleWideChar("l")}, {{ 3, 3 }, singleWideChar("e")}, }); } TEST_CASE("write e plus non spaceing combining mark") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 5, 3, "\u0308e", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); checkEmptyPlusSome(f.surface, { {{ 5, 3 }, singleWideChar("\u00a0\u0308")}, {{ 6, 3 }, singleWideChar("e")}, }); } TEST_CASE("write starting with non spaceing combining mark") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 5, 3, "e\u0308e", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); checkEmptyPlusSome(f.surface, { {{ 5, 3 }, singleWideChar("e\u0308")}, {{ 6, 3 }, singleWideChar("e")}, }); } TEST_CASE("double width with right clipping") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors_clipped(f.surface, 3, 3, "あえ", TERMPAINT_COLOR_RED, TERMPAINT_DEFAULT_COLOR, 0, 5); checkEmptyPlusSome(f.surface, { {{ 3, 3 }, doubleWideChar("あ").withFg(TERMPAINT_COLOR_RED)}, {{ 5, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_RED)}, }); } TEST_CASE("double width with left clipping") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors_clipped(f.surface, 3, 3, "あえ", TERMPAINT_COLOR_RED, TERMPAINT_DEFAULT_COLOR, 4, 6); checkEmptyPlusSome(f.surface, { {{ 4, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_RED)}, {{ 5, 3 }, doubleWideChar("え").withFg(TERMPAINT_COLOR_RED)}, }); } TEST_CASE("peek text") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 3, 3, "あえ", TERMPAINT_COLOR_RED, TERMPAINT_DEFAULT_COLOR); int left, right; int len; const char *data; len = -2; data = termpaint_surface_peek_text(f.surface, 3, 3, &len, nullptr, nullptr); REQUIRE(len == strlen("あ")); CHECK(std::string(data, len) == std::string("あ")); left = -2; data = termpaint_surface_peek_text(f.surface, 3, 3, &len, &left, nullptr); CHECK(left == 3); right = -2; data = termpaint_surface_peek_text(f.surface, 3, 3, &len, nullptr, &right); CHECK(right == 4); } TEST_CASE("peek text on left most column") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 0, 3, "あえ", TERMPAINT_COLOR_RED, TERMPAINT_DEFAULT_COLOR); int left, right; int len; const char *data; len = -2; data = termpaint_surface_peek_text(f.surface, 0, 3, &len, nullptr, nullptr); REQUIRE(len == strlen("あ")); CHECK(std::string(data, len) == std::string("あ")); left = -2; data = termpaint_surface_peek_text(f.surface, 0, 3, &len, &left, nullptr); CHECK(left == 0); right = -2; data = termpaint_surface_peek_text(f.surface, 0, 3, &len, nullptr, &right); CHECK(right == 1); } TEST_CASE("cluster with more than 8 bytes") { // white-box: Storage switches mode at 8 bytes length Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); std::string big_cluster = "e\u0308\u0308\u0308\u0308"; termpaint_surface_write_with_colors(f.surface, 3, 3, big_cluster.data(), TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); checkEmptyPlusSome(f.surface, { {{ 3, 3 }, singleWideChar(big_cluster)}, }); } TEST_CASE("gc of cluster with more than 8 bytes (overwrite one char)") { // white-box: try to trigger garbage collecton for storage of more than 8 bytes long data. Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); // Add some other cells termpaint_surface_write_with_colors(f.surface, 5, 3, "abあ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); std::string big_cluster; for (char ch = 'a'; ch <= 'z'; ch++) { big_cluster = std::string(1, ch) + std::string("\u0308\u0308\u0308\u0308"); termpaint_surface_write_with_colors(f.surface, 3, 3, big_cluster.data(), TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); } checkEmptyPlusSome(f.surface, { {{ 3, 3 }, singleWideChar(big_cluster)}, {{ 5, 3 }, singleWideChar("a")}, {{ 6, 3 }, singleWideChar("b")}, {{ 7, 3 }, doubleWideChar("あ")}, }); } TEST_CASE("gc of cluster with more than 8 bytes (with one to keep)") { // white-box: try to trigger garbage collecton for storage of more than 8 bytes long data. Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); // Add some other cells termpaint_surface_write_with_colors(f.surface, 5, 3, "abあ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(f.surface, 5, 5, " \u0308\u0308\u0308\u0308", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); std::string big_cluster; for (char ch = 'a'; ch <= 'z'; ch++) { big_cluster = std::string(1, ch) + std::string("\u0308\u0308\u0308\u0308"); termpaint_surface_write_with_colors(f.surface, 3, 3, big_cluster.data(), TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); } checkEmptyPlusSome(f.surface, { {{ 3, 3 }, singleWideChar(big_cluster)}, {{ 5, 3 }, singleWideChar("a")}, {{ 6, 3 }, singleWideChar("b")}, {{ 7, 3 }, doubleWideChar("あ")}, {{ 5, 5 }, singleWideChar(" \u0308\u0308\u0308\u0308")}, }); } // clear is implicitly tested all over the place TEST_CASE("clear_with_char") { Fixture f{80, 24}; struct TestCase { int ch; std::string s; }; auto testCase = GENERATE( TestCase{' ', " "}, TestCase{'a', "a"}, TestCase{'\x7f', TERMPAINT_ERASED}, TestCase{u'ä', "ä"}, TestCase{u'あ', TERMPAINT_ERASED}, TestCase{u'\u0308', TERMPAINT_ERASED}); CAPTURE(testCase.ch); termpaint_surface_clear_with_char(f.surface, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE, testCase.ch); checkEmptyPlusSome(f.surface, { }, singleWideChar(testCase.s).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)); } TEST_CASE("clear_with_attr") { Fixture f{80, 24}; auto test_style = GENERATE(0, TERMPAINT_STYLE_BOLD, TERMPAINT_STYLE_ITALIC, TERMPAINT_STYLE_BLINK, TERMPAINT_STYLE_INVERSE, TERMPAINT_STYLE_STRIKE, TERMPAINT_STYLE_UNDERLINE, TERMPAINT_STYLE_UNDERLINE_DBL, TERMPAINT_STYLE_UNDERLINE_CURLY, TERMPAINT_STYLE_OVERLINE); CAPTURE(test_style); uattr_ptr attr; attr.reset(termpaint_attr_new(TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE)); termpaint_attr_set_style(attr.get(), test_style); termpaint_surface_clear_with_attr(f.surface, attr); checkEmptyPlusSome(f.surface, { }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE) .withStyle(test_style)); } TEST_CASE("clear_with_attr_char") { Fixture f{80, 24}; auto test_style = GENERATE(0, TERMPAINT_STYLE_BOLD, TERMPAINT_STYLE_ITALIC, TERMPAINT_STYLE_BLINK, TERMPAINT_STYLE_INVERSE, TERMPAINT_STYLE_STRIKE, TERMPAINT_STYLE_UNDERLINE, TERMPAINT_STYLE_UNDERLINE_DBL, TERMPAINT_STYLE_UNDERLINE_CURLY, TERMPAINT_STYLE_OVERLINE); CAPTURE(test_style); uattr_ptr attr; attr.reset(termpaint_attr_new(TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE)); termpaint_attr_set_style(attr.get(), test_style); struct TestCase { int ch; std::string s; }; auto testCase = GENERATE( TestCase{' ', " "}, TestCase{'a', "a"}, TestCase{'\x7f', TERMPAINT_ERASED}, TestCase{u'ä', "ä"}, TestCase{u'あ', TERMPAINT_ERASED}, TestCase{u'\u0308', TERMPAINT_ERASED}); CAPTURE(testCase.ch); termpaint_surface_clear_with_attr_char(f.surface, attr, testCase.ch); checkEmptyPlusSome(f.surface, { }, singleWideChar(testCase.s).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE) .withStyle(test_style)); } TEST_CASE("clear_rect") { Fixture f{80, 24}; termpaint_surface_clear_with_char(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN, '/'); termpaint_surface_clear_rect(f.surface, 20, 12, 2, 3, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); checkEmptyPlusSome(f.surface, { {{20, 12}, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, {{21, 12}, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, {{20, 13}, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, {{21, 13}, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, {{20, 14}, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, {{21, 14}, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, }, singleWideChar("/").withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("clear_rect_with_char") { Fixture f{80, 24}; termpaint_surface_clear_with_char(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN, '/'); struct TestCase { int ch; std::string s; }; auto testCase = GENERATE( TestCase{' ', " "}, TestCase{'a', "a"}, TestCase{'\x7f', TERMPAINT_ERASED}, TestCase{u'ä', "ä"}, TestCase{u'あ', TERMPAINT_ERASED}, TestCase{u'\u0308', TERMPAINT_ERASED}); CAPTURE(testCase.ch); termpaint_surface_clear_rect_with_char(f.surface, 20, 12, 2, 3, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE, testCase.ch); checkEmptyPlusSome(f.surface, { {{20, 12}, singleWideChar(testCase.s).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, {{21, 12}, singleWideChar(testCase.s).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, {{20, 13}, singleWideChar(testCase.s).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, {{21, 13}, singleWideChar(testCase.s).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, {{20, 14}, singleWideChar(testCase.s).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, {{21, 14}, singleWideChar(testCase.s).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, }, singleWideChar("/").withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("clear_rect_with_attr") { Fixture f{80, 24}; termpaint_surface_clear_with_char(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN, '/'); auto test_style = GENERATE(0, TERMPAINT_STYLE_BOLD, TERMPAINT_STYLE_ITALIC, TERMPAINT_STYLE_BLINK, TERMPAINT_STYLE_INVERSE, TERMPAINT_STYLE_STRIKE, TERMPAINT_STYLE_UNDERLINE, TERMPAINT_STYLE_UNDERLINE_DBL, TERMPAINT_STYLE_UNDERLINE_CURLY, TERMPAINT_STYLE_OVERLINE); CAPTURE(test_style); uattr_ptr attr; attr.reset(termpaint_attr_new(TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE)); termpaint_attr_set_style(attr.get(), test_style); termpaint_surface_clear_rect_with_attr(f.surface, 20, 12, 2, 3, attr); checkEmptyPlusSome(f.surface, { {{20, 12}, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE) .withStyle(test_style)}, {{21, 12}, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE) .withStyle(test_style)}, {{20, 13}, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE) .withStyle(test_style)}, {{21, 13}, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE) .withStyle(test_style)}, {{20, 14}, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE) .withStyle(test_style)}, {{21, 14}, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE) .withStyle(test_style)}, }, singleWideChar("/").withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("clear_rect_with_attr_char") { Fixture f{80, 24}; termpaint_surface_clear_with_char(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN, '/'); auto test_style = GENERATE(0, TERMPAINT_STYLE_BOLD, TERMPAINT_STYLE_ITALIC, TERMPAINT_STYLE_BLINK, TERMPAINT_STYLE_INVERSE, TERMPAINT_STYLE_STRIKE, TERMPAINT_STYLE_UNDERLINE, TERMPAINT_STYLE_UNDERLINE_DBL, TERMPAINT_STYLE_UNDERLINE_CURLY, TERMPAINT_STYLE_OVERLINE); CAPTURE(test_style); uattr_ptr attr; attr.reset(termpaint_attr_new(TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE)); termpaint_attr_set_style(attr.get(), test_style); struct TestCase { int ch; std::string s; }; auto testCase = GENERATE( TestCase{' ', " "}, TestCase{'a', "a"}, TestCase{'\x7f', TERMPAINT_ERASED}, TestCase{u'ä', "ä"}, TestCase{u'あ', TERMPAINT_ERASED}, TestCase{u'\u0308', TERMPAINT_ERASED}); CAPTURE(testCase.ch); termpaint_surface_clear_rect_with_attr_char(f.surface, 20, 12, 2, 3, attr, testCase.ch); checkEmptyPlusSome(f.surface, { {{20, 12}, singleWideChar(testCase.s).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE) .withStyle(test_style)}, {{21, 12}, singleWideChar(testCase.s).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE) .withStyle(test_style)}, {{20, 13}, singleWideChar(testCase.s).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE) .withStyle(test_style)}, {{21, 13}, singleWideChar(testCase.s).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE) .withStyle(test_style)}, {{20, 14}, singleWideChar(testCase.s).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE) .withStyle(test_style)}, {{21, 14}, singleWideChar(testCase.s).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE) .withStyle(test_style)}, }, singleWideChar("/").withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("clear rect left totally clipped") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_clear_rect(f.surface, -1, 3, 1, 2, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); checkEmptyPlusSome(f.surface, { }); } TEST_CASE("clear rect left partially clipped") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_clear_rect(f.surface, -1, 3, 2, 2, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); checkEmptyPlusSome(f.surface, { {{ 0, 3 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, {{ 0, 4 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, }); } TEST_CASE("clear rect top totally clipped") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_clear_rect(f.surface, 5, -1, 2, 1, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); checkEmptyPlusSome(f.surface, { }); } TEST_CASE("clear rect top partially clipped") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_clear_rect(f.surface, 5, -1, 2, 2, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); checkEmptyPlusSome(f.surface, { {{ 5, 0 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, {{ 6, 0 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, }); } TEST_CASE("clear rect right totally clipped") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_clear_rect(f.surface, 80, 3, 1, 2, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); checkEmptyPlusSome(f.surface, { }); } TEST_CASE("clear rect right partially clipped") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_clear_rect(f.surface, 79, 3, 2, 2, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); checkEmptyPlusSome(f.surface, { {{ 79, 3 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, {{ 79, 4 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, }); } TEST_CASE("clear rect bottom totally clipped") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_clear_rect(f.surface, 5, 24, 2, 1, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); checkEmptyPlusSome(f.surface, { }); } TEST_CASE("clear rect bottom partially clipped") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_clear_rect(f.surface, 5, 23, 2, 2, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); checkEmptyPlusSome(f.surface, { {{ 5, 23 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, {{ 6, 23 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_RED).withBg(TERMPAINT_COLOR_BLUE)}, }); } TEST_CASE("soft wrap marker") { Fixture f{80, 24}; struct TestCase { int x; int y; }; auto testCase = GENERATE( TestCase{5, 23}, TestCase{0, 0}, TestCase{79, 0}, TestCase{0, 5}); CAPTURE(testCase.x); CAPTURE(testCase.y); termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_set_softwrap_marker(f.surface, testCase.x, testCase.y, true); checkEmptyPlusSome(f.surface, { {{ testCase.x, testCase.y }, singleWideChar(TERMPAINT_ERASED).withSoftWrapMarker()}, }); } TEST_CASE("soft wrap marker - removal") { Fixture f{80, 24}; struct TestCase { int x; int y; }; auto testCase = GENERATE(4); CAPTURE(testCase); termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_set_softwrap_marker(f.surface, 5, 23, true); auto expected = singleWideChar(TERMPAINT_ERASED); if (testCase == 0) { termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); } else if (testCase == 1) { termpaint_surface_clear_rect(f.surface, 5, 23, 1, 1, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); } else if (testCase == 2) { termpaint_surface_write_with_colors(f.surface, 5, 23, TERMPAINT_ERASED, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); } else if (testCase == 3) { termpaint_surface_write_with_colors(f.surface, 5, 23, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); expected = singleWideChar(" "); } else if (testCase == 4) { termpaint_surface_set_softwrap_marker(f.surface, 5, 23, false); } checkEmptyPlusSome(f.surface, { {{ 5, 23 }, expected}, }); } TEST_CASE("set fg color") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); uattr_ptr attr; attr.reset(termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR)); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_BOLD); termpaint_surface_write_with_attr(f.surface, 3, 3, "A", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_ITALIC); termpaint_surface_write_with_attr(f.surface, 4, 3, "B", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_BLINK); termpaint_surface_write_with_attr(f.surface, 5, 3, "C", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_INVERSE); termpaint_surface_write_with_attr(f.surface, 6, 3, "D", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_STRIKE); termpaint_surface_write_with_attr(f.surface, 7, 3, "E", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE); termpaint_surface_write_with_attr(f.surface, 8, 3, "F", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE_DBL); termpaint_surface_write_with_attr(f.surface, 9, 3, "G", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE_CURLY); termpaint_surface_write_with_attr(f.surface, 10, 3, "H", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_OVERLINE); termpaint_surface_write_with_attr(f.surface, 11, 3, "I", attr.get()); for (int i = 0; i < 10; i++) { termpaint_surface_set_fg_color(f.surface, 3 + i, 3, TERMPAINT_COLOR_GREEN); } checkEmptyPlusSome(f.surface, { {{ 3, 3 }, singleWideChar("A").withFg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_BOLD)}, {{ 4, 3 }, singleWideChar("B").withFg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_ITALIC)}, {{ 5, 3 }, singleWideChar("C").withFg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_BLINK)}, {{ 6, 3 }, singleWideChar("D").withFg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_INVERSE)}, {{ 7, 3 }, singleWideChar("E").withFg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_STRIKE)}, {{ 8, 3 }, singleWideChar("F").withFg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_UNDERLINE)}, {{ 9, 3 }, singleWideChar("G").withFg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_UNDERLINE_DBL)}, {{ 10, 3 }, singleWideChar("H").withFg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_UNDERLINE_CURLY)}, {{ 11, 3 }, singleWideChar("I").withFg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_OVERLINE)}, {{ 12, 3 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_GREEN)}, }); } TEST_CASE("set bg color") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); uattr_ptr attr; attr.reset(termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR)); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_BOLD); termpaint_surface_write_with_attr(f.surface, 3, 3, "A", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_ITALIC); termpaint_surface_write_with_attr(f.surface, 4, 3, "B", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_BLINK); termpaint_surface_write_with_attr(f.surface, 5, 3, "C", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_INVERSE); termpaint_surface_write_with_attr(f.surface, 6, 3, "D", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_STRIKE); termpaint_surface_write_with_attr(f.surface, 7, 3, "E", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE); termpaint_surface_write_with_attr(f.surface, 8, 3, "F", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE_DBL); termpaint_surface_write_with_attr(f.surface, 9, 3, "G", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE_CURLY); termpaint_surface_write_with_attr(f.surface, 10, 3, "H", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_OVERLINE); termpaint_surface_write_with_attr(f.surface, 11, 3, "I", attr.get()); for (int i = 0; i < 10; i++) { termpaint_surface_set_bg_color(f.surface, 3 + i, 3, TERMPAINT_COLOR_GREEN); } checkEmptyPlusSome(f.surface, { {{ 3, 3 }, singleWideChar("A").withBg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_BOLD)}, {{ 4, 3 }, singleWideChar("B").withBg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_ITALIC)}, {{ 5, 3 }, singleWideChar("C").withBg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_BLINK)}, {{ 6, 3 }, singleWideChar("D").withBg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_INVERSE)}, {{ 7, 3 }, singleWideChar("E").withBg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_STRIKE)}, {{ 8, 3 }, singleWideChar("F").withBg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_UNDERLINE)}, {{ 9, 3 }, singleWideChar("G").withBg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_UNDERLINE_DBL)}, {{ 10, 3 }, singleWideChar("H").withBg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_UNDERLINE_CURLY)}, {{ 11, 3 }, singleWideChar("I").withBg(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_OVERLINE)}, {{ 12, 3 }, singleWideChar(TERMPAINT_ERASED).withBg(TERMPAINT_COLOR_GREEN)}, }); } TEST_CASE("set deco color") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); uattr_ptr attr; attr.reset(termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR)); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_BOLD); termpaint_surface_write_with_attr(f.surface, 3, 3, "A", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_ITALIC); termpaint_surface_write_with_attr(f.surface, 4, 3, "B", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_BLINK); termpaint_surface_write_with_attr(f.surface, 5, 3, "C", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_INVERSE); termpaint_surface_write_with_attr(f.surface, 6, 3, "D", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_STRIKE); termpaint_surface_write_with_attr(f.surface, 7, 3, "E", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE); termpaint_surface_write_with_attr(f.surface, 8, 3, "F", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE_DBL); termpaint_surface_write_with_attr(f.surface, 9, 3, "G", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE_CURLY); termpaint_surface_write_with_attr(f.surface, 10, 3, "H", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_OVERLINE); termpaint_surface_write_with_attr(f.surface, 11, 3, "I", attr.get()); for (int i = 0; i < 10; i++) { termpaint_surface_set_deco_color(f.surface, 3 + i, 3, TERMPAINT_COLOR_GREEN); } checkEmptyPlusSome(f.surface, { {{ 3, 3 }, singleWideChar("A").withDeco(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_BOLD)}, {{ 4, 3 }, singleWideChar("B").withDeco(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_ITALIC)}, {{ 5, 3 }, singleWideChar("C").withDeco(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_BLINK)}, {{ 6, 3 }, singleWideChar("D").withDeco(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_INVERSE)}, {{ 7, 3 }, singleWideChar("E").withDeco(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_STRIKE)}, {{ 8, 3 }, singleWideChar("F").withDeco(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_UNDERLINE)}, {{ 9, 3 }, singleWideChar("G").withDeco(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_UNDERLINE_DBL)}, {{ 10, 3 }, singleWideChar("H").withDeco(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_UNDERLINE_CURLY)}, {{ 11, 3 }, singleWideChar("I").withDeco(TERMPAINT_COLOR_GREEN).withStyle(TERMPAINT_STYLE_OVERLINE)}, {{ 12, 3 }, singleWideChar(TERMPAINT_ERASED).withDeco(TERMPAINT_COLOR_GREEN)}, }); } static int some_handle; TEST_CASE("tint") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); uattr_ptr attr; attr.reset(termpaint_attr_new(TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE)); termpaint_attr_set_deco(attr, TERMPAINT_COLOR_LIGHT_GREY); //termpaint_surface_clear_rect_with_attr(f.surface, 5, 3, 2, 2, attr); termpaint_surface_write_with_attr(f.surface, 5, 3, " ", attr); termpaint_surface_write_with_attr(f.surface, 5, 4, " ", attr); termpaint_surface_tint(f.surface, [] (void *user_data, unsigned *fg, unsigned *bg, unsigned *deco) { CHECK(user_data == &some_handle); auto identity = [] (bool x) { return x;}; CHECK(identity(*fg == TERMPAINT_DEFAULT_COLOR || *fg == TERMPAINT_COLOR_RED)); if (*fg == TERMPAINT_COLOR_RED) { *fg = TERMPAINT_COLOR_MAGENTA; } else { *fg = TERMPAINT_COLOR_YELLOW; } CHECK(identity(*bg == TERMPAINT_DEFAULT_COLOR || *bg == TERMPAINT_COLOR_BLUE)); if (*bg == TERMPAINT_COLOR_BLUE) { *bg = TERMPAINT_COLOR_GREEN; } else { *bg = TERMPAINT_COLOR_CYAN; } CHECK(identity(*deco == TERMPAINT_DEFAULT_COLOR || *deco == TERMPAINT_COLOR_LIGHT_GREY)); if (*deco == TERMPAINT_COLOR_LIGHT_GREY) { *deco = TERMPAINT_COLOR_DARK_GREY; } else { *deco = TERMPAINT_COLOR_BRIGHT_YELLOW; } }, &some_handle); checkEmptyPlusSome(f.surface, { {{ 5, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_MAGENTA).withBg(TERMPAINT_COLOR_GREEN) .withDeco(TERMPAINT_COLOR_DARK_GREY)}, {{ 5, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_MAGENTA).withBg(TERMPAINT_COLOR_GREEN) .withDeco(TERMPAINT_COLOR_DARK_GREY)}, {{ 6, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_MAGENTA).withBg(TERMPAINT_COLOR_GREEN) .withDeco(TERMPAINT_COLOR_DARK_GREY)}, {{ 6, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_MAGENTA).withBg(TERMPAINT_COLOR_GREEN) .withDeco(TERMPAINT_COLOR_DARK_GREY)}, }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_YELLOW) .withBg(TERMPAINT_COLOR_CYAN) .withDeco(TERMPAINT_COLOR_BRIGHT_YELLOW)); } TEST_CASE("char width") { Fixture f{80, 24}; CHECK(termpaint_surface_char_width(f.surface, 'a') == 1); CHECK(termpaint_surface_char_width(f.surface, u'が') == 2); CHECK(termpaint_surface_char_width(f.surface, u'\u0308') == 0); } TEST_CASE("off screen: compare different size") { Fixture f{80, 24}; usurface_ptr s1, s2; s1.reset(termpaint_terminal_new_surface(f.terminal, 40, 24)); s2.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_clear(s2, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); CHECK_FALSE(termpaint_surface_same_contents(s1, s2)); s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); s2.reset(termpaint_terminal_new_surface(f.terminal, 80, 12)); termpaint_surface_clear(s1, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_clear(s2, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); CHECK_FALSE(termpaint_surface_same_contents(s1, s2)); } TEST_CASE("off screen: compare same object") { Fixture f{80, 24}; usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 40, 24)); CHECK(termpaint_surface_same_contents(s1, s1)); } TEST_CASE("off screen: compare") { Fixture f{80, 24}; usurface_ptr s1, s2; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); s2.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_clear(s2, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); SECTION("basic case for identical") { termpaint_surface_write_with_colors(s1, 10, 3, "Sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s2, 10, 3, "Sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); CHECK(termpaint_surface_same_contents(s1, s2)); } SECTION("different fg") { termpaint_surface_write_with_colors(s1, 10, 3, "Sample", TERMPAINT_COLOR_RED, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s2, 10, 3, "Sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); CHECK_FALSE(termpaint_surface_same_contents(s1, s2)); } SECTION("different bg") { termpaint_surface_write_with_colors(s1, 10, 3, "Sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s2, 10, 3, "Sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_CYAN); CHECK_FALSE(termpaint_surface_same_contents(s1, s2)); } SECTION("different text") { termpaint_surface_write_with_colors(s1, 10, 3, "Sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s2, 10, 3, "sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); CHECK_FALSE(termpaint_surface_same_contents(s1, s2)); } SECTION("different text") { termpaint_surface_write_with_colors(s1, 10, 3, "e", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s2, 10, 3, "e\u0308", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); CHECK_FALSE(termpaint_surface_same_contents(s1, s2)); } SECTION("no normalization") { // don't depend on this, this *could* change, but currently it's this way termpaint_surface_write_with_colors(s1, 10, 3, "\u00eb", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s2, 10, 3, "e\u0308", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); CHECK_FALSE(termpaint_surface_same_contents(s1, s2)); } SECTION("case for content and wide characters") { termpaint_surface_write_with_colors(s1, 10, 3, "あえ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s2, 10, 3, "あえ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); CHECK(termpaint_surface_same_contents(s1, s2)); } SECTION("different text, different cell sizes") { termpaint_surface_write_with_colors(s1, 10, 3, "あえ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s2, 10, 3, "sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); CHECK_FALSE(termpaint_surface_same_contents(s1, s2)); } SECTION("different deco color") { uattr_ptr attr; attr.reset(termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR)); termpaint_attr_set_deco(attr, TERMPAINT_COLOR_CYAN); termpaint_surface_write_with_attr(s1, 10, 3, "sample", attr); termpaint_surface_write_with_colors(s2, 10, 3, "sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); CHECK_FALSE(termpaint_surface_same_contents(s1, s2)); } SECTION("different patch setup, cleanup and optimize") { uattr_ptr attr; attr.reset(termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR)); termpaint_attr_set_patch(attr, true, "asdf", "dfgh"); termpaint_surface_write_with_attr(s1, 10, 3, "sample", attr); termpaint_surface_write_with_colors(s2, 10, 3, "sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); CHECK_FALSE(termpaint_surface_same_contents(s1, s2)); } SECTION("different setup") { uattr_ptr attr; attr.reset(termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR)); termpaint_attr_set_patch(attr, true, "asdf", "dfgh"); termpaint_surface_write_with_attr(s1, 10, 3, "sample", attr); attr.reset(termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR)); termpaint_attr_set_patch(attr, true, "xxxx", "dfgh"); termpaint_surface_write_with_attr(s2, 10, 3, "sample", attr); CHECK_FALSE(termpaint_surface_same_contents(s1, s2)); } SECTION("different cleanup") { uattr_ptr attr; attr.reset(termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR)); termpaint_attr_set_patch(attr, true, "asdf", "dfgh"); termpaint_surface_write_with_attr(s1, 10, 3, "sample", attr); attr.reset(termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR)); termpaint_attr_set_patch(attr, true, "asdf", "yyyy"); termpaint_surface_write_with_attr(s2, 10, 3, "sample", attr); CHECK_FALSE(termpaint_surface_same_contents(s1, s2)); } SECTION("different optimize") { uattr_ptr attr; attr.reset(termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR)); termpaint_attr_set_patch(attr, true, "asdf", "dfgh"); termpaint_surface_write_with_attr(s1, 10, 3, "sample", attr); attr.reset(termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR)); termpaint_attr_set_patch(attr, false, "asdf", "dfgh"); termpaint_surface_write_with_attr(s2, 10, 3, "sample", attr); CHECK_FALSE(termpaint_surface_same_contents(s1, s2)); } SECTION("different attributes") { auto test_style = GENERATE(TERMPAINT_STYLE_BOLD, TERMPAINT_STYLE_ITALIC, TERMPAINT_STYLE_BLINK, TERMPAINT_STYLE_INVERSE, TERMPAINT_STYLE_STRIKE, TERMPAINT_STYLE_UNDERLINE, TERMPAINT_STYLE_UNDERLINE_DBL, TERMPAINT_STYLE_UNDERLINE_CURLY, TERMPAINT_STYLE_OVERLINE); uattr_ptr attr; attr.reset(termpaint_attr_new(TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE)); termpaint_surface_write_with_attr(s1, 10, 3, "sample", attr); termpaint_attr_set_style(attr.get(), test_style); termpaint_surface_write_with_attr(s2, 10, 3, "sample", attr); CHECK_FALSE(termpaint_surface_same_contents(s1, s2)); } SECTION("different soft wrap marker state") { termpaint_surface_set_softwrap_marker(s1, 5, 17, true); CHECK_FALSE(termpaint_surface_same_contents(s1, s2)); } } TEST_CASE("attr") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); uattr_ptr attr, clone; attr.reset(termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR)); Cell expectedCell = singleWideChar("r"); auto allStyles = TERMPAINT_STYLE_BOLD | TERMPAINT_STYLE_ITALIC | TERMPAINT_STYLE_BLINK | TERMPAINT_STYLE_INVERSE | TERMPAINT_STYLE_STRIKE | TERMPAINT_STYLE_UNDERLINE | TERMPAINT_STYLE_OVERLINE; SECTION("fg red") { termpaint_attr_set_fg(attr, TERMPAINT_COLOR_RED); expectedCell = expectedCell.withFg(TERMPAINT_COLOR_RED); } SECTION("bg red") { termpaint_attr_set_bg(attr, TERMPAINT_COLOR_RED); expectedCell = expectedCell.withBg(TERMPAINT_COLOR_RED); } SECTION("deco red") { termpaint_attr_set_deco(attr, TERMPAINT_COLOR_RED); expectedCell = expectedCell.withDeco(TERMPAINT_COLOR_RED); } SECTION("bold") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_BOLD); expectedCell = expectedCell.withStyle(TERMPAINT_STYLE_BOLD); } SECTION("reset bold") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_BOLD); termpaint_attr_reset_style(attr); } SECTION("unset bold") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_BOLD); termpaint_attr_unset_style(attr, TERMPAINT_STYLE_BOLD); } SECTION("unset bold from all") { termpaint_attr_set_style(attr, allStyles); termpaint_attr_unset_style(attr, TERMPAINT_STYLE_BOLD); expectedCell = expectedCell.withStyle(allStyles & ~TERMPAINT_STYLE_BOLD); } SECTION("italic") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_ITALIC); expectedCell = expectedCell.withStyle(TERMPAINT_STYLE_ITALIC); } SECTION("reset italic") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_ITALIC); termpaint_attr_reset_style(attr); } SECTION("unset italic") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_ITALIC); termpaint_attr_unset_style(attr, TERMPAINT_STYLE_ITALIC); } SECTION("unset italic from all") { termpaint_attr_set_style(attr, allStyles); termpaint_attr_unset_style(attr, TERMPAINT_STYLE_ITALIC); expectedCell = expectedCell.withStyle(allStyles & ~TERMPAINT_STYLE_ITALIC); } SECTION("blink") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_BLINK); expectedCell = expectedCell.withStyle(TERMPAINT_STYLE_BLINK); } SECTION("reset blink") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_BLINK); termpaint_attr_reset_style(attr); } SECTION("unset blink") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_BLINK); termpaint_attr_unset_style(attr, TERMPAINT_STYLE_BLINK); } SECTION("unset blink from all") { termpaint_attr_set_style(attr, allStyles); termpaint_attr_unset_style(attr, TERMPAINT_STYLE_BLINK); expectedCell = expectedCell.withStyle(allStyles & ~TERMPAINT_STYLE_BLINK); } SECTION("inverse") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_INVERSE); expectedCell = expectedCell.withStyle(TERMPAINT_STYLE_INVERSE); } SECTION("reset inverse") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_INVERSE); termpaint_attr_reset_style(attr); } SECTION("unset inverse") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_INVERSE); termpaint_attr_unset_style(attr, TERMPAINT_STYLE_INVERSE); } SECTION("unset inverse from all") { termpaint_attr_set_style(attr, allStyles); termpaint_attr_unset_style(attr, TERMPAINT_STYLE_INVERSE); expectedCell = expectedCell.withStyle(allStyles & ~TERMPAINT_STYLE_INVERSE); } SECTION("strike") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_STRIKE); expectedCell = expectedCell.withStyle(TERMPAINT_STYLE_STRIKE); } SECTION("reset strike") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_STRIKE); termpaint_attr_reset_style(attr); } SECTION("unset strike") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_STRIKE); termpaint_attr_unset_style(attr, TERMPAINT_STYLE_STRIKE); } SECTION("unset strike from all") { termpaint_attr_set_style(attr, allStyles); termpaint_attr_unset_style(attr, TERMPAINT_STYLE_STRIKE); expectedCell = expectedCell.withStyle(allStyles & ~TERMPAINT_STYLE_STRIKE); } SECTION("underline") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_UNDERLINE); expectedCell = expectedCell.withStyle(TERMPAINT_STYLE_UNDERLINE); } SECTION("reset underline") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_UNDERLINE); termpaint_attr_reset_style(attr); } SECTION("unset underline") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_UNDERLINE); termpaint_attr_unset_style(attr, TERMPAINT_STYLE_UNDERLINE); } SECTION("unset underline from all") { termpaint_attr_set_style(attr, allStyles); termpaint_attr_unset_style(attr, TERMPAINT_STYLE_UNDERLINE); expectedCell = expectedCell.withStyle(allStyles & ~TERMPAINT_STYLE_UNDERLINE); } SECTION("double underline") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_UNDERLINE_DBL); expectedCell = expectedCell.withStyle(TERMPAINT_STYLE_UNDERLINE_DBL); } SECTION("reset double underline") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_UNDERLINE_DBL); termpaint_attr_reset_style(attr); } SECTION("unset double underline") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_UNDERLINE_DBL); termpaint_attr_unset_style(attr, TERMPAINT_STYLE_UNDERLINE_DBL); } SECTION("curly underline") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_UNDERLINE_CURLY); expectedCell = expectedCell.withStyle(TERMPAINT_STYLE_UNDERLINE_CURLY); } SECTION("reset curly underline") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_UNDERLINE_CURLY); termpaint_attr_reset_style(attr); } SECTION("unset curly underline") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_UNDERLINE_CURLY); termpaint_attr_unset_style(attr, TERMPAINT_STYLE_UNDERLINE_CURLY); } SECTION("underline - with conflicting style dbl") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_UNDERLINE | TERMPAINT_STYLE_UNDERLINE_DBL); expectedCell = expectedCell.withStyle(TERMPAINT_STYLE_UNDERLINE); } SECTION("underline - with conflicting styles dbl + curly") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_UNDERLINE | TERMPAINT_STYLE_UNDERLINE_DBL | TERMPAINT_STYLE_UNDERLINE_CURLY); expectedCell = expectedCell.withStyle(TERMPAINT_STYLE_UNDERLINE); } SECTION("underline - with conflicting style curly") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_UNDERLINE | TERMPAINT_STYLE_UNDERLINE_CURLY); expectedCell = expectedCell.withStyle(TERMPAINT_STYLE_UNDERLINE); } SECTION("double underline - with conflicting style curly") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_UNDERLINE_DBL | TERMPAINT_STYLE_UNDERLINE_CURLY); expectedCell = expectedCell.withStyle(TERMPAINT_STYLE_UNDERLINE_DBL); } SECTION("overline") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_OVERLINE); expectedCell = expectedCell.withStyle(TERMPAINT_STYLE_OVERLINE); } SECTION("reset overline") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_OVERLINE); termpaint_attr_reset_style(attr); } SECTION("unset overline") { termpaint_attr_set_style(attr, TERMPAINT_STYLE_OVERLINE); termpaint_attr_unset_style(attr, TERMPAINT_STYLE_OVERLINE); } SECTION("unset overline from all") { termpaint_attr_set_style(attr, allStyles); termpaint_attr_unset_style(attr, TERMPAINT_STYLE_OVERLINE); expectedCell = expectedCell.withStyle(allStyles & ~TERMPAINT_STYLE_OVERLINE); } SECTION("patch no optimize") { termpaint_attr_set_patch(attr, false, "blub", "blah"); expectedCell = expectedCell.withPatch(false, "blub", "blah"); } SECTION("patch optimize") { termpaint_attr_set_patch(attr, true, "blub", "blah"); expectedCell = expectedCell.withPatch(true, "blub", "blah"); } SECTION("unset patch") { termpaint_attr_set_patch(attr, true, "blub", "blah"); termpaint_attr_set_patch(attr, true, nullptr, nullptr); } clone.reset(termpaint_attr_clone(attr)); termpaint_surface_write_with_attr(f.surface, 3, 3, "r", clone); checkEmptyPlusSome(f.surface, { {{ 3, 3 }, expectedCell}, }); } TEST_CASE("off-screen: resize") { Fixture f{80, 24}; usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 40, 24)); SECTION("negative width") { termpaint_surface_resize(s1, -1, 100); CHECK(termpaint_surface_width(s1) == 0); CHECK(termpaint_surface_height(s1) == 0); } SECTION("negative height") { termpaint_surface_resize(s1, 100, -1); CHECK(termpaint_surface_width(s1) == 0); CHECK(termpaint_surface_height(s1) == 0); } SECTION("20x12") { termpaint_surface_resize(s1, 20, 12); CHECK(termpaint_surface_width(s1) == 20); CHECK(termpaint_surface_height(s1) == 12); } } TEST_CASE("copy - simple") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_write_with_colors(s1, 10, 3, "Sample", TERMPAINT_COLOR_BLUE, TERMPAINT_COLOR_YELLOW); termpaint_surface_copy_rect(s1, 9, 3, 8, 1, f.surface, 23, 15, tileLeft, tileRight); checkEmptyPlusSome(f.surface, { {{ 23, 15 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, {{ 24, 15 }, singleWideChar("S").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 25, 15 }, singleWideChar("a").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 26, 15 }, singleWideChar("m").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 27, 15 }, singleWideChar("p").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 28, 15 }, singleWideChar("l").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 29, 15 }, singleWideChar("e").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 30, 15 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - width == 0") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_write_with_colors(s1, 10, 3, "Sample", TERMPAINT_COLOR_BLUE, TERMPAINT_COLOR_YELLOW); termpaint_surface_copy_rect(s1, 9, 3, 0, 1, f.surface, 23, 15, tileLeft, tileRight); checkEmptyPlusSome(f.surface, { }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - src.x bigger than source size") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_write_with_colors(s1, 10, 3, "Sample", TERMPAINT_COLOR_BLUE, TERMPAINT_COLOR_YELLOW); termpaint_surface_copy_rect(s1, 80, 3, 0, 1, f.surface, 23, 15, tileLeft, tileRight); checkEmptyPlusSome(f.surface, { }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - src.y bigger than source size") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_write_with_colors(s1, 10, 3, "Sample", TERMPAINT_COLOR_BLUE, TERMPAINT_COLOR_YELLOW); termpaint_surface_copy_rect(s1, 9, 24, 0, 1, f.surface, 23, 15, tileLeft, tileRight); checkEmptyPlusSome(f.surface, { }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - src.y == -1") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_write_with_colors(s1, 10, 0, "Sample", TERMPAINT_COLOR_BLUE, TERMPAINT_COLOR_YELLOW); termpaint_surface_write_with_colors(s1, 10, 1, "xxxxxx", TERMPAINT_COLOR_BLUE, TERMPAINT_COLOR_YELLOW); termpaint_surface_copy_rect(s1, 9, -1, 8, 2, f.surface, 23, 15, tileLeft, tileRight); checkEmptyPlusSome(f.surface, { {{ 23, 16 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, {{ 24, 16 }, singleWideChar("S").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 25, 16 }, singleWideChar("a").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 26, 16 }, singleWideChar("m").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 27, 16 }, singleWideChar("p").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 28, 16 }, singleWideChar("l").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 29, 16 }, singleWideChar("e").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 30, 16 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - dst.y == -1") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_write_with_colors(s1, 10, 2, "xxxxxx", TERMPAINT_COLOR_BLUE, TERMPAINT_COLOR_YELLOW); termpaint_surface_write_with_colors(s1, 10, 3, "Sample", TERMPAINT_COLOR_BLUE, TERMPAINT_COLOR_YELLOW); termpaint_surface_copy_rect(s1, 9, 2, 8, 2, f.surface, 23, -1, tileLeft, tileRight); checkEmptyPlusSome(f.surface, { {{ 23, 0 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, {{ 24, 0 }, singleWideChar("S").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 25, 0 }, singleWideChar("a").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 26, 0 }, singleWideChar("m").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 27, 0 }, singleWideChar("p").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 28, 0 }, singleWideChar("l").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 29, 0 }, singleWideChar("e").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 30, 0 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - dst clipping bottom") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_write_with_colors(s1, 10, 2, "Sample", TERMPAINT_COLOR_BLUE, TERMPAINT_COLOR_YELLOW); termpaint_surface_write_with_colors(s1, 10, 3, "xxxxxx", TERMPAINT_COLOR_BLUE, TERMPAINT_COLOR_YELLOW); termpaint_surface_copy_rect(s1, 9, 2, 8, 2, f.surface, 23, 23, tileLeft, tileRight); checkEmptyPlusSome(f.surface, { {{ 23, 23 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, {{ 24, 23 }, singleWideChar("S").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 25, 23 }, singleWideChar("a").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 26, 23 }, singleWideChar("m").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 27, 23 }, singleWideChar("p").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 28, 23 }, singleWideChar("l").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 29, 23 }, singleWideChar("e").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 30, 23 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - src clipping bottom") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_write_with_colors(s1, 10, 23, "Sample", TERMPAINT_COLOR_BLUE, TERMPAINT_COLOR_YELLOW); termpaint_surface_write_with_colors(s1, 10, 22, "xxxxxx", TERMPAINT_COLOR_BLUE, TERMPAINT_COLOR_YELLOW); termpaint_surface_copy_rect(s1, 9, 23, 8, 2, f.surface, 23, 23, tileLeft, tileRight); checkEmptyPlusSome(f.surface, { {{ 23, 23 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, {{ 24, 23 }, singleWideChar("S").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 25, 23 }, singleWideChar("a").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 26, 23 }, singleWideChar("m").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 27, 23 }, singleWideChar("p").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 28, 23 }, singleWideChar("l").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 29, 23 }, singleWideChar("e").withFg(TERMPAINT_COLOR_BLUE).withBg(TERMPAINT_COLOR_YELLOW)}, {{ 30, 23 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - uninit cell") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_copy_rect(s1, 9, 3, 8, 1, f.surface, 23, 15, tileLeft, tileRight); checkEmptyPlusSome(f.surface, { {{ 23, 15 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, {{ 24, 15 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, {{ 25, 15 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, {{ 26, 15 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, {{ 27, 15 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, {{ 28, 15 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, {{ 29, 15 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, {{ 30, 15 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_DEFAULT_COLOR).withBg(TERMPAINT_DEFAULT_COLOR)}, }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - chars that get substituted") { Fixture f{80, 24}; usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 3, 3, "a\004\u00ad\u0088x", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_copy_rect(s1, 0, 0, 80, 24, f.surface, 0, 0, TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_NO_TILE); auto check = [] (auto& surface) { checkEmptyPlusSome(surface, { {{ 3, 3 }, singleWideChar("a")}, {{ 4, 3 }, singleWideChar(" ")}, {{ 5, 3 }, singleWideChar("-")}, {{ 6, 3 }, singleWideChar(" ")}, {{ 7, 3 }, singleWideChar("x")}, }); }; check(s1); check(f.surface); } TEST_CASE("copy - rgb colors") { Fixture f{80, 24}; usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 3, 3, "r", TERMPAINT_RGB_COLOR(255, 128, 128), TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 4, 3, "g", TERMPAINT_RGB_COLOR(128, 255, 128), TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 5, 3, "b", TERMPAINT_RGB_COLOR(128, 128, 255), TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 3, 4, "r", TERMPAINT_DEFAULT_COLOR, TERMPAINT_RGB_COLOR(255, 128, 128)); termpaint_surface_write_with_colors(s1, 4, 4, "g", TERMPAINT_DEFAULT_COLOR, TERMPAINT_RGB_COLOR(128, 255, 128)); termpaint_surface_write_with_colors(s1, 5, 4, "b", TERMPAINT_DEFAULT_COLOR, TERMPAINT_RGB_COLOR(128, 128, 255)); termpaint_surface_copy_rect(s1, 0, 0, 80, 24, f.surface, 0, 0, TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_NO_TILE); auto check = [] (auto& surface) { checkEmptyPlusSome(surface, { {{ 3, 3 }, singleWideChar("r").withFg(TERMPAINT_RGB_COLOR(0xff, 0x80, 0x80))}, {{ 4, 3 }, singleWideChar("g").withFg(TERMPAINT_RGB_COLOR(0x80, 0xff, 0x80))}, {{ 5, 3 }, singleWideChar("b").withFg(TERMPAINT_RGB_COLOR(0x80, 0x80, 0xff))}, {{ 3, 4 }, singleWideChar("r").withBg(TERMPAINT_RGB_COLOR(0xff, 0x80, 0x80))}, {{ 4, 4 }, singleWideChar("g").withBg(TERMPAINT_RGB_COLOR(0x80, 0xff, 0x80))}, {{ 5, 4 }, singleWideChar("b").withBg(TERMPAINT_RGB_COLOR(0x80, 0x80, 0xff))}, }); }; check(s1); check(f.surface); } TEST_CASE("copy - named fg colors") { Fixture f{80, 24}; usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 3, 3, " ", TERMPAINT_COLOR_BLACK, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 4, 3, " ", TERMPAINT_COLOR_RED, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 5, 3, " ", TERMPAINT_COLOR_GREEN, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 6, 3, " ", TERMPAINT_COLOR_YELLOW, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 7, 3, " ", TERMPAINT_COLOR_BLUE, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 8, 3, " ", TERMPAINT_COLOR_MAGENTA, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 9, 3, " ", TERMPAINT_COLOR_CYAN, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 10, 3, " ", TERMPAINT_COLOR_LIGHT_GREY, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 3, 4, " ", TERMPAINT_COLOR_DARK_GREY, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 4, 4, " ", TERMPAINT_COLOR_BRIGHT_RED, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 5, 4, " ", TERMPAINT_COLOR_BRIGHT_GREEN, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 6, 4, " ", TERMPAINT_COLOR_BRIGHT_YELLOW, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 7, 4, " ", TERMPAINT_COLOR_BRIGHT_BLUE, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 8, 4, " ", TERMPAINT_COLOR_BRIGHT_MAGENTA, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 9, 4, " ", TERMPAINT_COLOR_BRIGHT_CYAN, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 10, 4, " ", TERMPAINT_COLOR_WHITE, TERMPAINT_DEFAULT_COLOR); termpaint_surface_copy_rect(s1, 0, 0, 80, 24, f.surface, 0, 0, TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_NO_TILE); auto check = [] (auto& surface) { checkEmptyPlusSome(surface, { {{ 3, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BLACK)}, {{ 3, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_DARK_GREY)}, {{ 4, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_RED)}, {{ 4, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_RED)}, {{ 5, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_GREEN)}, {{ 5, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 6, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW)}, {{ 6, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_YELLOW)}, {{ 7, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BLUE)}, {{ 7, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_BLUE)}, {{ 8, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_MAGENTA)}, {{ 8, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_MAGENTA)}, {{ 9, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_CYAN)}, {{ 9, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN)}, {{ 10, 3 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_LIGHT_GREY)}, {{ 10, 4 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_WHITE)}, }); }; check(s1); check(f.surface); } TEST_CASE("copy - named bg colors") { Fixture f{80, 24}; usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 3, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BLACK); termpaint_surface_write_with_colors(s1, 4, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_RED); termpaint_surface_write_with_colors(s1, 5, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors(s1, 6, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_YELLOW); termpaint_surface_write_with_colors(s1, 7, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BLUE); termpaint_surface_write_with_colors(s1, 8, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_MAGENTA); termpaint_surface_write_with_colors(s1, 9, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_CYAN); termpaint_surface_write_with_colors(s1, 10, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_LIGHT_GREY); termpaint_surface_write_with_colors(s1, 3, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_DARK_GREY); termpaint_surface_write_with_colors(s1, 4, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_RED); termpaint_surface_write_with_colors(s1, 5, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_GREEN); termpaint_surface_write_with_colors(s1, 6, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_YELLOW); termpaint_surface_write_with_colors(s1, 7, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_BLUE); termpaint_surface_write_with_colors(s1, 8, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_MAGENTA); termpaint_surface_write_with_colors(s1, 9, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_CYAN); termpaint_surface_write_with_colors(s1, 10, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_WHITE); termpaint_surface_copy_rect(s1, 0, 0, 80, 24, f.surface, 0, 0, TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_NO_TILE); auto check = [] (auto& surface) { checkEmptyPlusSome(surface, { {{ 3, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BLACK)}, {{ 3, 4 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_DARK_GREY)}, {{ 4, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_RED)}, {{ 4, 4 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BRIGHT_RED)}, {{ 5, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_GREEN)}, {{ 5, 4 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 6, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_YELLOW)}, {{ 6, 4 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BRIGHT_YELLOW)}, {{ 7, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BLUE)}, {{ 7, 4 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BRIGHT_BLUE)}, {{ 8, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 8, 4 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BRIGHT_MAGENTA)}, {{ 9, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_CYAN)}, {{ 9, 4 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_BRIGHT_CYAN)}, {{ 10, 3 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_LIGHT_GREY)}, {{ 10, 4 }, singleWideChar(" ").withBg(TERMPAINT_COLOR_WHITE)}, }); }; check(s1); check(f.surface); } TEST_CASE("copy - indexed colors") { Fixture f{80, 24}; usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 3, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 16); termpaint_surface_write_with_colors(s1, 4, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 51); termpaint_surface_write_with_colors(s1, 5, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 70); termpaint_surface_write_with_colors(s1, 6, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 110); termpaint_surface_write_with_colors(s1, 7, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 123); termpaint_surface_write_with_colors(s1, 8, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 213); termpaint_surface_write_with_colors(s1, 9, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 232); termpaint_surface_write_with_colors(s1, 10, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 255); termpaint_surface_write_with_colors(s1, 3, 4, " ", TERMPAINT_INDEXED_COLOR + 16, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 4, 4, " ", TERMPAINT_INDEXED_COLOR + 51, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 5, 4, " ", TERMPAINT_INDEXED_COLOR + 70, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 6, 4, " ", TERMPAINT_INDEXED_COLOR + 110, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 7, 4, " ", TERMPAINT_INDEXED_COLOR + 123, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 8, 4, " ", TERMPAINT_INDEXED_COLOR + 213, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 9, 4, " ", TERMPAINT_INDEXED_COLOR + 232, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(s1, 10, 4, " ", TERMPAINT_INDEXED_COLOR + 255, TERMPAINT_DEFAULT_COLOR); termpaint_surface_copy_rect(s1, 0, 0, 80, 24, f.surface, 0, 0, TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_NO_TILE); auto check = [] (auto& surface) { checkEmptyPlusSome(surface, { {{ 3, 3 }, singleWideChar(" ").withBg(TERMPAINT_INDEXED_COLOR + 16)}, {{ 3, 4 }, singleWideChar(" ").withFg(TERMPAINT_INDEXED_COLOR + 16)}, {{ 4, 3 }, singleWideChar(" ").withBg(TERMPAINT_INDEXED_COLOR + 51)}, {{ 4, 4 }, singleWideChar(" ").withFg(TERMPAINT_INDEXED_COLOR + 51)}, {{ 5, 3 }, singleWideChar(" ").withBg(TERMPAINT_INDEXED_COLOR + 70)}, {{ 5, 4 }, singleWideChar(" ").withFg(TERMPAINT_INDEXED_COLOR + 70)}, {{ 6, 3 }, singleWideChar(" ").withBg(TERMPAINT_INDEXED_COLOR + 110)}, {{ 6, 4 }, singleWideChar(" ").withFg(TERMPAINT_INDEXED_COLOR + 110)}, {{ 7, 3 }, singleWideChar(" ").withBg(TERMPAINT_INDEXED_COLOR + 123)}, {{ 7, 4 }, singleWideChar(" ").withFg(TERMPAINT_INDEXED_COLOR + 123)}, {{ 8, 3 }, singleWideChar(" ").withBg(TERMPAINT_INDEXED_COLOR + 213)}, {{ 8, 4 }, singleWideChar(" ").withFg(TERMPAINT_INDEXED_COLOR + 213)}, {{ 9, 3 }, singleWideChar(" ").withBg(TERMPAINT_INDEXED_COLOR + 232)}, {{ 9, 4 }, singleWideChar(" ").withFg(TERMPAINT_INDEXED_COLOR + 232)}, {{ 10, 3 }, singleWideChar(" ").withBg(TERMPAINT_INDEXED_COLOR + 255)}, {{ 10, 4 }, singleWideChar(" ").withFg(TERMPAINT_INDEXED_COLOR + 255)}, }); }; check(s1); check(f.surface); } TEST_CASE("copy - attributes") { Fixture f{80, 24}; usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); uattr_ptr attr; attr.reset(termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR)); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_BOLD); termpaint_surface_write_with_attr(s1, 3, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_ITALIC); termpaint_surface_write_with_attr(s1, 4, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_BLINK); termpaint_surface_write_with_attr(s1, 5, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_INVERSE); termpaint_surface_write_with_attr(s1, 6, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_STRIKE); termpaint_surface_write_with_attr(s1, 7, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE); termpaint_surface_write_with_attr(s1, 8, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE_DBL); termpaint_surface_write_with_attr(s1, 9, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE_CURLY); termpaint_surface_write_with_attr(s1, 10, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_OVERLINE); termpaint_surface_write_with_attr(s1, 11, 3, "X", attr.get()); termpaint_surface_copy_rect(s1, 0, 0, 80, 24, f.surface, 0, 0, TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_NO_TILE); auto check = [] (auto& surface) { checkEmptyPlusSome(surface, { {{ 3, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_BOLD)}, {{ 4, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_ITALIC)}, {{ 5, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_BLINK)}, {{ 6, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_INVERSE)}, {{ 7, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_STRIKE)}, {{ 8, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_UNDERLINE)}, {{ 9, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_UNDERLINE_DBL)}, {{ 10, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_UNDERLINE_CURLY)}, {{ 11, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_OVERLINE)}, }); }; check(s1); check(f.surface); } TEST_CASE("copy - simple patch") { Fixture f{80, 24}; usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_attr* attr_url = termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_attr_set_patch(attr_url, true, "\033]8;;http://example.com\033\\", "\033]8;;\033\\"); termpaint_surface_write_with_attr(s1, 3, 3, "ABC", attr_url); termpaint_attr_free(attr_url); termpaint_surface_copy_rect(s1, 0, 0, 80, 24, f.surface, 0, 0, TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_NO_TILE); auto check = [] (auto& surface) { checkEmptyPlusSome(surface, { {{ 3, 3 }, singleWideChar("A").withPatch(true, "\033]8;;http://example.com\033\\", "\033]8;;\033\\")}, {{ 4, 3 }, singleWideChar("B").withPatch(true, "\033]8;;http://example.com\033\\", "\033]8;;\033\\")}, {{ 5, 3 }, singleWideChar("C").withPatch(true, "\033]8;;http://example.com\033\\", "\033]8;;\033\\")}, }); }; check(s1); check(f.surface); } TEST_CASE("copy - soft wrap marker") { Fixture f{80, 24}; termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); termpaint_surface_set_softwrap_marker(f.surface, 25, 15, true); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); //termpaint_surface_write_with_colors(s1, 10, 3, "Sample", TERMPAINT_COLOR_BLUE, TERMPAINT_COLOR_YELLOW); termpaint_surface_set_softwrap_marker(s1, 10, 3, true); termpaint_surface_copy_rect(s1, 9, 3, 8, 1, f.surface, 23, 15, TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_NO_TILE); checkEmptyPlusSome(f.surface, { {{ 23, 15 }, singleWideChar(TERMPAINT_ERASED)}, {{ 24, 15 }, singleWideChar(TERMPAINT_ERASED).withSoftWrapMarker()}, {{ 25, 15 }, singleWideChar(TERMPAINT_ERASED)}, {{ 26, 15 }, singleWideChar(TERMPAINT_ERASED)}, {{ 27, 15 }, singleWideChar(TERMPAINT_ERASED)}, {{ 28, 15 }, singleWideChar(TERMPAINT_ERASED)}, {{ 29, 15 }, singleWideChar(TERMPAINT_ERASED)}, {{ 30, 15 }, singleWideChar(TERMPAINT_ERASED)}, }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - double wide on src and dest (single line)") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); CAPTURE(tileLeft); CAPTURE(tileRight); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors(f.surface, 20, 15, "ABCDEFG", TERMPAINT_COLOR_BRIGHT_CYAN, TERMPAINT_COLOR_BRIGHT_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_COLOR_WHITE, TERMPAINT_COLOR_BLACK); termpaint_surface_write_with_colors(s1, 6, 3, "abcdefg", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_MAGENTA); termpaint_surface_copy_rect(s1, 9, 3, 8, 1, f.surface, 23, 15, tileLeft, tileRight); std::map, Cell> expectedCells = { {{ 20, 15 }, doubleWideChar("A").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 24, 15 }, doubleWideChar("c").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 26, 15 }, doubleWideChar("d").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 28, 15 }, doubleWideChar("e").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 32, 15 }, doubleWideChar("G").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, }; if (tileLeft == TERMPAINT_COPY_NO_TILE) { expectedCells[{ 22, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); expectedCells[{ 23, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); } else if (tileLeft == TERMPAINT_COPY_TILE_PUT) { expectedCells[{ 22, 15 }] = doubleWideChar("b").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); } else if (tileLeft == TERMPAINT_COPY_TILE_PRESERVE) { expectedCells[{ 22, 15 }] = doubleWideChar("B").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } if (tileRight == TERMPAINT_COPY_NO_TILE) { expectedCells[{ 30, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); expectedCells[{ 31, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } else if (tileRight == TERMPAINT_COPY_TILE_PUT) { expectedCells[{ 30, 15 }] = doubleWideChar("f").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); } else if (tileRight == TERMPAINT_COPY_TILE_PRESERVE) { expectedCells[{ 30, 15 }] = doubleWideChar("F").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } checkEmptyPlusSome(f.surface, expectedCells, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - double wide on src and dest (five lines)") { // first line has double wide on source and destionation // second line has double wide on source only // third line has double wide on destination only // fourth line has no double wide // fifth line has double wide on source and destination, but misaligned Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); CAPTURE(tileLeft); CAPTURE(tileRight); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors(f.surface, 20, 15, "ABCDEFG", TERMPAINT_COLOR_BRIGHT_CYAN, TERMPAINT_COLOR_BRIGHT_GREEN); termpaint_surface_write_with_colors(f.surface, 20, 17, "ABCDEFG", TERMPAINT_COLOR_BRIGHT_CYAN, TERMPAINT_COLOR_BRIGHT_GREEN); termpaint_surface_write_with_colors(f.surface, 19, 19, "ABCDEFGH", TERMPAINT_COLOR_BRIGHT_CYAN, TERMPAINT_COLOR_BRIGHT_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_COLOR_WHITE, TERMPAINT_COLOR_BLACK); termpaint_surface_write_with_colors(s1, 6, 3, "abcdefg", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_MAGENTA); termpaint_surface_write_with_colors(s1, 6, 4, "abcdefg", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_MAGENTA); termpaint_surface_write_with_colors(s1, 6, 7, "abcdefg", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_MAGENTA); termpaint_surface_copy_rect(s1, 9, 3, 8, 5, f.surface, 23, 15, tileLeft, tileRight); std::map, Cell> expectedCells = { {{ 20, 15 }, doubleWideChar("A").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 24, 15 }, doubleWideChar("c").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 26, 15 }, doubleWideChar("d").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 28, 15 }, doubleWideChar("e").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 32, 15 }, doubleWideChar("G").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 23, 16 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 24, 16 }, doubleWideChar("c").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 26, 16 }, doubleWideChar("d").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 28, 16 }, doubleWideChar("e").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 30, 16 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 20, 17 }, doubleWideChar("A").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 22, 17 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 23, 17 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_WHITE).withBg(TERMPAINT_COLOR_BLACK)}, {{ 24, 17 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_WHITE).withBg(TERMPAINT_COLOR_BLACK)}, {{ 25, 17 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_WHITE).withBg(TERMPAINT_COLOR_BLACK)}, {{ 26, 17 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_WHITE).withBg(TERMPAINT_COLOR_BLACK)}, {{ 27, 17 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_WHITE).withBg(TERMPAINT_COLOR_BLACK)}, {{ 28, 17 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_WHITE).withBg(TERMPAINT_COLOR_BLACK)}, {{ 29, 17 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_WHITE).withBg(TERMPAINT_COLOR_BLACK)}, {{ 30, 17 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_WHITE).withBg(TERMPAINT_COLOR_BLACK)}, {{ 31, 17 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 32, 17 }, doubleWideChar("G").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 23, 18 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_WHITE).withBg(TERMPAINT_COLOR_BLACK)}, {{ 24, 18 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_WHITE).withBg(TERMPAINT_COLOR_BLACK)}, {{ 25, 18 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_WHITE).withBg(TERMPAINT_COLOR_BLACK)}, {{ 26, 18 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_WHITE).withBg(TERMPAINT_COLOR_BLACK)}, {{ 27, 18 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_WHITE).withBg(TERMPAINT_COLOR_BLACK)}, {{ 28, 18 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_WHITE).withBg(TERMPAINT_COLOR_BLACK)}, {{ 29, 18 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_WHITE).withBg(TERMPAINT_COLOR_BLACK)}, {{ 30, 18 }, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_WHITE).withBg(TERMPAINT_COLOR_BLACK)}, {{ 19, 19 }, doubleWideChar("A").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 23, 19 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 24, 19 }, doubleWideChar("c").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 26, 19 }, doubleWideChar("d").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 28, 19 }, doubleWideChar("e").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 30, 19 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 33, 19 }, doubleWideChar("H").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, }; if (tileLeft == TERMPAINT_COPY_NO_TILE) { expectedCells[{ 22, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); expectedCells[{ 23, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); expectedCells[{ 21, 19 }] = doubleWideChar("B").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } else if (tileLeft == TERMPAINT_COPY_TILE_PUT) { expectedCells[{ 22, 15 }] = doubleWideChar("b").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); expectedCells[{ 22, 16 }] = doubleWideChar("b").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); expectedCells[{ 21, 19 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); expectedCells[{ 22, 19 }] = doubleWideChar("b").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); } else if (tileLeft == TERMPAINT_COPY_TILE_PRESERVE) { expectedCells[{ 22, 15 }] = doubleWideChar("B").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); expectedCells[{ 21, 19 }] = doubleWideChar("B").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } if (tileRight == TERMPAINT_COPY_NO_TILE) { expectedCells[{ 30, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); expectedCells[{ 31, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); expectedCells[{ 31, 19 }] = doubleWideChar("G").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } else if (tileRight == TERMPAINT_COPY_TILE_PUT) { expectedCells[{ 30, 15 }] = doubleWideChar("f").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); expectedCells[{ 30, 16 }] = doubleWideChar("f").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); expectedCells[{ 30, 19 }] = doubleWideChar("f").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); expectedCells[{ 32, 19 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } else if (tileRight == TERMPAINT_COPY_TILE_PRESERVE) { expectedCells[{ 30, 15 }] = doubleWideChar("F").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); expectedCells[{ 31, 19 }] = doubleWideChar("G").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } checkEmptyPlusSome(f.surface, expectedCells, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - double wide on src and dst with rect width == 1") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); CAPTURE(tileLeft); CAPTURE(tileRight); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors(f.surface, 20, 15, "ABC", TERMPAINT_COLOR_BRIGHT_CYAN, TERMPAINT_COLOR_BRIGHT_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_COLOR_WHITE, TERMPAINT_COLOR_BLACK); termpaint_surface_write_with_colors(s1, 6, 3, "abc", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_MAGENTA); termpaint_surface_copy_rect(s1, 9, 3, 1, 1, f.surface, 23, 15, tileLeft, tileRight); std::map, Cell> expectedCells = { {{ 20, 15 }, doubleWideChar("A").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 24, 15 }, doubleWideChar("C").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, }; if (tileLeft == TERMPAINT_COPY_NO_TILE) { expectedCells[{ 22, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); expectedCells[{ 23, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); } else if (tileLeft == TERMPAINT_COPY_TILE_PUT) { expectedCells[{ 22, 15 }] = doubleWideChar("b").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); } else if (tileLeft == TERMPAINT_COPY_TILE_PRESERVE) { expectedCells[{ 22, 15 }] = doubleWideChar("B").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } checkEmptyPlusSome(f.surface, expectedCells, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - double wide on src and dst with rect width == 1 and misaligned") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); CAPTURE(tileLeft); CAPTURE(tileRight); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors(f.surface, 20, 15, "ABC", TERMPAINT_COLOR_BRIGHT_CYAN, TERMPAINT_COLOR_BRIGHT_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_COLOR_WHITE, TERMPAINT_COLOR_BLACK); termpaint_surface_write_with_colors(s1, 7, 3, "abc", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_MAGENTA); termpaint_surface_copy_rect(s1, 9, 3, 1, 1, f.surface, 23, 15, tileLeft, tileRight); std::map, Cell> expectedCells = { {{ 20, 15 }, doubleWideChar("A").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 22, 15 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, }; if (tileRight == TERMPAINT_COPY_TILE_PUT) { expectedCells[{ 23, 15 }] = doubleWideChar("b").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); expectedCells[{ 25, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } else { expectedCells[{ 23, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); expectedCells[{ 24, 15 }] = doubleWideChar("C").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } checkEmptyPlusSome(f.surface, expectedCells, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - double wide on src and dst and dst.x == 0") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); CAPTURE(tileLeft); CAPTURE(tileRight); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors(f.surface, 0, 15, " CDEFG", TERMPAINT_COLOR_BRIGHT_CYAN, TERMPAINT_COLOR_BRIGHT_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_COLOR_WHITE, TERMPAINT_COLOR_BLACK); termpaint_surface_write_with_colors(s1, 6, 3, "abcdefg", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_MAGENTA); termpaint_surface_copy_rect(s1, 9, 3, 8, 1, f.surface, 0, 15, tileLeft, tileRight); std::map, Cell> expectedCells = { {{ 0, 15 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 1, 15 }, doubleWideChar("c").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 3, 15 }, doubleWideChar("d").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 5, 15 }, doubleWideChar("e").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 9, 15 }, doubleWideChar("G").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, }; if (tileRight == TERMPAINT_COPY_NO_TILE) { expectedCells[{ 7, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); expectedCells[{ 8, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } else if (tileRight == TERMPAINT_COPY_TILE_PUT) { expectedCells[{ 7, 15 }] = doubleWideChar("f").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); } else if (tileRight == TERMPAINT_COPY_TILE_PRESERVE) { expectedCells[{ 7, 15 }] = doubleWideChar("F").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } checkEmptyPlusSome(f.surface, expectedCells, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - double wide on src and dst and dst.x == -1") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); CAPTURE(tileLeft); CAPTURE(tileRight); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors(f.surface, 0, 15, " CDEFG", TERMPAINT_COLOR_BRIGHT_CYAN, TERMPAINT_COLOR_BRIGHT_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_COLOR_WHITE, TERMPAINT_COLOR_BLACK); termpaint_surface_write_with_colors(s1, 6, 3, "abcdefg", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_MAGENTA); termpaint_surface_copy_rect(s1, 8, 3, 9, 1, f.surface, -1, 15, tileLeft, tileRight); std::map, Cell> expectedCells = { {{ 0, 15 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 1, 15 }, doubleWideChar("c").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 3, 15 }, doubleWideChar("d").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 5, 15 }, doubleWideChar("e").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 9, 15 }, doubleWideChar("G").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, }; if (tileRight == TERMPAINT_COPY_NO_TILE) { expectedCells[{ 7, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); expectedCells[{ 8, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } else if (tileRight == TERMPAINT_COPY_TILE_PUT) { expectedCells[{ 7, 15 }] = doubleWideChar("f").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); } else if (tileRight == TERMPAINT_COPY_TILE_PRESERVE) { expectedCells[{ 7, 15 }] = doubleWideChar("F").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } checkEmptyPlusSome(f.surface, expectedCells, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - double wide on src and src.x == -1") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); CAPTURE(tileLeft); CAPTURE(tileRight); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors(f.surface, 20, 15, "ABCDEFG", TERMPAINT_COLOR_BRIGHT_CYAN, TERMPAINT_COLOR_BRIGHT_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_COLOR_WHITE, TERMPAINT_COLOR_BLACK); termpaint_surface_write_with_colors(s1, 0, 3, "cdefg", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_MAGENTA); termpaint_surface_copy_rect(s1, -1, 3, 8, 1, f.surface, 23, 15, tileLeft, tileRight); std::map, Cell> expectedCells = { {{ 20, 15 }, doubleWideChar("A").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 22, 15 }, doubleWideChar("B").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 24, 15 }, doubleWideChar("c").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 26, 15 }, doubleWideChar("d").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 28, 15 }, doubleWideChar("e").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 32, 15 }, doubleWideChar("G").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, }; if (tileRight == TERMPAINT_COPY_NO_TILE) { expectedCells[{ 30, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); expectedCells[{ 31, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } else if (tileRight == TERMPAINT_COPY_TILE_PUT) { expectedCells[{ 30, 15 }] = doubleWideChar("f").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); } else if (tileRight == TERMPAINT_COPY_TILE_PRESERVE) { expectedCells[{ 30, 15 }] = doubleWideChar("F").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } checkEmptyPlusSome(f.surface, expectedCells, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - double wide on src and dst and covering right most column of dst") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); CAPTURE(tileLeft); CAPTURE(tileRight); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors(f.surface, 69, 15, "ABCDEFG", TERMPAINT_COLOR_BRIGHT_CYAN, TERMPAINT_COLOR_BRIGHT_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_COLOR_WHITE, TERMPAINT_COLOR_BLACK); termpaint_surface_write_with_colors(s1, 6, 3, "abcdefg", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_MAGENTA); termpaint_surface_copy_rect(s1, 9, 3, 8, 1, f.surface, 72, 15, tileLeft, tileRight); std::map, Cell> expectedCells = { {{ 69, 15 }, doubleWideChar("A").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 73, 15 }, doubleWideChar("c").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 75, 15 }, doubleWideChar("d").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 77, 15 }, doubleWideChar("e").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 79, 15 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, }; if (tileLeft == TERMPAINT_COPY_NO_TILE) { expectedCells[{ 71, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); expectedCells[{ 72, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); } else if (tileLeft == TERMPAINT_COPY_TILE_PUT) { expectedCells[{ 71, 15 }] = doubleWideChar("b").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); } else if (tileLeft == TERMPAINT_COPY_TILE_PRESERVE) { expectedCells[{ 71, 15 }] = doubleWideChar("B").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } checkEmptyPlusSome(f.surface, expectedCells, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - double wide on src and dst and extending beyond the right most column of dst") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); CAPTURE(tileLeft); CAPTURE(tileRight); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors(f.surface, 69, 15, "ABCDEFG", TERMPAINT_COLOR_BRIGHT_CYAN, TERMPAINT_COLOR_BRIGHT_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 80, 24)); termpaint_surface_clear(s1, TERMPAINT_COLOR_WHITE, TERMPAINT_COLOR_BLACK); termpaint_surface_write_with_colors(s1, 6, 3, "abcdefg", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_MAGENTA); termpaint_surface_copy_rect(s1, 9, 3, 9, 1, f.surface, 72, 15, tileLeft, tileRight); std::map, Cell> expectedCells = { {{ 69, 15 }, doubleWideChar("A").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 73, 15 }, doubleWideChar("c").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 75, 15 }, doubleWideChar("d").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 77, 15 }, doubleWideChar("e").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 79, 15 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, }; if (tileLeft == TERMPAINT_COPY_NO_TILE) { expectedCells[{ 71, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); expectedCells[{ 72, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); } else if (tileLeft == TERMPAINT_COPY_TILE_PUT) { expectedCells[{ 71, 15 }] = doubleWideChar("b").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); } else if (tileLeft == TERMPAINT_COPY_TILE_PRESERVE) { expectedCells[{ 71, 15 }] = doubleWideChar("B").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } checkEmptyPlusSome(f.surface, expectedCells, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } TEST_CASE("copy - double wide on src and dst and extending beyond the right most column of src") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); CAPTURE(tileLeft); CAPTURE(tileRight); termpaint_surface_clear(f.surface, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors(f.surface, 20, 15, "ABCDEFG", TERMPAINT_COLOR_BRIGHT_CYAN, TERMPAINT_COLOR_BRIGHT_GREEN); usurface_ptr s1; s1.reset(termpaint_terminal_new_surface(f.terminal, 15, 24)); termpaint_surface_clear(s1, TERMPAINT_COLOR_WHITE, TERMPAINT_COLOR_BLACK); termpaint_surface_write_with_colors(s1, 6, 3, "abcdefg", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_MAGENTA); termpaint_surface_copy_rect(s1, 9, 3, 8, 1, f.surface, 23, 15, tileLeft, tileRight); std::map, Cell> expectedCells = { {{ 20, 15 }, doubleWideChar("A").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 24, 15 }, doubleWideChar("c").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 26, 15 }, doubleWideChar("d").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 28, 15 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA)}, {{ 29, 15 }, singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 30, 15 }, doubleWideChar("F").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, {{ 32, 15 }, doubleWideChar("G").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN)}, }; if (tileLeft == TERMPAINT_COPY_NO_TILE) { expectedCells[{ 22, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); expectedCells[{ 23, 15 }] = singleWideChar(" ").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); } else if (tileLeft == TERMPAINT_COPY_TILE_PUT) { expectedCells[{ 22, 15 }] = doubleWideChar("b").withFg(TERMPAINT_COLOR_YELLOW).withBg(TERMPAINT_COLOR_MAGENTA); } else if (tileLeft == TERMPAINT_COPY_TILE_PRESERVE) { expectedCells[{ 22, 15 }] = doubleWideChar("B").withFg(TERMPAINT_COLOR_BRIGHT_CYAN).withBg(TERMPAINT_COLOR_BRIGHT_GREEN); } checkEmptyPlusSome(f.surface, expectedCells, singleWideChar(TERMPAINT_ERASED).withFg(TERMPAINT_COLOR_CYAN).withBg(TERMPAINT_COLOR_GREEN)); } static void termpaintp_test_surface_copy_rect_same_surface(termpaint_surface *dst_surface, int x, int y, int width, int height, int dst_x, int dst_y, int tile_left, int tile_right) { // simple robust implementation to compare real implementation against auto src_surface = usurface_ptr::take_ownership(termpaint_surface_new_surface(dst_surface, termpaint_surface_width(dst_surface), termpaint_surface_height(dst_surface))); termpaint_surface_copy_rect(dst_surface, 0, 0, termpaint_surface_width(dst_surface), termpaint_surface_height(dst_surface), src_surface, 0, 0, TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_NO_TILE); termpaint_surface_copy_rect(src_surface, x, y, width, height, dst_surface, dst_x, dst_y, tile_left, tile_right); } static void loremipsumify(termpaint_surface *dst_surface) { // this is a random mix of normal ascii and full width variants of ascii letters. const char* text[] = { "Lorem ipsum dolor sit amet, consectetur adipisici el ", "it, sed eiusmod tempor incidunt ut labore et dolore ma", "gna aliqua. Ut enim ad minim veniam, quis nostrud exer ", "citation ullamco laboris nisi ut aliquid ex ea commodi c", "onsequat. Quis aute iure reprehenderit in voluptate ", "velit esse cillum dolore eu fugiat nulla pariatur. Except", "eur sint obcaecat cupiditat non proident, sunt in culpa q", "ui officia deserunt mollit anim id est laborum. Duis ", " autem vel eum iriure dolor in hendrerit in vulputate ", "velit esse molestie consequat, vel illum dolore eu feug", "iat nulla facilisis at vero eros et accumsan et ", "iusto odio dignissim qui blandit praesent luptatum  ", "zzril delenit augue duis dolore te feugait nulla fac", "ilisi. Lorem ipsum dolor sit amet, consectetuer adipis ", "cing elit, sed diam nonummy nibh euismod tincidunt ut ", "laoreet dolore magna aliquam erat volutpat. Ut wisi ", "enim ad minim veniam, quis nostrud exerci tation u ", "llamcorper suscipit lobortis nisl ut aliquip ex ea", " commodo consequat. Duis autem vel eum iriure dolor ", "in hendrerit in vulputate velit esse molestie consequ", "at, vel illum dolore eu feugiat nulla facilisis at vero ", " eros et accumsan et iusto odio dignissim qui blandit pr ", "aesent luptatum zzril delenit augue duis dolore te ", "feugait nulla facilisi. Nam liber tempor cum soluta nobis", }; for (int i = 0; i < 24; i++) { termpaint_surface_write_with_colors(dst_surface, 0, i, text[i], TERMPAINT_COLOR_WHITE, TERMPAINT_COLOR_BLACK); } } TEST_CASE("copy - same surface - same location") { Fixture f{80, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); // just get some coverage. nothing specific here. auto place = [] (int v) { if (v < 0) { return 80 - v; } else { return v; } }; int x = place(GENERATE(range(-15, 15))); int y = GENERATE(0, 8, 16); int width = GENERATE(range(0, 10)); int height = GENERATE(1, 2, 4, 8); int dst_x = x; int dst_y = y; CAPTURE(tileLeft); CAPTURE(tileRight); CAPTURE(x); CAPTURE(width); loremipsumify(f.surface); auto dup = usurface_ptr::take_ownership(termpaint_surface_duplicate(f.surface)); CHECK(termpaint_surface_same_contents(f.surface, dup)); termpaintp_test_surface_copy_rect_same_surface(dup, x, y, width, height, dst_x, dst_y, tileLeft, tileRight); termpaint_surface_copy_rect(f.surface, x, y, width, height, f.surface, dst_x, dst_y, tileLeft, tileRight); CHECK(termpaint_surface_same_contents(f.surface, dup)); } TEST_CASE("copy - same surface") { Fixture f{40, 24}; auto tileLeft = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); auto tileRight = GENERATE(TERMPAINT_COPY_NO_TILE, TERMPAINT_COPY_TILE_PUT, TERMPAINT_COPY_TILE_PRESERVE); // just get some coverage. nothing specific here. auto place = [] (int v) { if (v < 0) { return 40 - v; } else { return v; } }; int x = place(GENERATE(range(-10, 10))); int y = 8; int width = GENERATE(1, 2, 5); int height = 4; int dst_x = x + GENERATE(range(-7, 3)); int dst_y = GENERATE(0, 8, 16); CAPTURE(tileLeft); CAPTURE(tileRight); CAPTURE(x); CAPTURE(width); loremipsumify(f.surface); auto dup = usurface_ptr::take_ownership(termpaint_surface_duplicate(f.surface)); CHECK(termpaint_surface_same_contents(f.surface, dup)); termpaintp_test_surface_copy_rect_same_surface(dup, x, y, width, height, dst_x, dst_y, tileLeft, tileRight); termpaint_surface_copy_rect(f.surface, x, y, width, height, f.surface, dst_x, dst_y, tileLeft, tileRight); CHECK(termpaint_surface_same_contents(f.surface, dup)); } // internal but exposed extern "C" { bool termpaintp_test(); } TEST_CASE("termpaintp_test") { bool x = termpaintp_test(); CHECK(x); } termpaint-0.3.1/tests/terminal_misc.cpp000066400000000000000000000004011477303547200202210ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include #include #ifndef BUNDLED_CATCH2 #ifdef CATCH3 #include "catch2/catch_all.hpp" #else #include "catch2/catch.hpp" #endif #else #include "../third-party/catch.hpp" #endif #include termpaint-0.3.1/tests/terminaloutput.cpp000066400000000000000000002476531477303547200205150ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef BUNDLED_CATCH2 #ifdef CATCH3 #include "catch2/catch_all.hpp" #else #include "catch2/catch.hpp" #endif #else #include "../third-party/catch.hpp" #endif #include "../termpaint.h" #include "../termpaintx.h" #include "terminaloutput.h" #include #include #include template using DEL = void(T*); template del> struct Deleter{ void operator()(T* t) { del(t); } }; template del> struct unique_cptr : public std::unique_ptr> { operator T*() { return this->get(); } }; using uattr_ptr = unique_cptr; struct SimpleFullscreen { SimpleFullscreen(bool altScreen = true, bool reset = true) { if (reset) { resetAndClear(); } integration = termpaintx_full_integration_from_fd(1, 0, "+kbdsigint +kbdsigtstp"); REQUIRE(integration); terminal.reset(termpaint_terminal_new(integration)); REQUIRE(terminal); termpaintx_full_integration_set_terminal(integration, terminal); surface = termpaint_terminal_get_surface(terminal); REQUIRE(surface); termpaint_terminal_set_event_cb(terminal, [] (void *, termpaint_event*) {}, nullptr); termpaint_terminal_auto_detect(terminal); termpaintx_full_integration_wait_for_ready_with_message(integration, 10000, "Terminal auto detection is taking unusually long, press space to abort."); if (altScreen) { termpaint_terminal_setup_fullscreen(terminal, 80, 24, "+kbdsig"); } else { termpaint_terminal_setup_fullscreen(terminal, 80, 24, "+kbdsig -altscreen"); } } unique_cptr terminal; termpaint_surface *surface; termpaint_integration *integration; }; class Overrides { public: Overrides withSoftWrappedLines(std::vector lines) { softWrappedLines = lines; return *this; } Overrides noAltScreen() { altScreen = false; return *this; } public: std::vector softWrappedLines; bool altScreen = true; }; using CellMap = std::map, CapturedCell>; class SomeCells : public CellMap { public: using CellMap::CellMap; SomeCells extend(CellMap addition) { SomeCells ret = *this; ret.insert(std::begin(addition), std::end(addition)); return ret; } template SomeCells extend(CellMap addition, Additions... additions) { return extend(addition).extend(additions...); } }; void checkEmptyPlusSome(const CapturedState &s, const std::map, CapturedCell> &some, const Overrides overrides = {}) { CHECK(s.width == 80); CHECK(s.height == 24); CHECK(s.altScreen == overrides.altScreen); for (const CapturedRow& row: s.rows) { { INFO("y = " << row.cells[0].y); bool expectedSoftWrapped = std::find(overrides.softWrappedLines.begin(), overrides.softWrappedLines.end(), row.cells[0].y) != overrides.softWrappedLines.end(); CHECK(row.softWrapped == expectedSoftWrapped); } for (const CapturedCell& cell: row.cells) { CAPTURE(cell.x); CAPTURE(cell.y); if (some.count({cell.x, cell.y})) { auto& expected = some.at({cell.x, cell.y}); CHECK(cell.bg == expected.bg); CHECK(cell.fg == expected.fg); CHECK(cell.deco == expected.deco); CHECK(cell.data == expected.data); CHECK(cell.style == expected.style); CHECK(cell.width == expected.width); CHECK(cell.erased == expected.erased); } else { bool expectErased = true; for (int i = cell.x + 1; i < s.width; i++) { // terminals only track trailing erased cells if (some.count({i, cell.y}) && !some.at({i, cell.y}).erased) { expectErased = false; break; } } CHECK(cell.bg == ""); CHECK(cell.fg == ""); CHECK(cell.deco == ""); CHECK(cell.data == " "); CHECK(cell.style == 0); CHECK(cell.width == 1); CHECK(cell.erased == expectErased); } } } } CapturedCell singleWideChar(std::string ch) { CapturedCell c; c.data = ch; return c; } CapturedCell doubleWideChar(std::string ch) { CapturedCell c; c.data = ch; c.width = 2; return c; } // only works for ascii std::map, CapturedCell> lineOfText(int y, std::string text) { std::map, CapturedCell> data; int x = 0; for (char ch: text) { data.insert({{x++, y}, singleWideChar(std::string(1, ch))}); } return data; } struct SimpleInline { SimpleInline(int height, SomeCells* base_ = nullptr) { if (!base_) { resetAndClear(); puts("user@host:~$ ls -1"); puts("Desktop"); puts("Downloads"); puts("Pictures"); puts("user@host:~$ app"); base = SomeCells().extend( lineOfText(0, "user@host:~$ ls -1"), lineOfText(1, "Desktop"), lineOfText(2, "Downloads"), lineOfText(3, "Pictures"), lineOfText(4, "user@host:~$ app") ); } else { base = *base_; } CapturedState s = capture(); if (!base_) { CHECK(s.cursorX == 0); CHECK(s.cursorY == 5); } checkEmptyPlusSome(s, base, Overrides().noAltScreen()); integration = termpaintx_full_integration_from_fd(1, 0, "+kbdsigint +kbdsigtstp"); REQUIRE(integration); terminal.reset(termpaint_terminal_new(integration)); REQUIRE(terminal); termpaintx_full_integration_set_terminal(integration, terminal); surface = termpaint_terminal_get_surface(terminal); REQUIRE(surface); termpaint_terminal_set_event_cb(terminal, [] (void *, termpaint_event*) {}, nullptr); termpaint_terminal_auto_detect(terminal); termpaintx_full_integration_wait_for_ready_with_message(integration, 10000, "Terminal auto detection is taking unusually long, press space to abort."); termpaint_terminal_setup_inline(terminal, 80, height, "+kbdsig"); termpaintx_full_integration_set_inline(integration, true, height); } unique_cptr terminal; termpaint_surface *surface; termpaint_integration *integration; SomeCells base; }; TEST_CASE("no init") { SimpleFullscreen t; termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); checkEmptyPlusSome(s, {}); CHECK(s.altScreen == true); CHECK(s.invScreen == false); } TEST_CASE("empty") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); checkEmptyPlusSome(s, {}); CHECK(s.altScreen == true); CHECK(s.invScreen == false); } TEST_CASE("restore") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); termpaint_terminal_flush(t.terminal, false); t.terminal.reset(); CapturedState s = capture(); CHECK(s.altScreen == false); CHECK(s.invScreen == false); CHECK(s.sgrState.style == 0); CHECK(s.sgrState.fg == std::string()); CHECK(s.sgrState.bg == std::string()); CHECK(s.sgrState.deco == std::string()); } TEST_CASE("restore - no fullscreen") { resetAndClear(); termpaint_integration *integration = termpaintx_full_integration_from_fd(1, 0, "+kbdsigint +kbdsigtstp"); REQUIRE(integration); unique_cptr terminal; terminal.reset(termpaint_terminal_new(integration)); REQUIRE(terminal); termpaintx_full_integration_set_terminal(integration, terminal); termpaint_surface *surface = termpaint_terminal_get_surface(terminal); REQUIRE(surface); termpaint_terminal_set_event_cb(terminal, [] (void *, termpaint_event*) {}, nullptr); termpaint_terminal_auto_detect(terminal); termpaintx_full_integration_wait_for_ready_with_message(integration, 10000, "Terminal auto detection is taking unusually long, press space to abort."); termpaint_surface_clear(surface, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); termpaint_terminal_flush(terminal, false); terminal.reset(); CapturedState s = capture(); CHECK(s.altScreen == false); CHECK(s.invScreen == false); CHECK(s.sgrState.style == 0); CHECK(s.sgrState.fg == std::string()); CHECK(s.sgrState.bg == std::string()); CHECK(s.sgrState.deco == std::string()); } TEST_CASE("restore with persistent") { resetAndClear(); puts("user@host:~$ ls -1"); puts("Desktop"); puts("user@host:~$ app"); CapturedState s = capture(); CHECK(s.cursorX == 0); CHECK(s.cursorY == 3); SimpleFullscreen t{true, false}; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 10, 3, "Sample", TERMPAINT_COLOR_GREEN, TERMPAINT_COLOR_BLACK); termpaint_terminal_set_cursor_position(t.terminal, 6, 13); termpaint_terminal_flush(t.terminal, false); s = capture(); CHECK(s.cursorX == 6); CHECK(s.cursorY == 13); checkEmptyPlusSome(s, { {{ 10, 3 }, singleWideChar("S").withFg("green").withBg("black")}, {{ 11, 3 }, singleWideChar("a").withFg("green").withBg("black")}, {{ 12, 3 }, singleWideChar("m").withFg("green").withBg("black")}, {{ 13, 3 }, singleWideChar("p").withFg("green").withBg("black")}, {{ 14, 3 }, singleWideChar("l").withFg("green").withBg("black")}, {{ 15, 3 }, singleWideChar("e").withFg("green").withBg("black")}, }); CHECK(s.altScreen == true); CHECK(s.invScreen == false); unique_cptr persistent_surface; persistent_surface.reset(termpaint_terminal_new_surface(t.terminal, 40, 3)); termpaint_surface_write_with_colors(persistent_surface, 0, 0, "Some text here", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(persistent_surface, 5, 2, "Something else", TERMPAINT_COLOR_RED, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_free_with_restore_and_persistent(t.terminal.release(), persistent_surface); s = capture(); auto base = SomeCells({ {{ 5, 5 }, singleWideChar("S").withFg("red")}, {{ 6, 5}, singleWideChar("o").withFg("red")}, {{ 7, 5}, singleWideChar("m").withFg("red")}, {{ 8, 5}, singleWideChar("e").withFg("red")}, {{ 9, 5}, singleWideChar("t").withFg("red")}, {{ 10, 5}, singleWideChar("h").withFg("red")}, {{ 11, 5}, singleWideChar("i").withFg("red")}, {{ 12, 5}, singleWideChar("n").withFg("red")}, {{ 13, 5}, singleWideChar("g").withFg("red")}, {{ 14, 5}, singleWideChar(" ").withFg("red")}, {{ 15, 5}, singleWideChar("e").withFg("red")}, {{ 16, 5}, singleWideChar("l").withFg("red")}, {{ 17, 5}, singleWideChar("s").withFg("red")}, {{ 18, 5}, singleWideChar("e").withFg("red")}, }).extend( lineOfText(0, "user@host:~$ ls -1"), lineOfText(1, "Desktop"), lineOfText(2, "user@host:~$ app"), lineOfText(3, "Some text here") ); checkEmptyPlusSome(s, base, Overrides().noAltScreen()); CHECK(s.cursorX == 0); CHECK(s.cursorY == 6); CHECK(s.altScreen == false); } TEST_CASE("inline") { resetAndClear(); puts("user@host:~$ ls -1"); puts("Desktop"); puts("Downloads"); puts("Pictures"); puts("user@host:~$ app"); CapturedState s = capture(); CHECK(s.cursorX == 0); CHECK(s.cursorY == 5); SomeCells base = SomeCells().extend( lineOfText(0, "user@host:~$ ls -1"), lineOfText(1, "Desktop"), lineOfText(2, "Downloads"), lineOfText(3, "Pictures"), lineOfText(4, "user@host:~$ app") ); checkEmptyPlusSome(s, base, Overrides().noAltScreen()); termpaint_integration *integration = termpaintx_full_integration_from_fd(1, 0, "+kbdsigint +kbdsigtstp"); REQUIRE(integration); unique_cptr terminal; terminal.reset(termpaint_terminal_new(integration)); REQUIRE(terminal); termpaintx_full_integration_set_terminal(integration, terminal); termpaint_surface *surface = termpaint_terminal_get_surface(terminal); REQUIRE(surface); termpaint_terminal_set_event_cb(terminal, [] (void *, termpaint_event*) {}, nullptr); termpaint_terminal_auto_detect(terminal); termpaintx_full_integration_wait_for_ready_with_message(integration, 10000, "Terminal auto detection is taking unusually long, press space to abort."); termpaint_terminal_setup_inline(terminal, 80, 3, "+kbdsig"); termpaintx_full_integration_set_inline(integration, true, 3); s = capture(); checkEmptyPlusSome(s, base, Overrides().noAltScreen()); termpaint_surface_clear(surface, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); termpaint_terminal_set_cursor_position(terminal, 5, 1); termpaint_terminal_flush(terminal, false); CellMap filled; for (int y = 0; y < 3; y++) { for (int x = 0; x < 80; x++) { filled.insert({{ x, y + 5 }, singleWideChar(" ").setErased().withFg("red").withBg("blue")}); } } s = capture(); CHECK(s.cursorX == 5); CHECK(s.cursorY == 1 + 5); checkEmptyPlusSome(s, base.extend( filled ), Overrides().noAltScreen()); terminal.reset(); s = capture(); CHECK(s.cursorX == 0); CHECK(s.cursorY == 5); checkEmptyPlusSome(s, base, Overrides().noAltScreen()); CHECK(s.altScreen == false); CHECK(s.invScreen == false); CHECK(s.sgrState.style == 0); CHECK(s.sgrState.fg == std::string()); CHECK(s.sgrState.bg == std::string()); CHECK(s.sgrState.deco == std::string()); } TEST_CASE("inline expand + contract + fullscreen") { SimpleInline t{3}; termpaint_surface_clear(t.surface, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); termpaint_surface_write_with_colors(t.surface, 0, 0, "A", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 0, 1, "B", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 0, 2, "C", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); CellMap filled; for (int y = 0; y < 3; y++) { for (int x = 0; x < 80; x++) { filled.insert({{ x, y + 5 }, singleWideChar(" ").setErased().withFg("red").withBg("blue")}); } } filled[{ 0, 0 + 5 }] = singleWideChar("A"); filled[{ 0, 1 + 5 }] = singleWideChar("B"); filled[{ 0, 2 + 5 }] = singleWideChar("C"); CapturedState s = capture(); checkEmptyPlusSome(s, t.base.extend( filled ), Overrides().noAltScreen()); termpaintx_full_integration_set_inline(t.integration, true, 5); termpaint_surface_clear(t.surface, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); termpaint_surface_write_with_colors(t.surface, 0, 0, "a", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 0, 1, "b", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 0, 2, "c", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 0, 3, "d", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 0, 4, "e", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); CellMap filled2; for (int y = 0; y < 5; y++) { for (int x = 0; x < 80; x++) { filled2.insert({{ x, y + 5 }, singleWideChar(" ").setErased().withFg("red").withBg("blue")}); } } filled2[{ 0, 0 + 5 }] = singleWideChar("a"); filled2[{ 0, 1 + 5 }] = singleWideChar("b"); filled2[{ 0, 2 + 5 }] = singleWideChar("c"); filled2[{ 0, 3 + 5 }] = singleWideChar("d"); filled2[{ 0, 4 + 5 }] = singleWideChar("e"); s = capture(); checkEmptyPlusSome(s, t.base.extend( filled2 ), Overrides().noAltScreen()); termpaintx_full_integration_set_inline(t.integration, true, 2); termpaint_surface_clear(t.surface, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); termpaint_surface_write_with_colors(t.surface, 0, 0, "Y", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 0, 1, "Z", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); CellMap filled3; for (int y = 0; y < 2; y++) { for (int x = 0; x < 80; x++) { filled3.insert({{ x, y + 5 }, singleWideChar(" ").setErased().withFg("red").withBg("blue")}); } } filled3[{ 0, 0 + 5 }] = singleWideChar("Y"); filled3[{ 0, 1 + 5 }] = singleWideChar("Z"); s = capture(); checkEmptyPlusSome(s, t.base.extend( filled3 ), Overrides().noAltScreen()); termpaintx_full_integration_set_inline(t.integration, false, -1); s = capture(); checkEmptyPlusSome(s, { }); termpaint_surface_clear(t.surface, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); termpaint_surface_write_with_colors(t.surface, 0, 0, "J", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 0, 1, "K", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); CellMap filled4; for (int y = 0; y < 24; y++) { for (int x = 0; x < 80; x++) { filled4.insert({{ x, y }, singleWideChar(" ").setErased().withFg("red").withBg("blue")}); } } filled4[{ 0, 0 }] = singleWideChar("J"); filled4[{ 0, 1 }] = singleWideChar("K"); s = capture(); checkEmptyPlusSome(s, filled4); termpaintx_full_integration_set_inline(t.integration, true, -1); termpaint_surface_clear(t.surface, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); termpaint_surface_write_with_colors(t.surface, 0, 0, "Y", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 0, 1, "Z", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); s = capture(); checkEmptyPlusSome(s, t.base.extend( filled3 ), Overrides().noAltScreen()); } TEST_CASE("inline at top") { resetAndClear(); SomeCells base; SimpleInline t{3, &base}; termpaint_surface_clear(t.surface, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); termpaint_surface_write_with_colors(t.surface, 0, 0, "A", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 0, 1, "B", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 0, 2, "C", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); CellMap filled; for (int y = 0; y < 3; y++) { for (int x = 0; x < 80; x++) { filled.insert({{ x, y }, singleWideChar(" ").setErased().withFg("red").withBg("blue")}); } } filled[{ 0, 0 }] = singleWideChar("A"); filled[{ 0, 1 }] = singleWideChar("B"); filled[{ 0, 2 }] = singleWideChar("C"); CapturedState s = capture(); checkEmptyPlusSome(s, filled, Overrides().noAltScreen()); } TEST_CASE("inline at bottom") { resetAndClear(); puts(std::string(15, '\n').c_str()); puts("user@host:~$ ls -1"); puts("Desktop"); puts("Downloads"); puts("Pictures"); puts("user@host:~$ app"); SomeCells base = SomeCells().extend( lineOfText(16, "user@host:~$ ls -1"), lineOfText(17, "Desktop"), lineOfText(18, "Downloads"), lineOfText(19, "Pictures"), lineOfText(20, "user@host:~$ app") ); SimpleInline t{3, &base}; termpaint_surface_clear(t.surface, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); termpaint_surface_write_with_colors(t.surface, 0, 0, "A", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 0, 1, "B", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 0, 2, "C", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); CellMap filled; for (int y = 0; y < 3; y++) { for (int x = 0; x < 80; x++) { filled.insert({{ x, y + 21 }, singleWideChar(" ").setErased().withFg("red").withBg("blue")}); } } filled[{ 0, 0 + 21 }] = singleWideChar("A"); filled[{ 0, 1 + 21 }] = singleWideChar("B"); filled[{ 0, 2 + 21 }] = singleWideChar("C"); CapturedState s = capture(); checkEmptyPlusSome(s, t.base.extend( filled ), Overrides().noAltScreen()); } TEST_CASE("inline scroll bottom") { resetAndClear(); puts(std::string(16, '\n').c_str()); puts("user@host:~$ ls -1"); puts("Desktop"); puts("Downloads"); puts("Pictures"); puts("user@host:~$ app"); SomeCells preScroll = SomeCells().extend( lineOfText(17, "user@host:~$ ls -1"), lineOfText(18, "Desktop"), lineOfText(19, "Downloads"), lineOfText(20, "Pictures"), lineOfText(21, "user@host:~$ app") ); SimpleInline t{3, &preScroll}; termpaint_surface_clear(t.surface, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); termpaint_surface_write_with_colors(t.surface, 0, 0, "A", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 0, 1, "B", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 0, 2, "C", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); SomeCells base = SomeCells().extend( lineOfText(16, "user@host:~$ ls -1"), lineOfText(17, "Desktop"), lineOfText(18, "Downloads"), lineOfText(19, "Pictures"), lineOfText(20, "user@host:~$ app") ); CellMap filled; for (int y = 0; y < 3; y++) { for (int x = 0; x < 80; x++) { filled.insert({{ x, y + 21 }, singleWideChar(" ").setErased().withFg("red").withBg("blue")}); } } filled[{ 0, 0 + 21 }] = singleWideChar("A"); filled[{ 0, 1 + 21 }] = singleWideChar("B"); filled[{ 0, 2 + 21 }] = singleWideChar("C"); CapturedState s = capture(); checkEmptyPlusSome(s, base.extend( filled ), Overrides().noAltScreen()); } TEST_CASE("restore inline with persistent") { resetAndClear(); puts("user@host:~$ ls -1"); puts("Desktop"); puts("user@host:~$ app"); CapturedState s = capture(); CHECK(s.altScreen == false); CHECK(s.cursorX == 0); CHECK(s.cursorY == 3); SomeCells base = SomeCells().extend( lineOfText(0, "user@host:~$ ls -1"), lineOfText(1, "Desktop"), lineOfText(2, "user@host:~$ app") ); SimpleInline t{3, &base}; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 10, 2, "Sample", TERMPAINT_COLOR_GREEN, TERMPAINT_COLOR_BLACK); termpaint_terminal_set_cursor_position(t.terminal, 6, 0); termpaint_terminal_flush(t.terminal, false); s = capture(); CHECK(s.cursorX == 6); CHECK(s.cursorY == 3); checkEmptyPlusSome(s, base.extend({ {{ 10, 5 }, singleWideChar("S").withFg("green").withBg("black")}, {{ 11, 5 }, singleWideChar("a").withFg("green").withBg("black")}, {{ 12, 5 }, singleWideChar("m").withFg("green").withBg("black")}, {{ 13, 5 }, singleWideChar("p").withFg("green").withBg("black")}, {{ 14, 5 }, singleWideChar("l").withFg("green").withBg("black")}, {{ 15, 5 }, singleWideChar("e").withFg("green").withBg("black")}, }), Overrides{}.noAltScreen()); CHECK(s.altScreen == false); CHECK(s.invScreen == false); unique_cptr persistent_surface; persistent_surface.reset(termpaint_terminal_new_surface(t.terminal, 40, 3)); termpaint_surface_write_with_colors(persistent_surface, 0, 0, "Some text here", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(persistent_surface, 5, 2, "Something else", TERMPAINT_COLOR_RED, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_free_with_restore_and_persistent(t.terminal.release(), persistent_surface); s = capture(); base = SomeCells({ {{ 5, 5 }, singleWideChar("S").withFg("red")}, {{ 6, 5}, singleWideChar("o").withFg("red")}, {{ 7, 5}, singleWideChar("m").withFg("red")}, {{ 8, 5}, singleWideChar("e").withFg("red")}, {{ 9, 5}, singleWideChar("t").withFg("red")}, {{ 10, 5}, singleWideChar("h").withFg("red")}, {{ 11, 5}, singleWideChar("i").withFg("red")}, {{ 12, 5}, singleWideChar("n").withFg("red")}, {{ 13, 5}, singleWideChar("g").withFg("red")}, {{ 14, 5}, singleWideChar(" ").withFg("red")}, {{ 15, 5}, singleWideChar("e").withFg("red")}, {{ 16, 5}, singleWideChar("l").withFg("red")}, {{ 17, 5}, singleWideChar("s").withFg("red")}, {{ 18, 5}, singleWideChar("e").withFg("red")}, }).extend( lineOfText(0, "user@host:~$ ls -1"), lineOfText(1, "Desktop"), lineOfText(2, "user@host:~$ app"), lineOfText(3, "Some text here") ); checkEmptyPlusSome(s, base, Overrides().noAltScreen()); CHECK(s.cursorX == 0); CHECK(s.cursorY == 6); CHECK(s.altScreen == false); } TEST_CASE("simple text") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 10, 3, "Sample", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); checkEmptyPlusSome(s, { {{ 10, 3 }, singleWideChar("S")}, {{ 11, 3 }, singleWideChar("a")}, {{ 12, 3 }, singleWideChar("m")}, {{ 13, 3 }, singleWideChar("p")}, {{ 14, 3 }, singleWideChar("l")}, {{ 15, 3 }, singleWideChar("e")}, }); } TEST_CASE("double width") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 3, 3, "あえ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); checkEmptyPlusSome(s, { {{ 3, 3 }, doubleWideChar("あ")}, {{ 5, 3 }, doubleWideChar("え")}, }); } TEST_CASE("chars that get substituted") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 3, 3, "a\004\u00ad\u0088x", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); checkEmptyPlusSome(s, { {{ 3, 3 }, singleWideChar("a")}, {{ 4, 3 }, singleWideChar(" ")}, {{ 5, 3 }, singleWideChar("-")}, {{ 6, 3 }, singleWideChar(" ")}, {{ 7, 3 }, singleWideChar("x")}, }); } TEST_CASE("vanish chars") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 3, 3, "あえ", TERMPAINT_COLOR_RED, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors(t.surface, 4, 3, "ab", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_BLUE); termpaint_terminal_flush(t.terminal, true); CapturedState s = capture(); checkEmptyPlusSome(s, { {{ 3, 3 }, singleWideChar(" ").withBg("green").withFg("red")}, {{ 4, 3 }, singleWideChar("a").withBg("blue").withFg("yellow")}, {{ 5, 3 }, singleWideChar("b").withBg("blue").withFg("yellow")}, {{ 6, 3 }, singleWideChar(" ").withBg("green").withFg("red")}, }); } TEST_CASE("vanish chars - incremental") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 3, 3, "あえ", TERMPAINT_COLOR_RED, TERMPAINT_COLOR_GREEN); termpaint_terminal_flush(t.terminal, false); termpaint_surface_write_with_colors(t.surface, 4, 3, "ab", TERMPAINT_COLOR_YELLOW, TERMPAINT_COLOR_BLUE); termpaint_terminal_flush(t.terminal, true); CapturedState s = capture(); checkEmptyPlusSome(s, { {{ 3, 3 }, singleWideChar(" ").withBg("green").withFg("red")}, {{ 4, 3 }, singleWideChar("a").withBg("blue").withFg("yellow")}, {{ 5, 3 }, singleWideChar("b").withBg("blue").withFg("yellow")}, {{ 6, 3 }, singleWideChar(" ").withBg("green").withFg("red")}, }); } TEST_CASE("rgb colors") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 3, 3, "r", TERMPAINT_RGB_COLOR(255, 128, 128), TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 4, 3, "g", TERMPAINT_RGB_COLOR(128, 255, 128), TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 5, 3, "b", TERMPAINT_RGB_COLOR(128, 128, 255), TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 3, 4, "r", TERMPAINT_DEFAULT_COLOR, TERMPAINT_RGB_COLOR(255, 128, 128)); termpaint_surface_write_with_colors(t.surface, 4, 4, "g", TERMPAINT_DEFAULT_COLOR, TERMPAINT_RGB_COLOR(128, 255, 128)); termpaint_surface_write_with_colors(t.surface, 5, 4, "b", TERMPAINT_DEFAULT_COLOR, TERMPAINT_RGB_COLOR(128, 128, 255)); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); checkEmptyPlusSome(s, { {{ 3, 3 }, singleWideChar("r").withFg("#ff8080")}, {{ 4, 3 }, singleWideChar("g").withFg("#80ff80")}, {{ 5, 3 }, singleWideChar("b").withFg("#8080ff")}, {{ 3, 4 }, singleWideChar("r").withBg("#ff8080")}, {{ 4, 4 }, singleWideChar("g").withBg("#80ff80")}, {{ 5, 4 }, singleWideChar("b").withBg("#8080ff")}, }); } TEST_CASE("rgb colors with quantize to 256 colors - grey, misc and grid points") { SimpleFullscreen t; termpaint_terminal_disable_capability(t.terminal, TERMPAINT_CAPABILITY_TRUECOLOR_MAYBE_SUPPORTED); termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); // test that colors of the from (i,i,i) are converted to the nearest color on the grey ramp // or the nearest color from the 6x6x6 cube. // for each (except first and last) palette color there is one entry for the lower bound, // one entry for the palette color itself and one entry for the upper bound. const std::initializer_list> greyMain = { {"16", 0}, {"16", 4}, {"232", 5}, {"232", 8}, {"232", 12}, {"233", 13}, {"233", 18}, {"233", 22}, {"234", 23}, {"234", 28}, {"234", 32}, {"235", 33}, {"235", 38}, {"235", 42}, {"236", 43}, {"236", 48}, {"236", 52}, {"237", 53}, {"237", 58}, {"237", 62}, {"238", 63}, {"238", 68}, {"238", 72}, {"239", 73}, {"239", 78}, {"239", 82}, {"240", 83}, {"240", 88}, {"240", 91}, {"59", 92}, {"59", 95}, {"59", 96}, // 16 + 36 + 6 + 1 {"241", 97}, {"241", 98}, {"241", 102}, {"242", 103}, {"242", 108}, {"242", 112}, {"243", 113}, {"243", 118}, {"243", 122}, {"244", 123}, {"244", 128}, {"244", 131}, {"102", 132}, {"102", 135}, {"102", 136}, // 16 + 2*36 + 2*6 + 2*1 {"245", 137}, {"245", 138}, {"245", 142}, {"246", 143}, {"246", 148}, {"246", 152}, {"247", 153}, {"247", 158}, {"247", 162}, {"248", 163}, {"248", 168}, {"248", 171}, {"145", 172}, {"145", 175}, {"145", 176}, // 16 + 3*36 + 3*6 + 3*1 {"249", 177}, {"249", 178}, {"249", 182}, {"250", 183}, {"250", 188}, {"250", 192}, {"251", 193}, {"251", 198}, {"251", 202}, {"252", 203}, {"252", 208}, {"252", 211}, {"188", 212}, {"188", 215}, {"188", 216}, // 16 + 4*36 + 4*6 + 4*1 {"253", 217}, {"253", 218}, {"253", 222}, {"254", 223}, {"254", 228}, {"254", 232}, {"255", 233}, {"255", 238}, {"255", 246}, {"231", 247}, {"231", 255} // 16 + 5*36 + 5*6 + 5*1 }; for (size_t i=0; i < greyMain.size(); i++) { int val = std::get<1>(*(greyMain.begin()+i)); termpaint_surface_write_with_colors(t.surface, i, 0, "x", TERMPAINT_DEFAULT_COLOR, TERMPAINT_RGB_COLOR(val, val, val)); } int column = 0; int row = 1; const std::initializer_list> additionalTests = { {TERMPAINT_RGB_COLOR(255, 128, 128), "210", TERMPAINT_DEFAULT_COLOR, ""}, // 255,135,135 -> 16 + 5*36 + 2*6 + 2 {TERMPAINT_RGB_COLOR(128, 255, 128), "120", TERMPAINT_DEFAULT_COLOR, ""}, // 135,255,135 -> 16 + 2*36 + 5*6 + 2 {TERMPAINT_RGB_COLOR(128, 128, 255), "105", TERMPAINT_DEFAULT_COLOR, ""}, // 135,135,255 -> 16 + 2*36 + 2*6 + 5 {TERMPAINT_DEFAULT_COLOR, "", TERMPAINT_RGB_COLOR(255, 128, 128), "210"}, // 255,135,135 -> 16 + 5*36 + 2*6 + 2 {TERMPAINT_DEFAULT_COLOR, "", TERMPAINT_RGB_COLOR(128, 255, 128), "120"}, // 135,255,135 -> 16 + 2*36 + 5*6 + 2 {TERMPAINT_DEFAULT_COLOR, "", TERMPAINT_RGB_COLOR(128, 128, 255), "105"}, // 135,135,255 -> 16 + 2*36 + 2*6 + 5 }; for (auto test : additionalTests) { termpaint_surface_write_with_colors(t.surface, column, row, "a", std::get<0>(test), std::get<2>(test)); if (++column == 80) { column = 0; ++row; } } const auto grid = { 0, 95, 135, 175, 215, 255 }; // exact palette grid values for (int r : grid) for (int g : grid) for (int b : grid) { termpaint_surface_write_with_colors(t.surface, column, row, "y", TERMPAINT_DEFAULT_COLOR, TERMPAINT_RGB_COLOR(r, g, b)); if (++column == 80) { column = 0; ++row; } } CHECK(row < 24); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); std::map, CapturedCell> expected; for (size_t i=0; i < greyMain.size(); i++) { std::string val = std::get<0>(*(greyMain.begin()+i)); expected[{i, 0}] = singleWideChar("x").withBg(val); } column = 0; row = 1; for (auto test : additionalTests) { auto cell = singleWideChar("a"); if (std::get<1>(test).size()) { cell = cell.withFg(std::get<1>(test)); } if (std::get<3>(test).size()) { cell = cell.withBg(std::get<3>(test)); } expected[{column, row}] = cell; if (++column == 80) { column = 0; ++row; } } // exact palette grid values for (size_t r_idx = 0; r_idx < grid.size(); r_idx++) for (size_t g_idx = 0; g_idx < grid.size(); g_idx++) for (size_t b_idx = 0; b_idx < grid.size(); b_idx++) { expected[{column, row}] = singleWideChar("y").withBg(std::to_string(16 + r_idx*36 + g_idx*6 + b_idx)); if (++column == 80) { column = 0; ++row; } } checkEmptyPlusSome(s, expected); } TEST_CASE("rgb colors with quantize to 256 colors - grid bounds") { SimpleFullscreen t; termpaint_terminal_disable_capability(t.terminal, TERMPAINT_CAPABILITY_TRUECOLOR_MAYBE_SUPPORTED); termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); const auto grid = { 0, 95, 135, 175, 215, 255 }; int column = 0; int row = 0; // (value, index) const std::initializer_list> gridBounds = { {0, 0}, {47, 0}, {48, 1}, {114, 1}, {115, 2}, {154, 2}, {155, 3}, {194, 3}, {195, 4}, {234, 4}, {235, 5}, {255, 5} }; const auto grey_ramp = {8, 18, 28, 38, 48, 58, 68, 78, 88, 98, 108, 118, 128, 138, 148, 158, 168, 178, 188, 198, 208, 218, 228, 238}; // check bounds of boxes for (auto r_t : gridBounds) for (auto g_t : gridBounds) for (auto b_t : gridBounds) { int r_val = std::get<0>(r_t); int g_val = std::get<0>(g_t); int b_val = std::get<0>(b_t); termpaint_surface_write_with_colors(t.surface, column, row, "z", TERMPAINT_DEFAULT_COLOR, TERMPAINT_RGB_COLOR(r_val, g_val, b_val)); if (++column == 80) { column = 0; ++row; } } CHECK(row < 24); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); std::map, CapturedCell> expected; column = 0; row = 0; // check bounds of boxes for (auto r_t : gridBounds) for (auto g_t : gridBounds) for (auto b_t : gridBounds) { int r_center = *(grid.begin() + std::get<1>(r_t)); int r_idx = std::get<1>(r_t); int r_val = std::get<0>(r_t); int g_center = *(grid.begin() + std::get<1>(g_t)); int g_idx = std::get<1>(g_t); int g_val = std::get<0>(g_t); int b_center = *(grid.begin() + std::get<1>(b_t)); int b_idx = std::get<1>(b_t); int b_val = std::get<0>(b_t); std::string expected_color = std::to_string(16 + r_idx*36 + g_idx*6 + b_idx); auto sq = [](int x) { return x*x; }; int best_metric = sq(r_center - r_val) + sq(g_center - g_val) + sq(b_center - b_val); for (size_t grey_index = 0; grey_index < grey_ramp.size(); grey_index++) { const int grey_quantized = *(grey_ramp.begin() + grey_index); const int cur_metric = sq(grey_quantized - r_val) + sq(grey_quantized - g_val) + sq(grey_quantized - b_val); if (cur_metric < best_metric) { expected_color = std::to_string(232 + grey_index); best_metric = cur_metric; } } expected[{column, row}] = singleWideChar("z").withBg(expected_color); if (++column == 80) { column = 0; ++row; } } checkEmptyPlusSome(s, expected); } TEST_CASE("named fg colors") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 3, 3, " ", TERMPAINT_COLOR_BLACK, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 4, 3, " ", TERMPAINT_COLOR_RED, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 5, 3, " ", TERMPAINT_COLOR_GREEN, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 6, 3, " ", TERMPAINT_COLOR_YELLOW, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 7, 3, " ", TERMPAINT_COLOR_BLUE, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 8, 3, " ", TERMPAINT_COLOR_MAGENTA, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 9, 3, " ", TERMPAINT_COLOR_CYAN, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 10, 3, " ", TERMPAINT_COLOR_LIGHT_GREY, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 3, 4, " ", TERMPAINT_COLOR_DARK_GREY, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 4, 4, " ", TERMPAINT_COLOR_BRIGHT_RED, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 5, 4, " ", TERMPAINT_COLOR_BRIGHT_GREEN, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 6, 4, " ", TERMPAINT_COLOR_BRIGHT_YELLOW, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 7, 4, " ", TERMPAINT_COLOR_BRIGHT_BLUE, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 8, 4, " ", TERMPAINT_COLOR_BRIGHT_MAGENTA, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 9, 4, " ", TERMPAINT_COLOR_BRIGHT_CYAN, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 10, 4, " ", TERMPAINT_COLOR_WHITE, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); checkEmptyPlusSome(s, { {{ 3, 3 }, singleWideChar(" ").withFg("black")}, {{ 3, 4 }, singleWideChar(" ").withFg("bright black")}, {{ 4, 3 }, singleWideChar(" ").withFg("red")}, {{ 4, 4 }, singleWideChar(" ").withFg("bright red")}, {{ 5, 3 }, singleWideChar(" ").withFg("green")}, {{ 5, 4 }, singleWideChar(" ").withFg("bright green")}, {{ 6, 3 }, singleWideChar(" ").withFg("yellow")}, {{ 6, 4 }, singleWideChar(" ").withFg("bright yellow")}, {{ 7, 3 }, singleWideChar(" ").withFg("blue")}, {{ 7, 4 }, singleWideChar(" ").withFg("bright blue")}, {{ 8, 3 }, singleWideChar(" ").withFg("magenta")}, {{ 8, 4 }, singleWideChar(" ").withFg("bright magenta")}, {{ 9, 3 }, singleWideChar(" ").withFg("cyan")}, {{ 9, 4 }, singleWideChar(" ").withFg("bright cyan")}, {{ 10, 3 }, singleWideChar(" ").withFg("white")}, {{ 10, 4 }, singleWideChar(" ").withFg("bright white")}, }); } TEST_CASE("named bg colors") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 3, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BLACK); termpaint_surface_write_with_colors(t.surface, 4, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_RED); termpaint_surface_write_with_colors(t.surface, 5, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_GREEN); termpaint_surface_write_with_colors(t.surface, 6, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_YELLOW); termpaint_surface_write_with_colors(t.surface, 7, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BLUE); termpaint_surface_write_with_colors(t.surface, 8, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_MAGENTA); termpaint_surface_write_with_colors(t.surface, 9, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_CYAN); termpaint_surface_write_with_colors(t.surface, 10, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_LIGHT_GREY); termpaint_surface_write_with_colors(t.surface, 3, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_DARK_GREY); termpaint_surface_write_with_colors(t.surface, 4, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_RED); termpaint_surface_write_with_colors(t.surface, 5, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_GREEN); termpaint_surface_write_with_colors(t.surface, 6, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_YELLOW); termpaint_surface_write_with_colors(t.surface, 7, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_BLUE); termpaint_surface_write_with_colors(t.surface, 8, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_MAGENTA); termpaint_surface_write_with_colors(t.surface, 9, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_CYAN); termpaint_surface_write_with_colors(t.surface, 10, 4, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_WHITE); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); checkEmptyPlusSome(s, { {{ 3, 3 }, singleWideChar(" ").withBg("black")}, {{ 3, 4 }, singleWideChar(" ").withBg("bright black")}, {{ 4, 3 }, singleWideChar(" ").withBg("red")}, {{ 4, 4 }, singleWideChar(" ").withBg("bright red")}, {{ 5, 3 }, singleWideChar(" ").withBg("green")}, {{ 5, 4 }, singleWideChar(" ").withBg("bright green")}, {{ 6, 3 }, singleWideChar(" ").withBg("yellow")}, {{ 6, 4 }, singleWideChar(" ").withBg("bright yellow")}, {{ 7, 3 }, singleWideChar(" ").withBg("blue")}, {{ 7, 4 }, singleWideChar(" ").withBg("bright blue")}, {{ 8, 3 }, singleWideChar(" ").withBg("magenta")}, {{ 8, 4 }, singleWideChar(" ").withBg("bright magenta")}, {{ 9, 3 }, singleWideChar(" ").withBg("cyan")}, {{ 9, 4 }, singleWideChar(" ").withBg("bright cyan")}, {{ 10, 3 }, singleWideChar(" ").withBg("white")}, {{ 10, 4 }, singleWideChar(" ").withBg("bright white")}, }); } TEST_CASE("indexed colors") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 3, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 16); termpaint_surface_write_with_colors(t.surface, 4, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 51); termpaint_surface_write_with_colors(t.surface, 5, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 70); termpaint_surface_write_with_colors(t.surface, 6, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 110); termpaint_surface_write_with_colors(t.surface, 7, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 123); termpaint_surface_write_with_colors(t.surface, 8, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 213); termpaint_surface_write_with_colors(t.surface, 9, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 232); termpaint_surface_write_with_colors(t.surface, 10, 3, " ", TERMPAINT_DEFAULT_COLOR, TERMPAINT_INDEXED_COLOR + 255); termpaint_surface_write_with_colors(t.surface, 3, 4, " ", TERMPAINT_INDEXED_COLOR + 16, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 4, 4, " ", TERMPAINT_INDEXED_COLOR + 51, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 5, 4, " ", TERMPAINT_INDEXED_COLOR + 70, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 6, 4, " ", TERMPAINT_INDEXED_COLOR + 110, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 7, 4, " ", TERMPAINT_INDEXED_COLOR + 123, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 8, 4, " ", TERMPAINT_INDEXED_COLOR + 213, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 9, 4, " ", TERMPAINT_INDEXED_COLOR + 232, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 10, 4, " ", TERMPAINT_INDEXED_COLOR + 255, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); checkEmptyPlusSome(s, { {{ 3, 3 }, singleWideChar(" ").withBg("16")}, {{ 3, 4 }, singleWideChar(" ").withFg("16")}, {{ 4, 3 }, singleWideChar(" ").withBg("51")}, {{ 4, 4 }, singleWideChar(" ").withFg("51")}, {{ 5, 3 }, singleWideChar(" ").withBg("70")}, {{ 5, 4 }, singleWideChar(" ").withFg("70")}, {{ 6, 3 }, singleWideChar(" ").withBg("110")}, {{ 6, 4 }, singleWideChar(" ").withFg("110")}, {{ 7, 3 }, singleWideChar(" ").withBg("123")}, {{ 7, 4 }, singleWideChar(" ").withFg("123")}, {{ 8, 3 }, singleWideChar(" ").withBg("213")}, {{ 8, 4 }, singleWideChar(" ").withFg("213")}, {{ 9, 3 }, singleWideChar(" ").withBg("232")}, {{ 9, 4 }, singleWideChar(" ").withFg("232")}, {{ 10, 3 }, singleWideChar(" ").withBg("255")}, {{ 10, 4 }, singleWideChar(" ").withFg("255")}, }); } TEST_CASE("attributes") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); uattr_ptr attr; attr.reset(termpaint_attr_new(TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR)); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_BOLD); termpaint_surface_write_with_attr(t.surface, 3, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_ITALIC); termpaint_surface_write_with_attr(t.surface, 4, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_BLINK); termpaint_surface_write_with_attr(t.surface, 5, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_INVERSE); termpaint_surface_write_with_attr(t.surface, 6, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_STRIKE); termpaint_surface_write_with_attr(t.surface, 7, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE); termpaint_surface_write_with_attr(t.surface, 8, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE_DBL); termpaint_surface_write_with_attr(t.surface, 9, 3, "X", attr.get()); termpaint_attr_reset_style(attr.get()); termpaint_attr_set_style(attr.get(), TERMPAINT_STYLE_UNDERLINE_CURLY); termpaint_surface_write_with_attr(t.surface, 10, 3, "X", attr.get()); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); checkEmptyPlusSome(s, { {{ 3, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_BOLD)}, {{ 4, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_ITALIC)}, {{ 5, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_BLINK)}, {{ 6, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_INVERSE)}, {{ 7, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_STRIKE)}, {{ 8, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_UNDERLINE)}, {{ 9, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_UNDERLINE_DBL)}, {{ 10, 3 }, singleWideChar("X").withStyle(TERMPAINT_STYLE_UNDERLINE_CURLY)}, }); } TEST_CASE("cleared but colored") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_clear_rect(t.surface, 5, 2, 2, 2, TERMPAINT_COLOR_RED, TERMPAINT_COLOR_BLUE); termpaint_surface_clear_rect(t.surface, 8, 2, 2, 2, TERMPAINT_COLOR_CYAN, TERMPAINT_COLOR_WHITE); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); checkEmptyPlusSome(s, { {{ 5, 2 }, singleWideChar(" ").setErased().withFg("red").withBg("blue")}, {{ 6, 2 }, singleWideChar(" ").setErased().withFg("red").withBg("blue")}, {{ 5, 3 }, singleWideChar(" ").setErased().withFg("red").withBg("blue")}, {{ 6, 3 }, singleWideChar(" ").setErased().withFg("red").withBg("blue")}, {{ 8, 2 }, singleWideChar(" ").setErased().withFg("cyan").withBg("bright white")}, {{ 9, 2 }, singleWideChar(" ").setErased().withFg("cyan").withBg("bright white")}, {{ 8, 3 }, singleWideChar(" ").setErased().withFg("cyan").withBg("bright white")}, {{ 9, 3 }, singleWideChar(" ").setErased().withFg("cyan").withBg("bright white")}, }); } TEST_CASE("wrapped line") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); std::string str1 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor i"; termpaint_surface_write_with_colors(t.surface, 0, 4, str1.data(), TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_RED); std::string str2 = "ncididunt ut labore et dolore magna"; termpaint_surface_write_with_colors(t.surface, 0, 5, str2.data(), TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_RED); termpaint_surface_set_softwrap_marker(t.surface, 79, 4, true); termpaint_surface_set_softwrap_marker(t.surface, 0, 5, true); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); std::map, CapturedCell> expected; for (size_t i = 0; i < str1.size(); i++) { expected[{ i, 4 }] = singleWideChar(str1.substr(i, 1)).withBg("bright red"); } for (size_t i = 0; i < str2.size(); i++) { expected[{ i, 5 }] = singleWideChar(str2.substr(i, 1)).withBg("bright red"); } checkEmptyPlusSome(s, expected, Overrides().withSoftWrappedLines({4})); } TEST_CASE("wrapped line - missing initial marker") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); std::string str1 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor i"; termpaint_surface_write_with_colors(t.surface, 0, 4, str1.data(), TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_RED); std::string str2 = "ncididunt ut labore et dolore magna"; termpaint_surface_write_with_colors(t.surface, 0, 5, str2.data(), TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_RED); termpaint_surface_set_softwrap_marker(t.surface, 0, 5, true); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); std::map, CapturedCell> expected; for (size_t i = 0; i < str1.size(); i++) { expected[{ i, 4 }] = singleWideChar(str1.substr(i, 1)).withBg("bright red"); } for (size_t i = 0; i < str2.size(); i++) { expected[{ i, 5 }] = singleWideChar(str2.substr(i, 1)).withBg("bright red"); } checkEmptyPlusSome(s, expected, Overrides().withSoftWrappedLines({})); } TEST_CASE("wrapped line - missing continuation marker") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); std::string str1 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor i"; termpaint_surface_write_with_colors(t.surface, 0, 4, str1.data(), TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_RED); std::string str2 = "ncididunt ut labore et dolore magna"; termpaint_surface_write_with_colors(t.surface, 0, 5, str2.data(), TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_RED); termpaint_surface_set_softwrap_marker(t.surface, 79, 4, true); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); std::map, CapturedCell> expected; for (size_t i = 0; i < str1.size(); i++) { expected[{ i, 4 }] = singleWideChar(str1.substr(i, 1)).withBg("bright red"); } for (size_t i = 0; i < str2.size(); i++) { expected[{ i, 5 }] = singleWideChar(str2.substr(i, 1)).withBg("bright red"); } checkEmptyPlusSome(s, expected, Overrides().withSoftWrappedLines({})); } TEST_CASE("wrapped line - misplaced initial marker") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); std::string str1 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor i"; termpaint_surface_write_with_colors(t.surface, 0, 4, str1.data(), TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_RED); std::string str2 = "ncididunt ut labore et dolore magna"; termpaint_surface_write_with_colors(t.surface, 0, 5, str2.data(), TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_RED); termpaint_surface_set_softwrap_marker(t.surface, 78, 4, true); termpaint_surface_set_softwrap_marker(t.surface, 0, 5, true); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); std::map, CapturedCell> expected; for (size_t i = 0; i < str1.size(); i++) { expected[{ i, 4 }] = singleWideChar(str1.substr(i, 1)).withBg("bright red"); } for (size_t i = 0; i < str2.size(); i++) { expected[{ i, 5 }] = singleWideChar(str2.substr(i, 1)).withBg("bright red"); } checkEmptyPlusSome(s, expected, Overrides().withSoftWrappedLines({})); } TEST_CASE("wrapped line - misplaced continuation marker") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); std::string str1 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor i"; termpaint_surface_write_with_colors(t.surface, 0, 4, str1.data(), TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_RED); std::string str2 = "ncididunt ut labore et dolore magna"; termpaint_surface_write_with_colors(t.surface, 0, 5, str2.data(), TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_RED); termpaint_surface_set_softwrap_marker(t.surface, 79, 4, true); termpaint_surface_set_softwrap_marker(t.surface, 1, 5, true); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); std::map, CapturedCell> expected; for (size_t i = 0; i < str1.size(); i++) { expected[{ i, 4 }] = singleWideChar(str1.substr(i, 1)).withBg("bright red"); } for (size_t i = 0; i < str2.size(); i++) { expected[{ i, 5 }] = singleWideChar(str2.substr(i, 1)).withBg("bright red"); } checkEmptyPlusSome(s, expected, Overrides().withSoftWrappedLines({})); } TEST_CASE("wrapped line - wide wrapping character") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); std::string str1 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod temporあ"; termpaint_surface_write_with_colors(t.surface, 0, 4, str1.data(), TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_RED); std::string str2 = "ncididunt ut labore et dolore magna"; termpaint_surface_write_with_colors(t.surface, 0, 5, str2.data(), TERMPAINT_DEFAULT_COLOR, TERMPAINT_COLOR_BRIGHT_RED); termpaint_surface_set_softwrap_marker(t.surface, 78, 4, true); termpaint_surface_set_softwrap_marker(t.surface, 0, 5, true); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); std::map, CapturedCell> expected; for (size_t i = 0; i < str1.size() - 1; i++) { expected[{ i, 4 }] = singleWideChar(str1.substr(i, 1)).withBg("bright red"); } expected[{ 78, 4 }] = doubleWideChar("あ").withBg("bright red"); for (size_t i = 0; i < str2.size(); i++) { expected[{ i, 5 }] = singleWideChar(str2.substr(i, 1)).withBg("bright red"); } checkEmptyPlusSome(s, expected, Overrides().withSoftWrappedLines({})); } TEST_CASE("mouse mode: clicks") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_set_mouse_mode(t.terminal, TERMPAINT_MOUSE_MODE_CLICKS); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); checkEmptyPlusSome(s, {}); CHECK(s.mouseMode == "clicks"); termpaint_terminal_set_mouse_mode(t.terminal, TERMPAINT_MOUSE_MODE_OFF); s = capture(); CHECK(s.mouseMode == ""); } TEST_CASE("mouse mode: drag") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_set_mouse_mode(t.terminal, TERMPAINT_MOUSE_MODE_DRAG); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); checkEmptyPlusSome(s, {}); CHECK(s.mouseMode == "drag"); } TEST_CASE("mouse mode: movement") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_set_mouse_mode(t.terminal, TERMPAINT_MOUSE_MODE_MOVEMENT); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); checkEmptyPlusSome(s, {}); CHECK(s.mouseMode == "movement"); } TEST_CASE("cursor position default") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); CHECK(s.cursorX == 79); CHECK(s.cursorY == 23); } TEST_CASE("cursor position set") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_set_cursor_position(t.terminal, 14, 4); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); CHECK(s.cursorX == 14); CHECK(s.cursorY == 4); } TEST_CASE("cursor visibility default") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); CHECK(s.cursorVisible == true); } TEST_CASE("cursor show") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_set_cursor_position(t.terminal, 14, 4); termpaint_terminal_set_cursor_visible(t.terminal, true); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); CHECK(s.cursorVisible == true); } TEST_CASE("cursor hide") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_set_cursor_position(t.terminal, 14, 4); termpaint_terminal_set_cursor_visible(t.terminal, false); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); CHECK(s.cursorVisible == false); } TEST_CASE("cursor blink") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_set_cursor_position(t.terminal, 14, 4); termpaint_terminal_set_cursor_style(t.terminal, TERMPAINT_CURSOR_STYLE_TERM_DEFAULT, true); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); CHECK(s.cursorBlink == true); } TEST_CASE("cursor no blink") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_set_cursor_position(t.terminal, 14, 4); termpaint_terminal_set_cursor_style(t.terminal, TERMPAINT_CURSOR_STYLE_BLOCK, false); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); CHECK(s.cursorBlink == false); } TEST_CASE("cursor shape block") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_set_cursor_position(t.terminal, 14, 4); termpaint_terminal_set_cursor_style(t.terminal, TERMPAINT_CURSOR_STYLE_BLOCK, false); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); CHECK(s.cursorShape == "block"); } #include TEST_CASE("cursor shape bar") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_set_cursor_position(t.terminal, 14, 4); termpaint_terminal_set_cursor_style(t.terminal, TERMPAINT_CURSOR_STYLE_BAR, false); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); CHECK(s.cursorShape == "bar"); } TEST_CASE("cursor shape underline") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_set_cursor_position(t.terminal, 14, 4); termpaint_terminal_set_cursor_style(t.terminal, TERMPAINT_CURSOR_STYLE_UNDERLINE, false); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); CHECK(s.cursorShape == "underline"); } TEST_CASE("title") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_set_title(t.terminal, "fancy title", TERMPAINT_TITLE_MODE_PREFER_RESTORE); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); CHECK(s.title == "fancy title"); } TEST_CASE("title with chars that get substituted") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_set_title(t.terminal, "fancy\005\u00ad\u0087title", TERMPAINT_TITLE_MODE_PREFER_RESTORE); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); CHECK(s.title == "fancy - title"); } TEST_CASE("icon title") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_set_icon_title(t.terminal, "fancy title", TERMPAINT_TITLE_MODE_PREFER_RESTORE); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); CHECK(s.iconTitle == "fancy title"); } TEST_CASE("no alt screen") { SimpleFullscreen t { false }; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); CHECK(s.altScreen == false); } TEST_CASE("basic pause") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); checkEmptyPlusSome(s, {}); CHECK(s.altScreen == true); CHECK(s.invScreen == false); termpaint_terminal_pause(t.terminal); s = capture(); CHECK(s.altScreen == false); termpaint_terminal_unpause(t.terminal); termpaint_terminal_flush(t.terminal, false); s = capture(); checkEmptyPlusSome(s, {}); CHECK(s.altScreen == true); } TEST_CASE("pause with mouse") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_set_mouse_mode(t.terminal, TERMPAINT_MOUSE_MODE_MOVEMENT); termpaint_terminal_flush(t.terminal, false); CapturedState s = capture(); checkEmptyPlusSome(s, {}); CHECK(s.mouseMode == "movement"); termpaint_terminal_pause(t.terminal); s = capture(); CHECK(s.mouseMode == ""); termpaint_terminal_unpause(t.terminal); termpaint_terminal_flush(t.terminal, false); s = capture(); checkEmptyPlusSome(s, {}); CHECK(s.mouseMode == "movement"); } TEST_CASE("pause with persistent") { resetAndClear(); puts("user@host:~$ ls -1"); puts("Desktop"); puts("user@host:~$ app"); CapturedState s = capture(); CHECK(s.cursorX == 0); CHECK(s.cursorY == 3); SimpleFullscreen t{true, false}; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 10, 3, "Sample", TERMPAINT_COLOR_GREEN, TERMPAINT_COLOR_BLACK); termpaint_terminal_set_cursor_position(t.terminal, 6, 13); termpaint_terminal_flush(t.terminal, false); s = capture(); CHECK(s.cursorX == 6); CHECK(s.cursorY == 13); checkEmptyPlusSome(s, { {{ 10, 3 }, singleWideChar("S").withFg("green").withBg("black")}, {{ 11, 3 }, singleWideChar("a").withFg("green").withBg("black")}, {{ 12, 3 }, singleWideChar("m").withFg("green").withBg("black")}, {{ 13, 3 }, singleWideChar("p").withFg("green").withBg("black")}, {{ 14, 3 }, singleWideChar("l").withFg("green").withBg("black")}, {{ 15, 3 }, singleWideChar("e").withFg("green").withBg("black")}, }); CHECK(s.altScreen == true); CHECK(s.invScreen == false); unique_cptr persistent_surface; persistent_surface.reset(termpaint_terminal_new_surface(t.terminal, 40, 3)); termpaint_surface_write_with_colors(persistent_surface, 0, 0, "Some text here", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(persistent_surface, 5, 2, "Something else", TERMPAINT_COLOR_RED, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_pause_and_persistent(t.terminal, persistent_surface); s = capture(); auto base = SomeCells({ {{ 5, 5 }, singleWideChar("S").withFg("red")}, {{ 6, 5}, singleWideChar("o").withFg("red")}, {{ 7, 5}, singleWideChar("m").withFg("red")}, {{ 8, 5}, singleWideChar("e").withFg("red")}, {{ 9, 5}, singleWideChar("t").withFg("red")}, {{ 10, 5}, singleWideChar("h").withFg("red")}, {{ 11, 5}, singleWideChar("i").withFg("red")}, {{ 12, 5}, singleWideChar("n").withFg("red")}, {{ 13, 5}, singleWideChar("g").withFg("red")}, {{ 14, 5}, singleWideChar(" ").withFg("red")}, {{ 15, 5}, singleWideChar("e").withFg("red")}, {{ 16, 5}, singleWideChar("l").withFg("red")}, {{ 17, 5}, singleWideChar("s").withFg("red")}, {{ 18, 5}, singleWideChar("e").withFg("red")}, }).extend( lineOfText(0, "user@host:~$ ls -1"), lineOfText(1, "Desktop"), lineOfText(2, "user@host:~$ app"), lineOfText(3, "Some text here") ); checkEmptyPlusSome(s, base, Overrides().noAltScreen()); CHECK(s.cursorX == 0); CHECK(s.cursorY == 6); CHECK(s.altScreen == false); termpaint_terminal_unpause(t.terminal); termpaint_terminal_flush(t.terminal, false); s = capture(); CHECK(s.cursorX == 6); CHECK(s.cursorY == 13); checkEmptyPlusSome(s, { {{ 10, 3 }, singleWideChar("S").withFg("green").withBg("black")}, {{ 11, 3 }, singleWideChar("a").withFg("green").withBg("black")}, {{ 12, 3 }, singleWideChar("m").withFg("green").withBg("black")}, {{ 13, 3 }, singleWideChar("p").withFg("green").withBg("black")}, {{ 14, 3 }, singleWideChar("l").withFg("green").withBg("black")}, {{ 15, 3 }, singleWideChar("e").withFg("green").withBg("black")}, }); CHECK(s.altScreen == true); } TEST_CASE("pause inline with persistent") { resetAndClear(); puts("user@host:~$ ls -1"); puts("Desktop"); puts("user@host:~$ app"); CapturedState s = capture(); CHECK(s.altScreen == false); CHECK(s.cursorX == 0); CHECK(s.cursorY == 3); SomeCells base = SomeCells().extend( lineOfText(0, "user@host:~$ ls -1"), lineOfText(1, "Desktop"), lineOfText(2, "user@host:~$ app") ); SimpleInline t{3, &base}; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 10, 2, "Sample", TERMPAINT_COLOR_GREEN, TERMPAINT_COLOR_BLACK); termpaint_terminal_set_cursor_position(t.terminal, 6, 0); termpaint_terminal_flush(t.terminal, false); s = capture(); CHECK(s.cursorX == 6); CHECK(s.cursorY == 3); checkEmptyPlusSome(s, base.extend({ {{ 10, 5 }, singleWideChar("S").withFg("green").withBg("black")}, {{ 11, 5 }, singleWideChar("a").withFg("green").withBg("black")}, {{ 12, 5 }, singleWideChar("m").withFg("green").withBg("black")}, {{ 13, 5 }, singleWideChar("p").withFg("green").withBg("black")}, {{ 14, 5 }, singleWideChar("l").withFg("green").withBg("black")}, {{ 15, 5 }, singleWideChar("e").withFg("green").withBg("black")}, }), Overrides{}.noAltScreen()); CHECK(s.altScreen == false); CHECK(s.invScreen == false); unique_cptr persistent_surface; persistent_surface.reset(termpaint_terminal_new_surface(t.terminal, 40, 3)); termpaint_surface_write_with_colors(persistent_surface, 0, 0, "Some text here", TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(persistent_surface, 5, 2, "Something else", TERMPAINT_COLOR_RED, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_pause_and_persistent(t.terminal, persistent_surface); s = capture(); base = SomeCells({ {{ 5, 5 }, singleWideChar("S").withFg("red")}, {{ 6, 5}, singleWideChar("o").withFg("red")}, {{ 7, 5}, singleWideChar("m").withFg("red")}, {{ 8, 5}, singleWideChar("e").withFg("red")}, {{ 9, 5}, singleWideChar("t").withFg("red")}, {{ 10, 5}, singleWideChar("h").withFg("red")}, {{ 11, 5}, singleWideChar("i").withFg("red")}, {{ 12, 5}, singleWideChar("n").withFg("red")}, {{ 13, 5}, singleWideChar("g").withFg("red")}, {{ 14, 5}, singleWideChar(" ").withFg("red")}, {{ 15, 5}, singleWideChar("e").withFg("red")}, {{ 16, 5}, singleWideChar("l").withFg("red")}, {{ 17, 5}, singleWideChar("s").withFg("red")}, {{ 18, 5}, singleWideChar("e").withFg("red")}, }).extend( lineOfText(0, "user@host:~$ ls -1"), lineOfText(1, "Desktop"), lineOfText(2, "user@host:~$ app"), lineOfText(3, "Some text here") ); checkEmptyPlusSome(s, base, Overrides().noAltScreen()); CHECK(s.cursorX == 0); CHECK(s.cursorY == 6); CHECK(s.altScreen == false); termpaint_terminal_unpause(t.terminal); termpaint_terminal_flush(t.terminal, false); s = capture(); CHECK(s.cursorX == 6); CHECK(s.cursorY == 6); checkEmptyPlusSome(s, base.extend({ {{ 10, 3 + 5 }, singleWideChar("S").withFg("green").withBg("black")}, {{ 11, 3 + 5 }, singleWideChar("a").withFg("green").withBg("black")}, {{ 12, 3 + 5 }, singleWideChar("m").withFg("green").withBg("black")}, {{ 13, 3 + 5 }, singleWideChar("p").withFg("green").withBg("black")}, {{ 14, 3 + 5 }, singleWideChar("l").withFg("green").withBg("black")}, {{ 15, 3 + 5 }, singleWideChar("e").withFg("green").withBg("black")}, }), Overrides{}.noAltScreen()); CHECK(s.altScreen == false); } TEST_CASE("pause inline to fullscreen") { // Test that switching from inline to fullscreen followed by a pause and unpause works. // Switching between fullscreen and inline changes the needed sequences for pause and unpause. // This test also checks that cursor positions and screen contents are correct throughout the operation resetAndClear(); puts("user@host:~$ ls -1"); puts("Desktop"); puts("user@host:~$ app"); CapturedState s = capture(); CHECK(s.altScreen == false); CHECK(s.cursorX == 0); CHECK(s.cursorY == 3); SomeCells base = SomeCells().extend( lineOfText(0, "user@host:~$ ls -1"), lineOfText(1, "Desktop"), lineOfText(2, "user@host:~$ app") ); SimpleInline t{3, &base}; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 10, 2, "Sample", TERMPAINT_COLOR_GREEN, TERMPAINT_COLOR_BLACK); termpaint_terminal_set_cursor_position(t.terminal, 6, 1); termpaint_terminal_flush(t.terminal, false); s = capture(); CHECK(s.cursorX == 6); CHECK(s.cursorY == 4); checkEmptyPlusSome(s, base.extend({ {{ 10, 5 }, singleWideChar("S").withFg("green").withBg("black")}, {{ 11, 5 }, singleWideChar("a").withFg("green").withBg("black")}, {{ 12, 5 }, singleWideChar("m").withFg("green").withBg("black")}, {{ 13, 5 }, singleWideChar("p").withFg("green").withBg("black")}, {{ 14, 5 }, singleWideChar("l").withFg("green").withBg("black")}, {{ 15, 5 }, singleWideChar("e").withFg("green").withBg("black")}, }), Overrides{}.noAltScreen()); CHECK(s.altScreen == false); termpaint_terminal_set_inline(t.terminal, false); termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 10, 2, "Sample", TERMPAINT_COLOR_GREEN, TERMPAINT_COLOR_BLACK); termpaint_terminal_flush(t.terminal, false); s = capture(); CHECK(s.cursorX == 6); CHECK(s.cursorY == 1); checkEmptyPlusSome(s, { {{ 10, 2 }, singleWideChar("S").withFg("green").withBg("black")}, {{ 11, 2 }, singleWideChar("a").withFg("green").withBg("black")}, {{ 12, 2 }, singleWideChar("m").withFg("green").withBg("black")}, {{ 13, 2 }, singleWideChar("p").withFg("green").withBg("black")}, {{ 14, 2 }, singleWideChar("l").withFg("green").withBg("black")}, {{ 15, 2 }, singleWideChar("e").withFg("green").withBg("black")}, }); termpaint_terminal_pause(t.terminal); s = capture(); CHECK(s.altScreen == false); CHECK(s.cursorX == 0); CHECK(s.cursorY == 3); checkEmptyPlusSome(s, base, Overrides{}.noAltScreen()); termpaint_terminal_unpause(t.terminal); termpaint_terminal_flush(t.terminal, false); s = capture(); CHECK(s.cursorX == 6); CHECK(s.cursorY == 1); checkEmptyPlusSome(s, { {{ 10, 2 }, singleWideChar("S").withFg("green").withBg("black")}, {{ 11, 2 }, singleWideChar("a").withFg("green").withBg("black")}, {{ 12, 2 }, singleWideChar("m").withFg("green").withBg("black")}, {{ 13, 2 }, singleWideChar("p").withFg("green").withBg("black")}, {{ 14, 2 }, singleWideChar("l").withFg("green").withBg("black")}, {{ 15, 2 }, singleWideChar("e").withFg("green").withBg("black")}, }); t.terminal.reset(); s = capture(); checkEmptyPlusSome(s, base, Overrides().noAltScreen()); CHECK(s.cursorX == 0); CHECK(s.cursorY == 3); CHECK(s.altScreen == false); } TEST_CASE("pause fullscreen to inline") { // Test that switching from fullscreen to inline followed by a pause and unpause works. // Switching between fullscreen and inline changes the needed sequences for pause and unpause. // This test also checks that cursor positions and screen contents are correct throughout the operation resetAndClear(); puts("user@host:~$ ls -1"); puts("Desktop"); puts("user@host:~$ app"); CapturedState s = capture(); CHECK(s.altScreen == false); CHECK(s.cursorX == 0); CHECK(s.cursorY == 3); SomeCells base = SomeCells().extend( lineOfText(0, "user@host:~$ ls -1"), lineOfText(1, "Desktop"), lineOfText(2, "user@host:~$ app") ); SimpleFullscreen t{true, false}; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 10, 2, "Sample", TERMPAINT_COLOR_GREEN, TERMPAINT_COLOR_BLACK); termpaint_terminal_set_cursor_position(t.terminal, 6, 1); termpaint_terminal_flush(t.terminal, false); s = capture(); CHECK(s.cursorX == 6); CHECK(s.cursorY == 1); checkEmptyPlusSome(s, { {{ 10, 2 }, singleWideChar("S").withFg("green").withBg("black")}, {{ 11, 2 }, singleWideChar("a").withFg("green").withBg("black")}, {{ 12, 2 }, singleWideChar("m").withFg("green").withBg("black")}, {{ 13, 2 }, singleWideChar("p").withFg("green").withBg("black")}, {{ 14, 2 }, singleWideChar("l").withFg("green").withBg("black")}, {{ 15, 2 }, singleWideChar("e").withFg("green").withBg("black")}, }); CHECK(s.altScreen == true); termpaint_terminal_set_inline(t.terminal, true); termpaint_surface_resize(t.surface, 80, 3); termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_surface_write_with_colors(t.surface, 10, 2, "Sample", TERMPAINT_COLOR_GREEN, TERMPAINT_COLOR_BLACK); termpaint_terminal_flush(t.terminal, false); s = capture(); CHECK(s.cursorX == 6); CHECK(s.cursorY == 4); checkEmptyPlusSome(s, base.extend({ {{ 10, 5 }, singleWideChar("S").withFg("green").withBg("black")}, {{ 11, 5 }, singleWideChar("a").withFg("green").withBg("black")}, {{ 12, 5 }, singleWideChar("m").withFg("green").withBg("black")}, {{ 13, 5 }, singleWideChar("p").withFg("green").withBg("black")}, {{ 14, 5 }, singleWideChar("l").withFg("green").withBg("black")}, {{ 15, 5 }, singleWideChar("e").withFg("green").withBg("black")}, }), Overrides{}.noAltScreen()); termpaint_terminal_pause(t.terminal); s = capture(); CHECK(s.altScreen == false); CHECK(s.cursorX == 0); CHECK(s.cursorY == 3); checkEmptyPlusSome(s, base, Overrides{}.noAltScreen()); termpaint_terminal_unpause(t.terminal); termpaint_terminal_flush(t.terminal, false); s = capture(); CHECK(s.cursorX == 6); CHECK(s.cursorY == 4); checkEmptyPlusSome(s, base.extend({ {{ 10, 5 }, singleWideChar("S").withFg("green").withBg("black")}, {{ 11, 5 }, singleWideChar("a").withFg("green").withBg("black")}, {{ 12, 5 }, singleWideChar("m").withFg("green").withBg("black")}, {{ 13, 5 }, singleWideChar("p").withFg("green").withBg("black")}, {{ 14, 5 }, singleWideChar("l").withFg("green").withBg("black")}, {{ 15, 5 }, singleWideChar("e").withFg("green").withBg("black")}, }), Overrides{}.noAltScreen()); t.terminal.reset(); s = capture(); checkEmptyPlusSome(s, base, Overrides().noAltScreen()); CHECK(s.cursorX == 0); CHECK(s.cursorY == 3); CHECK(s.altScreen == false); } TEST_CASE("bell") { SimpleFullscreen t; termpaint_surface_clear(t.surface, TERMPAINT_DEFAULT_COLOR, TERMPAINT_DEFAULT_COLOR); termpaint_terminal_flush(t.terminal, false); termpaint_terminal_bell(t.terminal); CapturedState s = capture(); checkEmptyPlusSome(s, {}); CHECK(*asyncQueue.pop() == "*bell"); } termpaint-0.3.1/tests/terminaloutput.h000066400000000000000000000047341477303547200201510ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef TERMPAINT_TERMINALOUTPUT_INCLUDED #define TERMPAINT_TERMINALOUTPUT_INCLUDED #include #include #include #include extern int driverFd; void resetAndClear(); class Queue { public: void push(std::unique_ptr&& item) { std::unique_lock lock(mutex); queue.push(std::move(item)); lock.unlock(); cond.notify_one(); } std::unique_ptr pop() { std::unique_lock lock(mutex); while (queue.empty()) { if (cond.wait_for(lock, std::chrono::milliseconds(500)) == std::cv_status::timeout) { puts("Timeout while waiting for message. Aborting\n."); abort(); } } auto item = move(queue.front()); queue.pop(); return item; } void clear() { std::unique_lock lock(mutex); while (!queue.empty()) { queue.pop(); } } private: std::mutex mutex; std::condition_variable cond; std::queue> queue; }; extern Queue queue; extern Queue asyncQueue; class CapturedCell { public: int x; int y; std::string data; std::string fg, bg, deco; // empty is default (name for named colors, number for palette colors or #rrggbb int style = 0; int width = 1; bool erased = false; public: CapturedCell withFg(std::string val) { auto r = *this; r.fg = val; return r; } CapturedCell withBg(std::string val) { auto r = *this; r.bg = val; return r; } CapturedCell withDeco(std::string val) { auto r = *this; r.deco = val; return r; } CapturedCell withStyle(int val) { auto r = *this; r.style = val; return r; } CapturedCell setErased() { auto r = *this; r.erased = true; return r; } }; class CapturedRow { public: std::vector cells; bool softWrapped = false; }; class CapturedState { public: std::vector rows; int width = -1, height = -1; int cursorX = -1, cursorY = -1; bool cursorVisible = true; bool cursorBlink = true; std::string cursorShape; std::string mouseMode; bool altScreen = false; bool invScreen = false; std::string title, iconTitle; CapturedCell sgrState; }; CapturedState capture(); #endif termpaint-0.3.1/tests/terminaloutput_main.cpp000066400000000000000000000277361477303547200215170ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #define CATCH_CONFIG_RUNNER #define CATCH_CONFIG_NOSTDOUT #ifndef BUNDLED_CATCH2 #ifdef CATCH3 #include "catch2/catch_all.hpp" #else #include "catch2/catch.hpp" #endif #else #include "../third-party/catch.hpp" #endif #include #include #include #include #include #ifndef BUNDLED_PICOJSON #include "picojson.h" #else #include "../third-party/picojson.h" #endif #include #include "terminaloutput.h" int driverFd; Queue queue; Queue asyncQueue; void write_or_abort(int fd, const void *buf, size_t n) { if (write(fd, buf, n) != (ssize_t)n) { abort(); } } void driver_quit() { char msg[] = "set:auto-quit"; write_or_abort(driverFd, msg, sizeof(msg)); } namespace Catch { std::ostream& cout() { return std::clog; } std::ostream& cerr() { return std::clog; } std::ostream& clog() { return std::clog; } } std::atomic stopReader; void reader() { std::string item; while (true) { char buff[1000]; ssize_t ret = read(driverFd, buff, sizeof(buff)); if (stopReader) continue; // we are stopping, just discard input if (ret < 0) { perror("reading from socket"); std::terminate(); } for (int i = 0; i < ret; i++) { if (buff[i] == 0) { if (item.size() && item[0] == '*') { asyncQueue.push(std::make_unique(item)); } else { queue.push(std::make_unique(item)); } item.clear(); } else { item.push_back(buff[i]); } } } } void resetAndClear() { // altscreen should be disabled by reset. But somehow that doesn't seem to work. // So for now manually ensure altscreen is disabled. puts("\033[?1049l"); char msg[] = "reset"; write_or_abort(driverFd, msg, sizeof(msg)); queue.pop(); asyncQueue.clear(); } namespace { template bool has(const picojson::object& obj, const char* name) { return obj.count(name) && obj.at(name).is(); } template<> bool has(const picojson::object& obj, const char* name) { return obj.count(name) && obj.at(name).is(); } template T get(const picojson::object& obj, const char* name) { return obj.at(name).get(); } template<> int get(const picojson::object& obj, const char* name) { return static_cast(obj.at(name).get()); } } CapturedState capture() { CapturedState state; char msg[] = "capture:all"; write_or_abort(driverFd, msg, sizeof(msg)); std::string reply = move(*queue.pop()); picojson::value rootValue; std::string err; picojson::parse(rootValue, reply.begin(), reply.end(), &err); if (err.size()) { std::clog << "capture: " << err; std::terminate(); } if (!rootValue.is()) { std::clog << "capture: root is not an object"; std::terminate(); } picojson::object root = rootValue.get(); if (!has(root, "version") || !has(root, "width") || !has(root, "height") || !has(root, "cells")) { std::clog << "capture: root has wrong shape"; std::terminate(); } int width = static_cast(get(root, "width")); int height = static_cast(get(root, "height")); state.width = width; state.height = height; if (has(root, "cursor_column")) { state.cursorX = get(root, "cursor_column"); } if (has(root, "cursor_row")) { state.cursorY = get(root, "cursor_row"); } if (has(root, "cursor_visible")) { state.cursorVisible = get(root, "cursor_visible"); } if (has(root, "cursor_blink")) { state.cursorBlink = get(root, "cursor_blink"); } if (has(root, "cursor_shape")) { state.cursorShape = get(root, "cursor_shape"); } if (has(root, "mouse_mode")) { state.mouseMode = get(root, "mouse_mode"); } if (has(root, "alternate_screen")) { state.altScreen = get(root, "alternate_screen"); } if (has(root, "inverse_screen")) { state.invScreen = get(root, "inverse_screen"); } if (has(root, "title")) { state.title = get(root, "title"); } if (has(root, "icon_title")) { state.iconTitle = get(root, "icon_title"); } if (has(root, "errors")) { std::string errors = get(root, "errors"); std::clog << "capture: terminal reported capturing errors: " << errors; std::terminate(); } auto read_flag = [](const picojson::object& obj, const char* name, int flag) { if (has(obj, name) && get(obj, name)) { return flag; } return 0; }; if (has(root, "current_sgr_attr")) { picojson::object cell = get(root, "current_sgr_attr"); int style = 0; style |= read_flag(cell, "bold", TERMPAINT_STYLE_BOLD); style |= read_flag(cell, "italic", TERMPAINT_STYLE_ITALIC); style |= read_flag(cell, "blink", TERMPAINT_STYLE_BLINK); style |= read_flag(cell, "overline", TERMPAINT_STYLE_OVERLINE); style |= read_flag(cell, "inverse", TERMPAINT_STYLE_INVERSE); style |= read_flag(cell, "strike", TERMPAINT_STYLE_STRIKE); style |= read_flag(cell, "underline", TERMPAINT_STYLE_UNDERLINE); style |= read_flag(cell, "double_underline", TERMPAINT_STYLE_UNDERLINE_DBL); style |= read_flag(cell, "curly_underline", TERMPAINT_STYLE_UNDERLINE_CURLY); state.sgrState.style = style; if (has(cell, "fg")) { state.sgrState.fg = get(cell, "fg"); } if (has(cell, "bg")) { state.sgrState.bg = get(cell, "bg"); } if (has(cell, "deco")) { state.sgrState.deco = get(cell, "deco"); } } picojson::array cells = get(root, "cells"); for (const auto& cellValue: cells) { if (!cellValue.is()) { std::clog << "capture: cell is not an object"; std::terminate(); } picojson::object cell = cellValue.get(); if (!has(cell, "x") || !has(cell, "y") || !has(cell, "t")) { std::clog << "capture: cell is missing x, y or t"; std::terminate(); } CapturedCell ccell; if (has(cell, "fg")) { ccell.fg = get(cell, "fg"); } if (has(cell, "bg")) { ccell.bg = get(cell, "bg"); } if (has(cell, "deco")) { ccell.deco = get(cell, "deco"); } int style = 0; style |= read_flag(cell, "bold", TERMPAINT_STYLE_BOLD); style |= read_flag(cell, "italic", TERMPAINT_STYLE_ITALIC); style |= read_flag(cell, "blink", TERMPAINT_STYLE_BLINK); style |= read_flag(cell, "overline", TERMPAINT_STYLE_OVERLINE); style |= read_flag(cell, "inverse", TERMPAINT_STYLE_INVERSE); style |= read_flag(cell, "strike", TERMPAINT_STYLE_STRIKE); style |= read_flag(cell, "underline", TERMPAINT_STYLE_UNDERLINE); style |= read_flag(cell, "double_underline", TERMPAINT_STYLE_UNDERLINE_DBL); style |= read_flag(cell, "curly_underline", TERMPAINT_STYLE_UNDERLINE_CURLY); ccell.style = style; int x = static_cast(get(cell, "x")); int y = static_cast(get(cell, "y")); int width = 1; if (has(cell, "width")) { width = static_cast(get(cell, "width")); } ccell.width = width; std::string text = get(cell, "t"); ccell.erased = has(cell, "cleared") ? get(cell, "cleared") : false; if (ccell.erased && text != " ") { std::clog << "capture: is erased but content is not space"; std::terminate(); } ccell.x = x; ccell.y = y; ccell.data = text; if ((int)state.rows.size() <= y) { state.rows.resize(y + 1); } state.rows[y].cells.push_back(ccell); } if ((int)state.rows.size() != height) { return CapturedState{}; } if (has(root, "lines")) { picojson::object rows = get(root, "lines"); for (const auto& row: rows) { char *endp = const_cast(row.first.data() + row.first.length()); int y = strtol(row.first.data(), &endp, 10); if (endp != row.first.data() + row.first.length()) { std::clog << "capture: can not parse line key"; std::terminate(); } if (y < 0 || y >= state.height) { std::clog << "capture: line number out of range"; std::terminate(); } if (!row.second.is()) { std::clog << "capture: expected type object for line"; std::terminate(); } picojson::object lineObj = row.second.get(); if (has(lineObj, "soft_wrapped")) { state.rows[y].softWrapped = get(lineObj, "soft_wrapped"); } } } int y = 0; for (auto& row: state.rows) { std::sort(row.cells.begin(), row.cells.end(), [] (const CapturedCell &c1, const CapturedCell &c2) { return c1.x < c2.x; }); int x = 0; for (auto& cell: row.cells) { if (cell.x != x) { return CapturedState{}; } if (cell.y != y) { return CapturedState{}; } x += cell.width; } if (x != width) { return CapturedState{}; } ++y; } return state; } int main( int argc, char* argv[] ) { if (getenv("DRIVERFD")) { dup2(42, 2); } Catch::Session session; std::string testdriver; bool valgrind = false; #ifdef CATCH3 using namespace Catch::Clara; #else using namespace Catch::clara; #endif auto cli = session.cli() | Opt(testdriver, "testdriver")["--driver"].required() | Opt( valgrind )["--valgrind"]; session.cli( cli ); int returnCode = session.applyCommandLine( argc, argv ); if( returnCode != 0 ) { return returnCode; } if (getenv("DRIVERFD")) { driverFd = std::atoi(getenv("DRIVERFD")); std::thread thr{reader}; int retval = session.run(); stopReader.store(true); driver_quit(); thr.detach(); return retval; } else { auto args = std::vector{argv, argv + argc}; if (valgrind) { args.insert(begin(args), strdup("--log-file=testtermpaint_terminaloutput.valgrind.txt")); args.insert(begin(args), strdup("valgrind")); } args.insert(begin(args), strdup("--")); args.insert(begin(args), strdup("--control-via-fd0")); args.insert(begin(args), strdup("--propagate-exit-code")); args.insert(begin(args), strdup(testdriver.data())); args.push_back(nullptr); int fds[2]; socketpair(AF_LOCAL, SOCK_STREAM, 0, fds); // no cloexec, these will be inherited dup2(fds[0], 0); close(fds[0]); dup2(2, 42); auto fdString = std::to_string(fds[1]); setenv("DRIVERFD", fdString.data(), 1); execv(testdriver.data(), args.data()); return 1; } } termpaint-0.3.1/tests/testhelper.h000066400000000000000000000017711477303547200172320ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #ifndef TERMPAINT_TESTHELPER_INCLUDED #define TERMPAINT_TESTHELPER_INCLUDED #include #include #ifndef BUNDLED_CATCH2 #ifdef CATCH3 #include "catch2/catch_all.hpp" #else #include "catch2/catch.hpp" #endif #else #include "../third-party/catch.hpp" #endif template using cptr_DEL = void(T*); template del> struct cptr_Deleter{ void operator()(T* t) { del(t); } }; template del> struct unique_cptr : public std::unique_ptr> { operator T*() { return this->get(); } }; using terminal_uptr = unique_cptr; namespace Catch { template<> struct StringMaker> { static std::string convert(std::tuple const& value) { return "{" + std::to_string(std::get<0>(value)) + ", " + std::to_string(std::get<1>(value)) + "}"; } }; } #endif termpaint-0.3.1/tests/utf8_tests.cpp000066400000000000000000000145131477303547200175140ustar00rootroot00000000000000// SPDX-License-Identifier: BSL-1.0 #include #ifndef BUNDLED_CATCH2 #ifdef CATCH3 #include "catch2/catch_all.hpp" #else #include "catch2/catch.hpp" #endif #else #include "../third-party/catch.hpp" #endif typedef bool _Bool; #include template static const unsigned char* u8p(X); // intentionally undefined template <> const unsigned char* u8p(const char *str) { return (const unsigned char*)str; } TEST_CASE("misc invalid single byte utf8") { unsigned char ch; for (int i = 0x80; i < 0xc0; i++) { INFO(i); ch = i; REQUIRE(termpaintp_check_valid_sequence(&ch, 1) == false); } { INFO(0xfe); ch = 0xfe; REQUIRE(termpaintp_check_valid_sequence(&ch, 1) == false); } { INFO(0xff); ch = 0xff; REQUIRE(termpaintp_check_valid_sequence(&ch, 1) == false); } } TEST_CASE("short utf8 sequences") { } static inline int create_encoding_with_length(int codepoint, unsigned char *buf, int length) { #define STORE_AND_SHIFT(index) \ buf[index] = (codepoint & 0b00111111) | 0x80; \ codepoint = codepoint >> 6; if (length == 6) { //This is implied by the range of int: //REQUIRE(codepoint < (1u << (6*5+1))); STORE_AND_SHIFT(5) STORE_AND_SHIFT(4) STORE_AND_SHIFT(3) STORE_AND_SHIFT(2) STORE_AND_SHIFT(1) buf[0] = 0b11111100 | codepoint; return 6; } else if (length == 5) { REQUIRE(codepoint < (1 << (6*4+2))); STORE_AND_SHIFT(4) STORE_AND_SHIFT(3) STORE_AND_SHIFT(2) STORE_AND_SHIFT(1) buf[0] = 0b11111000 | codepoint; return 5; } else if (length == 4) { REQUIRE(codepoint < (1 << (6*3+3))); STORE_AND_SHIFT(3) STORE_AND_SHIFT(2) STORE_AND_SHIFT(1) buf[0] = 0b11110000 | codepoint; return 4; } else if (length == 3) { REQUIRE(codepoint < (1 << (6*2+4))); STORE_AND_SHIFT(2) STORE_AND_SHIFT(1) buf[0] = 0b11100000 | codepoint; return 3; } else if (length == 2) { REQUIRE(codepoint < (1 << (6*1+5))); STORE_AND_SHIFT(1) buf[0] = 0b11000000 | codepoint; return 2; } else { REQUIRE(codepoint < 0x80); REQUIRE(length == 1); buf[0] = codepoint; return 1; } #undef STORE_AND_SHIFT } TEST_CASE("Non shortest form") { SECTION ("2 byte /") { REQUIRE(termpaintp_check_valid_sequence(u8p("\xc1\x9c"), 2) == false); } SECTION ("2 byte A") { REQUIRE(termpaintp_check_valid_sequence(u8p("\xc1\x81"), 2) == false); } SECTION ("3 byte A") { REQUIRE(termpaintp_check_valid_sequence(u8p("\xe0\x81\x81"), 3) == false); } SECTION ("overlong nul") { unsigned char buffer[7]; for (int i = 2; i < 7; i++) { INFO(i); create_encoding_with_length(0, buffer, i); REQUIRE(termpaintp_check_valid_sequence(buffer, i) == false); } } SECTION ("overlong 0x7f") { unsigned char buffer[7]; for (int i = 2; i < 7; i++) { INFO(i); create_encoding_with_length(0x7f, buffer, i); REQUIRE(termpaintp_check_valid_sequence(buffer, i) == false); } } SECTION ("overlong 0x80") { unsigned char buffer[7]; for (int i = 3; i < 7; i++) { INFO(i); create_encoding_with_length(0x80, buffer, i); REQUIRE(termpaintp_check_valid_sequence(buffer, i) == false); } } SECTION ("overlong 0x7ff") { unsigned char buffer[7]; for (int i = 3; i < 7; i++) { INFO(i); create_encoding_with_length(0x7ff, buffer, i); REQUIRE(termpaintp_check_valid_sequence(buffer, i) == false); } } SECTION ("overlong 0x800") { unsigned char buffer[7]; for (int i = 4; i < 7; i++) { INFO(i); create_encoding_with_length(0x800, buffer, i); REQUIRE(termpaintp_check_valid_sequence(buffer, i) == false); } } SECTION ("overlong 0xffff") { unsigned char buffer[7]; for (int i = 4; i < 7; i++) { INFO(i); create_encoding_with_length(0xffff, buffer, i); REQUIRE(termpaintp_check_valid_sequence(buffer, i) == false); } } SECTION ("overlong 0x10000") { unsigned char buffer[7]; for (int i = 5; i < 7; i++) { INFO(i); create_encoding_with_length(0x10000, buffer, i); REQUIRE(termpaintp_check_valid_sequence(buffer, i) == false); } } SECTION ("overlong 0x1fffff") { unsigned char buffer[7]; for (int i = 5; i < 7; i++) { INFO(i); create_encoding_with_length(0x1fffff, buffer, i); REQUIRE(termpaintp_check_valid_sequence(buffer, i) == false); } } SECTION ("overlong 0x200000") { unsigned char buffer[7]; create_encoding_with_length(0x200000, buffer, 6); REQUIRE(termpaintp_check_valid_sequence(buffer, 6) == false); } SECTION ("overlong 0x3ffffff") { unsigned char buffer[7]; create_encoding_with_length(0x3ffffff, buffer, 6); REQUIRE(termpaintp_check_valid_sequence(buffer, 6) == false); } } static void codepoint_test(unsigned lower, unsigned upper) { unsigned char buffer[7]; for (unsigned codepoint = lower; codepoint <= upper; codepoint++) { INFO(codepoint); memset(buffer, 42, 7); int len = termpaintp_encode_to_utf8(codepoint, buffer); REQUIRE(buffer[len] == 42); REQUIRE(len == termpaintp_utf8_len(buffer[0])); INFO("buffer " << std::hex << (unsigned)buffer[0] << " " << (unsigned)buffer[1] << " " << (unsigned)buffer[2] << " " << (unsigned)buffer[3] << " " << (unsigned)buffer[4] << " " << (unsigned)buffer[5]); if (codepoint > 0xd7ff && codepoint < 0xe000) { REQUIRE(termpaintp_check_valid_sequence(buffer, len) == false); } else { REQUIRE(termpaintp_check_valid_sequence(buffer, len) == true); } } } TEST_CASE( "utf8 brute force unicode", "[utf8]" ) { codepoint_test(1, 0x10ffff); } TEST_CASE( "utf8 brute force", "[.utf8slow]" ) { codepoint_test(1, 0x7fffffff); } termpaint-0.3.1/third-party/000077500000000000000000000000001477303547200160015ustar00rootroot00000000000000termpaint-0.3.1/third-party/catch.hpp000066400000000000000000024047431477303547200176120ustar00rootroot00000000000000/* * Catch v2.13.7 * Generated: 2021-07-28 20:29:27.753164 * ---------------------------------------------------------- * This file has been merged from multiple headers. Please don't edit it directly * Copyright (c) 2021 Two Blue Cubes Ltd. All rights reserved. * * Distributed under the Boost Software License, Version 1.0. (See accompanying * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) */ #ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED #define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED // start catch.hpp #define CATCH_VERSION_MAJOR 2 #define CATCH_VERSION_MINOR 13 #define CATCH_VERSION_PATCH 7 #ifdef __clang__ # pragma clang system_header #elif defined __GNUC__ # pragma GCC system_header #endif // start catch_suppress_warnings.h #ifdef __clang__ # ifdef __ICC // icpc defines the __clang__ macro # pragma warning(push) # pragma warning(disable: 161 1682) # else // __ICC # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wpadded" # pragma clang diagnostic ignored "-Wswitch-enum" # pragma clang diagnostic ignored "-Wcovered-switch-default" # endif #elif defined __GNUC__ // Because REQUIREs trigger GCC's -Wparentheses, and because still // supported version of g++ have only buggy support for _Pragmas, // Wparentheses have to be suppressed globally. # pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wunused-variable" # pragma GCC diagnostic ignored "-Wpadded" #endif // end catch_suppress_warnings.h #if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) # define CATCH_IMPL # define CATCH_CONFIG_ALL_PARTS #endif // In the impl file, we want to have access to all parts of the headers // Can also be used to sanely support PCHs #if defined(CATCH_CONFIG_ALL_PARTS) # define CATCH_CONFIG_EXTERNAL_INTERFACES # if defined(CATCH_CONFIG_DISABLE_MATCHERS) # undef CATCH_CONFIG_DISABLE_MATCHERS # endif # if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) # define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER # endif #endif #if !defined(CATCH_CONFIG_IMPL_ONLY) // start catch_platform.h // See e.g.: // https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html #ifdef __APPLE__ # include # if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) # define CATCH_PLATFORM_MAC # elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) # define CATCH_PLATFORM_IPHONE # endif #elif defined(linux) || defined(__linux) || defined(__linux__) # define CATCH_PLATFORM_LINUX #elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) # define CATCH_PLATFORM_WINDOWS #endif // end catch_platform.h #ifdef CATCH_IMPL # ifndef CLARA_CONFIG_MAIN # define CLARA_CONFIG_MAIN_NOT_DEFINED # define CLARA_CONFIG_MAIN # endif #endif // start catch_user_interfaces.h namespace Catch { unsigned int rngSeed(); } // end catch_user_interfaces.h // start catch_tag_alias_autoregistrar.h // start catch_common.h // start catch_compiler_capabilities.h // Detect a number of compiler features - by compiler // The following features are defined: // // CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? // CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? // CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? // CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? // **************** // Note to maintainers: if new toggles are added please document them // in configuration.md, too // **************** // In general each macro has a _NO_ form // (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. // Many features, at point of detection, define an _INTERNAL_ macro, so they // can be combined, en-mass, with the _NO_ forms later. #ifdef __cplusplus # if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) # define CATCH_CPP14_OR_GREATER # endif # if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) # define CATCH_CPP17_OR_GREATER # endif #endif // Only GCC compiler should be used in this block, so other compilers trying to // mask themselves as GCC should be ignored. #if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) # define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) #endif #if defined(__clang__) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) // As of this writing, IBM XL's implementation of __builtin_constant_p has a bug // which results in calls to destructors being emitted for each temporary, // without a matching initialization. In practice, this can result in something // like `std::string::~string` being called on an uninitialized value. // // For example, this code will likely segfault under IBM XL: // ``` // REQUIRE(std::string("12") + "34" == "1234") // ``` // // Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. # if !defined(__ibmxl__) && !defined(__CUDACC__) # define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ # endif # define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) # define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) # define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) # define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) #endif // __clang__ //////////////////////////////////////////////////////////////////////////////// // Assume that non-Windows platforms support posix signals by default #if !defined(CATCH_PLATFORM_WINDOWS) #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS #endif //////////////////////////////////////////////////////////////////////////////// // We know some environments not to support full POSIX signals #if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS #endif #ifdef __OS400__ # define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS # define CATCH_CONFIG_COLOUR_NONE #endif //////////////////////////////////////////////////////////////////////////////// // Android somehow still does not support std::to_string #if defined(__ANDROID__) # define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING # define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE #endif //////////////////////////////////////////////////////////////////////////////// // Not all Windows environments support SEH properly #if defined(__MINGW32__) # define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH #endif //////////////////////////////////////////////////////////////////////////////// // PS4 #if defined(__ORBIS__) # define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE #endif //////////////////////////////////////////////////////////////////////////////// // Cygwin #ifdef __CYGWIN__ // Required for some versions of Cygwin to declare gettimeofday // see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin # define _BSD_SOURCE // some versions of cygwin (most) do not support std::to_string. Use the libstd check. // https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 # if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) # define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING # endif #endif // __CYGWIN__ //////////////////////////////////////////////////////////////////////////////// // Visual C++ #if defined(_MSC_VER) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) // Universal Windows platform does not support SEH // Or console colours (or console at all...) # if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) # define CATCH_CONFIG_COLOUR_NONE # else # define CATCH_INTERNAL_CONFIG_WINDOWS_SEH # endif // MSVC traditional preprocessor needs some workaround for __VA_ARGS__ // _MSVC_TRADITIONAL == 0 means new conformant preprocessor // _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor # if !defined(__clang__) // Handle Clang masquerading for msvc # if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) # define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR # endif // MSVC_TRADITIONAL # endif // __clang__ #endif // _MSC_VER #if defined(_REENTRANT) || defined(_MSC_VER) // Enable async processing, as -pthread is specified or no additional linking is required # define CATCH_INTERNAL_CONFIG_USE_ASYNC #endif // _MSC_VER //////////////////////////////////////////////////////////////////////////////// // Check if we are compiled with -fno-exceptions or equivalent #if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) # define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED #endif //////////////////////////////////////////////////////////////////////////////// // DJGPP #ifdef __DJGPP__ # define CATCH_INTERNAL_CONFIG_NO_WCHAR #endif // __DJGPP__ //////////////////////////////////////////////////////////////////////////////// // Embarcadero C++Build #if defined(__BORLANDC__) #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN #endif //////////////////////////////////////////////////////////////////////////////// // Use of __COUNTER__ is suppressed during code analysis in // CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly // handled by it. // Otherwise all supported compilers support COUNTER macro, // but user still might want to turn it off #if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) #define CATCH_INTERNAL_CONFIG_COUNTER #endif //////////////////////////////////////////////////////////////////////////////// // RTX is a special version of Windows that is real time. // This means that it is detected as Windows, but does not provide // the same set of capabilities as real Windows does. #if defined(UNDER_RTSS) || defined(RTX64_BUILD) #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH #define CATCH_INTERNAL_CONFIG_NO_ASYNC #define CATCH_CONFIG_COLOUR_NONE #endif #if !defined(_GLIBCXX_USE_C99_MATH_TR1) #define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER #endif // Various stdlib support checks that require __has_include #if defined(__has_include) // Check if string_view is available and usable #if __has_include() && defined(CATCH_CPP17_OR_GREATER) # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW #endif // Check if optional is available and usable # if __has_include() && defined(CATCH_CPP17_OR_GREATER) # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) // Check if byte is available and usable # if __has_include() && defined(CATCH_CPP17_OR_GREATER) # include # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) # define CATCH_INTERNAL_CONFIG_CPP17_BYTE # endif # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) // Check if variant is available and usable # if __has_include() && defined(CATCH_CPP17_OR_GREATER) # if defined(__clang__) && (__clang_major__ < 8) // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 // fix should be in clang 8, workaround in libstdc++ 8.2 # include # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) # define CATCH_CONFIG_NO_CPP17_VARIANT # else # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) # else # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT # endif // defined(__clang__) && (__clang_major__ < 8) # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) #endif // defined(__has_include) #if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) # define CATCH_CONFIG_COUNTER #endif #if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) # define CATCH_CONFIG_WINDOWS_SEH #endif // This is set by default, because we assume that unix compilers are posix-signal-compatible by default. #if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) # define CATCH_CONFIG_POSIX_SIGNALS #endif // This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. #if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) # define CATCH_CONFIG_WCHAR #endif #if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) # define CATCH_CONFIG_CPP11_TO_STRING #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) # define CATCH_CONFIG_CPP17_OPTIONAL #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) # define CATCH_CONFIG_CPP17_STRING_VIEW #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) # define CATCH_CONFIG_CPP17_VARIANT #endif #if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) # define CATCH_CONFIG_CPP17_BYTE #endif #if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) # define CATCH_INTERNAL_CONFIG_NEW_CAPTURE #endif #if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) # define CATCH_CONFIG_NEW_CAPTURE #endif #if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) # define CATCH_CONFIG_DISABLE_EXCEPTIONS #endif #if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) # define CATCH_CONFIG_POLYFILL_ISNAN #endif #if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) # define CATCH_CONFIG_USE_ASYNC #endif #if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) # define CATCH_CONFIG_ANDROID_LOGWRITE #endif #if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) # define CATCH_CONFIG_GLOBAL_NEXTAFTER #endif // Even if we do not think the compiler has that warning, we still have // to provide a macro that can be used by the code. #if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) # define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION #endif #if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) # define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION #endif #if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS #endif // The goal of this macro is to avoid evaluation of the arguments, but // still have the compiler warn on problems inside... #if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) # define CATCH_INTERNAL_IGNORE_BUT_WARN(...) #endif #if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) # undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #elif defined(__clang__) && (__clang_major__ < 5) # undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #endif #if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) # define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS #endif #if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) #define CATCH_TRY if ((true)) #define CATCH_CATCH_ALL if ((false)) #define CATCH_CATCH_ANON(type) if ((false)) #else #define CATCH_TRY try #define CATCH_CATCH_ALL catch (...) #define CATCH_CATCH_ANON(type) catch (type) #endif #if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) #define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #endif // end catch_compiler_capabilities.h #define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line #define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) #ifdef CATCH_CONFIG_COUNTER # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) #else # define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) #endif #include #include #include // We need a dummy global operator<< so we can bring it into Catch namespace later struct Catch_global_namespace_dummy {}; std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); namespace Catch { struct CaseSensitive { enum Choice { Yes, No }; }; class NonCopyable { NonCopyable( NonCopyable const& ) = delete; NonCopyable( NonCopyable && ) = delete; NonCopyable& operator = ( NonCopyable const& ) = delete; NonCopyable& operator = ( NonCopyable && ) = delete; protected: NonCopyable(); virtual ~NonCopyable(); }; struct SourceLineInfo { SourceLineInfo() = delete; SourceLineInfo( char const* _file, std::size_t _line ) noexcept : file( _file ), line( _line ) {} SourceLineInfo( SourceLineInfo const& other ) = default; SourceLineInfo& operator = ( SourceLineInfo const& ) = default; SourceLineInfo( SourceLineInfo&& ) noexcept = default; SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; bool empty() const noexcept { return file[0] == '\0'; } bool operator == ( SourceLineInfo const& other ) const noexcept; bool operator < ( SourceLineInfo const& other ) const noexcept; char const* file; std::size_t line; }; std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); // Bring in operator<< from global namespace into Catch namespace // This is necessary because the overload of operator<< above makes // lookup stop at namespace Catch using ::operator<<; // Use this in variadic streaming macros to allow // >> +StreamEndStop // as well as // >> stuff +StreamEndStop struct StreamEndStop { std::string operator+() const; }; template T const& operator + ( T const& value, StreamEndStop ) { return value; } } #define CATCH_INTERNAL_LINEINFO \ ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) // end catch_common.h namespace Catch { struct RegistrarForTagAliases { RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); }; } // end namespace Catch #define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION // end catch_tag_alias_autoregistrar.h // start catch_test_registry.h // start catch_interfaces_testcase.h #include namespace Catch { class TestSpec; struct ITestInvoker { virtual void invoke () const = 0; virtual ~ITestInvoker(); }; class TestCase; struct IConfig; struct ITestCaseRegistry { virtual ~ITestCaseRegistry(); virtual std::vector const& getAllTests() const = 0; virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; }; bool isThrowSafe( TestCase const& testCase, IConfig const& config ); bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); std::vector const& getAllTestCasesSorted( IConfig const& config ); } // end catch_interfaces_testcase.h // start catch_stringref.h #include #include #include #include namespace Catch { /// A non-owning string class (similar to the forthcoming std::string_view) /// Note that, because a StringRef may be a substring of another string, /// it may not be null terminated. class StringRef { public: using size_type = std::size_t; using const_iterator = const char*; private: static constexpr char const* const s_empty = ""; char const* m_start = s_empty; size_type m_size = 0; public: // construction constexpr StringRef() noexcept = default; StringRef( char const* rawChars ) noexcept; constexpr StringRef( char const* rawChars, size_type size ) noexcept : m_start( rawChars ), m_size( size ) {} StringRef( std::string const& stdString ) noexcept : m_start( stdString.c_str() ), m_size( stdString.size() ) {} explicit operator std::string() const { return std::string(m_start, m_size); } public: // operators auto operator == ( StringRef const& other ) const noexcept -> bool; auto operator != (StringRef const& other) const noexcept -> bool { return !(*this == other); } auto operator[] ( size_type index ) const noexcept -> char { assert(index < m_size); return m_start[index]; } public: // named queries constexpr auto empty() const noexcept -> bool { return m_size == 0; } constexpr auto size() const noexcept -> size_type { return m_size; } // Returns the current start pointer. If the StringRef is not // null-terminated, throws std::domain_exception auto c_str() const -> char const*; public: // substrings and searches // Returns a substring of [start, start + length). // If start + length > size(), then the substring is [start, size()). // If start > size(), then the substring is empty. auto substr( size_type start, size_type length ) const noexcept -> StringRef; // Returns the current start pointer. May not be null-terminated. auto data() const noexcept -> char const*; constexpr auto isNullTerminated() const noexcept -> bool { return m_start[m_size] == '\0'; } public: // iterators constexpr const_iterator begin() const { return m_start; } constexpr const_iterator end() const { return m_start + m_size; } }; auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { return StringRef( rawChars, size ); } } // namespace Catch constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { return Catch::StringRef( rawChars, size ); } // end catch_stringref.h // start catch_preprocessor.hpp #define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ #define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) #define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) #ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ // MSVC needs more evaluations #define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) #define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) #else #define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) #endif #define CATCH_REC_END(...) #define CATCH_REC_OUT #define CATCH_EMPTY() #define CATCH_DEFER(id) id CATCH_EMPTY() #define CATCH_REC_GET_END2() 0, CATCH_REC_END #define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 #define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 #define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT #define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) #define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) #define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) #define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) #define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) #define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) #define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) #define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) // Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, // and passes userdata as the first parameter to each invocation, // e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) #define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) #define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) #define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) #define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ #define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ #define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF #define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ #define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) #else // MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF #define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) #define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ #define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) #endif #define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ #define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) #define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) #ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR #define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) #else #define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) #define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) #endif #define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) #define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) #define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) #define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) #define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) #define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) #define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) #define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) #define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) #define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) #define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) #define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) #define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N #define INTERNAL_CATCH_TYPE_GEN\ template struct TypeList {};\ template\ constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ template class...> struct TemplateTypeList{};\ template class...Cs>\ constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ template\ struct append;\ template\ struct rewrap;\ template class, typename...>\ struct create;\ template class, typename>\ struct convert;\ \ template \ struct append { using type = T; };\ template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ template< template class L1, typename...E1, typename...Rest>\ struct append, TypeList, Rest...> { using type = L1; };\ \ template< template class Container, template class List, typename...elems>\ struct rewrap, List> { using type = TypeList>; };\ template< template class Container, template class List, class...Elems, typename...Elements>\ struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ \ template