From 6e3f3c950d4964c258c6d140b902d2f38b148b22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Wed, 23 Sep 2020 16:58:30 +0200 Subject: [PATCH] Convert from Xlib xcb This will make it easier to convert this project to Go/xgb later, even though the SYNC extension isn't currently supported there. So far unresolved: error handling. --- CMakeLists.txt | 5 +- compound-text.c | 633 +++++++++++++++++++++++++++++++++++++ compound-text.h | 21 ++ meson.build | 7 +- wdmtg.c | 395 ++++++++++++++--------- xext.vapi => xcb-sync.vapi | 0 xextproto.vapi | 1 - 7 files changed, 913 insertions(+), 149 deletions(-) create mode 100644 compound-text.c create mode 100644 compound-text.h rename xext.vapi => xcb-sync.vapi (100%) delete mode 100644 xextproto.vapi diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f6d3e4..8f2443e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,7 @@ endif (OPTION_NOINSTALL) set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) find_package (Vala 0.12 REQUIRED) find_package (PkgConfig REQUIRED) -pkg_check_modules (dependencies REQUIRED gtk+-3.0 sqlite3 x11 xext xextproto) +pkg_check_modules (dependencies REQUIRED gtk+-3.0 sqlite3 xcb xcb-sync) # Precompile Vala sources include (ValaPrecompile) @@ -57,7 +57,8 @@ set (project_SOURCES ${project_VALA_SOURCES} ${project_VALA_C} ${symbols_path}) # Build the executable and install it include_directories (${dependencies_INCLUDE_DIRS}) link_directories (${dependencies_LIBRARY_DIRS}) -add_executable (${PROJECT_NAME} ${PROJECT_NAME}.c ${project_SOURCES}) +add_executable (${PROJECT_NAME} + ${PROJECT_NAME}.c compound-text.c ${project_SOURCES}) target_link_libraries (${PROJECT_NAME} ${dependencies_LIBRARIES}) install (TARGETS ${PROJECT_NAME} DESTINATION bin) diff --git a/compound-text.c b/compound-text.c new file mode 100644 index 0000000..f3e9837 --- /dev/null +++ b/compound-text.c @@ -0,0 +1,633 @@ +// +// compound-text.c: partial X11 COMPOUND_TEXT to UCS-4 transcoder +// +// Copyright (c) 2020, Přemysl Eric Janouch +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// + +#include +#include // malloc, free, NULL, size_t +#include // g_utf8_*, g_ucs4_to_utf8 + +// None of the full Chinese, Japanese, Korean character sets are supported, +// and will be replaced by a lot of funny question marks +enum compound_text_encoding { + COMPOUND_TEXT_ASCII, + COMPOUND_TEXT_ISO8859_1_GR, + COMPOUND_TEXT_ISO8859_2_GR, + COMPOUND_TEXT_ISO8859_3_GR, + COMPOUND_TEXT_ISO8859_4_GR, + COMPOUND_TEXT_ISO8859_5_GR, + COMPOUND_TEXT_ISO8859_6_GR, + COMPOUND_TEXT_ISO8859_7_GR, + COMPOUND_TEXT_ISO8859_8_GR, + COMPOUND_TEXT_ISO8859_9_GR, + COMPOUND_TEXT_ISO8859_10_GR, + COMPOUND_TEXT_ISO8859_13_GR, + COMPOUND_TEXT_ISO8859_14_GR, + COMPOUND_TEXT_ISO8859_15_GR, + COMPOUND_TEXT_ISO8859_16_GR, + COMPOUND_TEXT_JIS_X0201_GR, + COMPOUND_TEXT_JIS_X0201_GL, + COMPOUND_TEXT_COUNT +}; + +/* + Generated from glibc charset data, based on the following script: + + for enc in ISO-8859-{1,2,3,4,5,6,7,8,9,10,13,14,15,16} JIS_X0201; do + echo "[COMPOUND_TEXT_$enc] =" + zcat /usr/share/i18n/charmaps/$enc.gz | \ + perl -nle '$x{hex($2)} = "0x$1" if m|^ +/x(..) +.*|; + sub tbl { join ", ", map { $x{$_} || "0x0000" } @_ } + END { print tbl(0x20..0x7F); print tbl(0xa0..0xFF); }' + done | fmt -sw70 | sed 's|^0|\t&|; s|^|\t|' +*/ +static unsigned short compound_text_tables[COMPOUND_TEXT_COUNT][96] = { + [COMPOUND_TEXT_ASCII] = { + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x007F + }, + [COMPOUND_TEXT_ISO8859_1_GR] = { + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, + 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, + 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, + 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, + 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF + }, + [COMPOUND_TEXT_ISO8859_2_GR] = { + 0x00A0, 0x0104, 0x02D8, 0x0141, 0x00A4, 0x013D, 0x015A, 0x00A7, + 0x00A8, 0x0160, 0x015E, 0x0164, 0x0179, 0x00AD, 0x017D, 0x017B, + 0x00B0, 0x0105, 0x02DB, 0x0142, 0x00B4, 0x013E, 0x015B, 0x02C7, + 0x00B8, 0x0161, 0x015F, 0x0165, 0x017A, 0x02DD, 0x017E, 0x017C, + 0x0154, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0139, 0x0106, 0x00C7, + 0x010C, 0x00C9, 0x0118, 0x00CB, 0x011A, 0x00CD, 0x00CE, 0x010E, + 0x0110, 0x0143, 0x0147, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x00D7, + 0x0158, 0x016E, 0x00DA, 0x0170, 0x00DC, 0x00DD, 0x0162, 0x00DF, + 0x0155, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x013A, 0x0107, 0x00E7, + 0x010D, 0x00E9, 0x0119, 0x00EB, 0x011B, 0x00ED, 0x00EE, 0x010F, + 0x0111, 0x0144, 0x0148, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x00F7, + 0x0159, 0x016F, 0x00FA, 0x0171, 0x00FC, 0x00FD, 0x0163, 0x02D9 + }, + [COMPOUND_TEXT_ISO8859_3_GR] = { + 0x00A0, 0x0126, 0x02D8, 0x00A3, 0x00A4, 0x0000, 0x0124, 0x00A7, + 0x00A8, 0x0130, 0x015E, 0x011E, 0x0134, 0x00AD, 0x0000, 0x017B, + 0x00B0, 0x0127, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x0125, 0x00B7, + 0x00B8, 0x0131, 0x015F, 0x011F, 0x0135, 0x00BD, 0x0000, 0x017C, + 0x00C0, 0x00C1, 0x00C2, 0x0000, 0x00C4, 0x010A, 0x0108, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x0000, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x0120, 0x00D6, 0x00D7, + 0x011C, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x016C, 0x015C, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x0000, 0x00E4, 0x010B, 0x0109, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x0000, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x0121, 0x00F6, 0x00F7, + 0x011D, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x016D, 0x015D, 0x02D9 + }, + [COMPOUND_TEXT_ISO8859_4_GR] = { + 0x00A0, 0x0104, 0x0138, 0x0156, 0x00A4, 0x0128, 0x013B, 0x00A7, + 0x00A8, 0x0160, 0x0112, 0x0122, 0x0166, 0x00AD, 0x017D, 0x00AF, + 0x00B0, 0x0105, 0x02DB, 0x0157, 0x00B4, 0x0129, 0x013C, 0x02C7, + 0x00B8, 0x0161, 0x0113, 0x0123, 0x0167, 0x014A, 0x017E, 0x014B, + 0x0100, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x012E, + 0x010C, 0x00C9, 0x0118, 0x00CB, 0x0116, 0x00CD, 0x00CE, 0x012A, + 0x0110, 0x0145, 0x014C, 0x0136, 0x00D4, 0x00D5, 0x00D6, 0x00D7, + 0x00D8, 0x0172, 0x00DA, 0x00DB, 0x00DC, 0x0168, 0x016A, 0x00DF, + 0x0101, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x012F, + 0x010D, 0x00E9, 0x0119, 0x00EB, 0x0117, 0x00ED, 0x00EE, 0x012B, + 0x0111, 0x0146, 0x014D, 0x0137, 0x00F4, 0x00F5, 0x00F6, 0x00F7, + 0x00F8, 0x0173, 0x00FA, 0x00FB, 0x00FC, 0x0169, 0x016B, 0x02D9 + }, + [COMPOUND_TEXT_ISO8859_5_GR] = { + 0x00A0, 0x0401, 0x0402, 0x0403, 0x0404, 0x0405, 0x0406, 0x0407, + 0x0408, 0x0409, 0x040A, 0x040B, 0x040C, 0x00AD, 0x040E, 0x040F, + 0x0410, 0x0411, 0x0412, 0x0413, 0x0414, 0x0415, 0x0416, 0x0417, + 0x0418, 0x0419, 0x041A, 0x041B, 0x041C, 0x041D, 0x041E, 0x041F, + 0x0420, 0x0421, 0x0422, 0x0423, 0x0424, 0x0425, 0x0426, 0x0427, + 0x0428, 0x0429, 0x042A, 0x042B, 0x042C, 0x042D, 0x042E, 0x042F, + 0x0430, 0x0431, 0x0432, 0x0433, 0x0434, 0x0435, 0x0436, 0x0437, + 0x0438, 0x0439, 0x043A, 0x043B, 0x043C, 0x043D, 0x043E, 0x043F, + 0x0440, 0x0441, 0x0442, 0x0443, 0x0444, 0x0445, 0x0446, 0x0447, + 0x0448, 0x0449, 0x044A, 0x044B, 0x044C, 0x044D, 0x044E, 0x044F, + 0x2116, 0x0451, 0x0452, 0x0453, 0x0454, 0x0455, 0x0456, 0x0457, + 0x0458, 0x0459, 0x045A, 0x045B, 0x045C, 0x00A7, 0x045E, 0x045F + }, + [COMPOUND_TEXT_ISO8859_6_GR] = { + 0x00A0, 0x0000, 0x0000, 0x0000, 0x00A4, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x060C, 0x00AD, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x061B, 0x0000, 0x0000, 0x0000, 0x061F, + 0x0000, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, + 0x0628, 0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F, + 0x0630, 0x0631, 0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637, + 0x0638, 0x0639, 0x063A, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0640, 0x0641, 0x0642, 0x0643, 0x0644, 0x0645, 0x0646, 0x0647, + 0x0648, 0x0649, 0x064A, 0x064B, 0x064C, 0x064D, 0x064E, 0x064F, + 0x0650, 0x0651, 0x0652, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 + }, + [COMPOUND_TEXT_ISO8859_7_GR] = { + 0x00A0, 0x2018, 0x2019, 0x00A3, 0x20AC, 0x20AF, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0x037A, 0x00AB, 0x00AC, 0x00AD, 0x0000, 0x2015, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x0384, 0x0385, 0x0386, 0x00B7, + 0x0388, 0x0389, 0x038A, 0x00BB, 0x038C, 0x00BD, 0x038E, 0x038F, + 0x0390, 0x0391, 0x0392, 0x0393, 0x0394, 0x0395, 0x0396, 0x0397, + 0x0398, 0x0399, 0x039A, 0x039B, 0x039C, 0x039D, 0x039E, 0x039F, + 0x03A0, 0x03A1, 0x0000, 0x03A3, 0x03A4, 0x03A5, 0x03A6, 0x03A7, + 0x03A8, 0x03A9, 0x03AA, 0x03AB, 0x03AC, 0x03AD, 0x03AE, 0x03AF, + 0x03B0, 0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, + 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BC, 0x03BD, 0x03BE, 0x03BF, + 0x03C0, 0x03C1, 0x03C2, 0x03C3, 0x03C4, 0x03C5, 0x03C6, 0x03C7, + 0x03C8, 0x03C9, 0x03CA, 0x03CB, 0x03CC, 0x03CD, 0x03CE, 0x0000 + }, + [COMPOUND_TEXT_ISO8859_8_GR] = { + 0x00A0, 0x0000, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0x00D7, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, + 0x00B8, 0x00B9, 0x00F7, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x2017, + 0x05D0, 0x05D1, 0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, + 0x05D8, 0x05D9, 0x05DA, 0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF, + 0x05E0, 0x05E1, 0x05E2, 0x05E3, 0x05E4, 0x05E5, 0x05E6, 0x05E7, + 0x05E8, 0x05E9, 0x05EA, 0x0000, 0x0000, 0x200E, 0x200F, 0x0000 + }, + [COMPOUND_TEXT_ISO8859_9_GR] = { + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, + 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, + 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, + 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x011E, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, + 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x0130, 0x015E, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x011F, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, + 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x0131, 0x015F, 0x00FF + }, + [COMPOUND_TEXT_ISO8859_10_GR] = { + 0x00A0, 0x0104, 0x0112, 0x0122, 0x012A, 0x0128, 0x0136, 0x00A7, + 0x013B, 0x0110, 0x0160, 0x0166, 0x017D, 0x00AD, 0x016A, 0x014A, + 0x00B0, 0x0105, 0x0113, 0x0123, 0x012B, 0x0129, 0x0137, 0x00B7, + 0x013C, 0x0111, 0x0161, 0x0167, 0x017E, 0x2015, 0x016B, 0x014B, + 0x0100, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x012E, + 0x010C, 0x00C9, 0x0118, 0x00CB, 0x0116, 0x00CD, 0x00CE, 0x00CF, + 0x00D0, 0x0145, 0x014C, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x0168, + 0x00D8, 0x0172, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, + 0x0101, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x012F, + 0x010D, 0x00E9, 0x0119, 0x00EB, 0x0117, 0x00ED, 0x00EE, 0x00EF, + 0x00F0, 0x0146, 0x014D, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x0169, + 0x00F8, 0x0173, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x0138 + }, + [COMPOUND_TEXT_ISO8859_13_GR] = { + 0x00A0, 0x201D, 0x00A2, 0x00A3, 0x00A4, 0x201E, 0x00A6, 0x00A7, + 0x00D8, 0x00A9, 0x0156, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00C6, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x201C, 0x00B5, 0x00B6, 0x00B7, + 0x00F8, 0x00B9, 0x0157, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00E6, + 0x0104, 0x012E, 0x0100, 0x0106, 0x00C4, 0x00C5, 0x0118, 0x0112, + 0x010C, 0x00C9, 0x0179, 0x0116, 0x0122, 0x0136, 0x012A, 0x013B, + 0x0160, 0x0143, 0x0145, 0x00D3, 0x014C, 0x00D5, 0x00D6, 0x00D7, + 0x0172, 0x0141, 0x015A, 0x016A, 0x00DC, 0x017B, 0x017D, 0x00DF, + 0x0105, 0x012F, 0x0101, 0x0107, 0x00E4, 0x00E5, 0x0119, 0x0113, + 0x010D, 0x00E9, 0x017A, 0x0117, 0x0123, 0x0137, 0x012B, 0x013C, + 0x0161, 0x0144, 0x0146, 0x00F3, 0x014D, 0x00F5, 0x00F6, 0x00F7, + 0x0173, 0x0142, 0x015B, 0x016B, 0x00FC, 0x017C, 0x017E, 0x2019 + }, + [COMPOUND_TEXT_ISO8859_14_GR] = { + 0x00A0, 0x1E02, 0x1E03, 0x00A3, 0x010A, 0x010B, 0x1E0A, 0x00A7, + 0x1E80, 0x00A9, 0x1E82, 0x1E0B, 0x1EF2, 0x00AD, 0x00AE, 0x0178, + 0x1E1E, 0x1E1F, 0x0120, 0x0121, 0x1E40, 0x1E41, 0x00B6, 0x1E56, + 0x1E81, 0x1E57, 0x1E83, 0x1E60, 0x1EF3, 0x1E84, 0x1E85, 0x1E61, + 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x0174, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x1E6A, + 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x0176, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x0175, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x1E6B, + 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x0177, 0x00FF + }, + [COMPOUND_TEXT_ISO8859_15_GR] = { + 0x00A0, 0x00A1, 0x00A2, 0x00A3, 0x20AC, 0x00A5, 0x0160, 0x00A7, + 0x0161, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, + 0x00B0, 0x00B1, 0x00B2, 0x00B3, 0x017D, 0x00B5, 0x00B6, 0x00B7, + 0x017E, 0x00B9, 0x00BA, 0x00BB, 0x0152, 0x0153, 0x0178, 0x00BF, + 0x00C0, 0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x00D0, 0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, + 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x00F0, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, + 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF + }, + [COMPOUND_TEXT_ISO8859_16_GR] = { + 0x00A0, 0x0104, 0x0105, 0x0141, 0x20AC, 0x201E, 0x0160, 0x00A7, + 0x0161, 0x00A9, 0x0218, 0x00AB, 0x0179, 0x00AD, 0x017A, 0x017B, + 0x00B0, 0x00B1, 0x010C, 0x0142, 0x017D, 0x201D, 0x00B6, 0x00B7, + 0x017E, 0x010D, 0x0219, 0x00BB, 0x0152, 0x0153, 0x0178, 0x017C, + 0x00C0, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0106, 0x00C6, 0x00C7, + 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, + 0x0110, 0x0143, 0x00D2, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x015A, + 0x0170, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x0118, 0x021A, 0x00DF, + 0x00E0, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x0107, 0x00E6, 0x00E7, + 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, + 0x0111, 0x0144, 0x00F2, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x015B, + 0x0171, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x0119, 0x021B, 0x00FF + }, + [COMPOUND_TEXT_JIS_X0201_GR] = { + 0x0000, 0x3002, 0x300C, 0x300D, 0x3001, 0x30FB, 0x30F2, 0x30A1, + 0x30A3, 0x30A5, 0x30A7, 0x30A9, 0x30E3, 0x30E5, 0x30E7, 0x30C3, + 0x30FC, 0x30A2, 0x30A4, 0x30A6, 0x30A8, 0x30AA, 0x30AB, 0x30AD, + 0x30AF, 0x30B1, 0x30B3, 0x30B5, 0x30B7, 0x30B9, 0x30BB, 0x30BD, + 0x30BF, 0x30C1, 0x30C4, 0x30C6, 0x30C8, 0x30CA, 0x30CB, 0x30CC, + 0x30CD, 0x30CE, 0x30CF, 0x30D2, 0x30D5, 0x30D8, 0x30DB, 0x30DE, + 0x30DF, 0x30E0, 0x30E1, 0x30E2, 0x30E4, 0x30E6, 0x30E8, 0x30E9, + 0x30EA, 0x30EB, 0x30EC, 0x30ED, 0x30EF, 0x30F3, 0x309B, 0x309C, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 + }, + [COMPOUND_TEXT_JIS_X0201_GL] = { + 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, + 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, + 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, + 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, + 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, + 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F, + 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x0058, 0x0059, 0x005A, 0x005B, 0x00A5, 0x005D, 0x005E, 0x005F, + 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, + 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F, + 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, + 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x203E, 0x007F + }, +}; + +struct compound_text_state { + const char *in; // Current input iterator + const char *end; // End of input + int *out; // Current result iterator + int gl_encoding; // Current GL encoding or -N for unknown N-octet + int gr_encoding; // Current GR encoding or -N for unknown N-octet +}; + +static bool +compound_text_peek(struct compound_text_state *s, unsigned char *c) +{ + if (s->in >= s->end) + return false; + + *c = *s->in; + return true; +} + +static bool +compound_text_read(struct compound_text_state *s, unsigned char *c) +{ + if (!compound_text_peek(s, c)) + return false; + + s->in++; + return true; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static bool +compound_text_skip_I(struct compound_text_state *s, unsigned char *c) +{ + if (*c >= 0x21 && *c <= 0x23) { + while (compound_text_peek(s, c) && *c >= 0x20 && *c <= 0x2f) + compound_text_read(s, c); + if (!compound_text_read(s, c)) + return false; + } + return true; +} + +static bool +compound_text_unknown_1(struct compound_text_state *s, unsigned char c, + int *encoding) +{ + if (!compound_text_skip_I(s, &c)) + return false; + else if (c >= 0x40 && c <= 0x7e) + *encoding = -1; + else + return false; + return true; +} + +static bool +compound_text_unknown_N(struct compound_text_state *s, unsigned char c, + int *encoding) +{ + if (!compound_text_skip_I(s, &c)) + return false; + else if (c >= 0x40 && c <= 0x5f) + *encoding = -2; + else if (c >= 0x60 && c <= 0x6f) + *encoding = -3; + else if (c >= 0x70 && c <= 0x7e) + return false; // "4 or more" + else + return false; + return true; +} + +static bool +compound_text_utf8(struct compound_text_state *s) +{ + // The specification isn't entirely clear about termination, + // let's be naïve and careful about what we accept + while (s->in + 3 <= s->end) { + if (s->in[0] == 0x1b && s->in[1] == 0x25 && s->in[2] == 0x40) { + s->in += 3; + return true; + } + + gunichar r = g_utf8_get_char_validated(s->in, s->end - s->in); + if (r == (gunichar) -1 || r == (gunichar) -2) + return false; + + // Don't allow circumventing the rules with this stupid mode + if (r < 0x20 && r != '\t' && r != '\n') + r = 0xFFFD; + + s->in = g_utf8_next_char(s->in); + *s->out++ = r; + } + return false; +} + +static bool +compound_text_ESC(struct compound_text_state *s) +{ + unsigned char c; + if (!compound_text_read(s, &c)) { + return false; + } else if (c == 0x28 /* GL 94 */) { + if (!compound_text_read(s, &c)) + return false; + else if (c == 0x42) + s->gl_encoding = COMPOUND_TEXT_ASCII; + else if (c == 0x4a) + s->gl_encoding = COMPOUND_TEXT_JIS_X0201_GL; + else if (!compound_text_unknown_1(s, c, &s->gl_encoding)) + return false; + } else if (c == 0x29 /* GR 94 */) { + if (!compound_text_read(s, &c)) + return false; + else if (c == 0x49) + s->gr_encoding = COMPOUND_TEXT_JIS_X0201_GR; + else if (!compound_text_unknown_1(s, c, &s->gr_encoding)) + return false; + } else if (c == 0x2d /* GR 96 */) { + if (!compound_text_read(s, &c)) + return false; + else if (c == 0x41) + s->gr_encoding = COMPOUND_TEXT_ISO8859_1_GR; + else if (c == 0x42) + s->gr_encoding = COMPOUND_TEXT_ISO8859_2_GR; + else if (c == 0x43) + s->gr_encoding = COMPOUND_TEXT_ISO8859_3_GR; + else if (c == 0x44) + s->gr_encoding = COMPOUND_TEXT_ISO8859_4_GR; + else if (c == 0x46) + s->gr_encoding = COMPOUND_TEXT_ISO8859_7_GR; + else if (c == 0x47) + s->gr_encoding = COMPOUND_TEXT_ISO8859_6_GR; + else if (c == 0x48) + s->gr_encoding = COMPOUND_TEXT_ISO8859_8_GR; + else if (c == 0x4c) + s->gr_encoding = COMPOUND_TEXT_ISO8859_5_GR; + else if (c == 0x4d) + s->gr_encoding = COMPOUND_TEXT_ISO8859_9_GR; + else if (c == 0x56) + s->gr_encoding = COMPOUND_TEXT_ISO8859_10_GR; + else if (c == 0x59) + s->gr_encoding = COMPOUND_TEXT_ISO8859_13_GR; + else if (c == 0x5f) + s->gr_encoding = COMPOUND_TEXT_ISO8859_14_GR; + else if (c == 0x62) + s->gr_encoding = COMPOUND_TEXT_ISO8859_15_GR; + else if (c == 0x66) + s->gr_encoding = COMPOUND_TEXT_ISO8859_16_GR; + else if (!compound_text_unknown_1(s, c, &s->gr_encoding)) + return false; + } else if (c == 0x24 /* ^N */) { + if (!compound_text_read(s, &c)) { + return false; + } else if (c == 0x28 /* GL 94^N */) { + if (!compound_text_read(s, &c) || + !compound_text_unknown_N(s, c, &s->gl_encoding)) + return false; + } else if (c == 0x29 /* GR 94^N */) { + if (!compound_text_read(s, &c) || + !compound_text_unknown_N(s, c, &s->gr_encoding)) + return false; + } else { + return false; + } + } else if (c == 0x25 /* Non-Standard Character Set Encodings */) { + if (!compound_text_read(s, &c)) + return false; + if (c == 0x47 /* from version 1.1.xf86.1 */) + return compound_text_utf8(s); + if (c != 0x2f || !compound_text_read(s, &c) || c < 0x30 || c > 0x34) + return false; + + if (!compound_text_read(s, &c) || !(c & 0x80)) + return false; + int skip_h = c - 128; + + if (!compound_text_read(s, &c) || !(c & 0x80)) + return false; + int skip_l = c - 128; + + for (int skip = skip_h << 8 | skip_l; skip--; ) { + if (!compound_text_read(s, &c)) + return false; + } + + // TODO: this would deserve more obvious handling, + // we're replacing an entire sequence with just one character. + // For that, we'd need to parse the sequence, though. + *s->out++ = 0xFFFD; + } else if (c == 0x23 /* Extensions, starting control sequences */) { + // NOTE: major version = c - 0x20 + 1 + if (!compound_text_read(s, &c) || c < 0x20 || c > 0x2f) + return false; + + if (!compound_text_read(s, &c)) + return false; + else if (c == 0x30) + return false; // not supported: ignoring extensions is OK + else if (c == 0x31) + return false; // not supported: ignoring extensions is not OK + else + return false; + } else if (c >= 0x20 && c <= 0x2f /* extension, Intermediate */) { + return false; // not supported + } else if (c >= 0x30 && c <= 0x7e /* extension, Final */) { + return false; // not supported + } + return true; +} + +static bool +compound_text_CSI(struct compound_text_state *s) +{ + unsigned char c; + if (!compound_text_read(s, &c)) { + return false; + } else if (c == 0x31) { + if (!compound_text_read(s, &c) || c != 0x5d) + return false; + *s->out++ = 0x202A; // LRE + } else if (c == 0x32) { + if (!compound_text_read(s, &c) || c != 0x5d) + return false; + *s->out++ = 0x202B; // RLE + } else if (c == 0x5d) { + *s->out++ = 0x202C; // PDF + } else if (c >= 0x30 && c <= 0x3f /* extension, P */) { + return false; // not supported + } else if (c >= 0x20 && c <= 0x2f /* extension, Intermediate */) { + return false; // not supported + } else if (c >= 0x40 && c <= 0x7e /* extension, Final */) { + return false; // not supported + } else { + return false; + } + return true; +} + +static bool +compound_text_GL(struct compound_text_state *s, unsigned char c) +{ + if (s->gl_encoding < 0 || + !(*s->out = compound_text_tables[s->gl_encoding][c - 0x20])) + *s->out = 0xFFFD; + for (int i = 0; --i > s->gl_encoding; ) { + if (!compound_text_read(s, &c) || c < 0x20 || c >= 0x80) + return false; + } + s->out++; + return true; +} + +static bool +compound_text_GR(struct compound_text_state *s, unsigned char c) +{ + if (s->gr_encoding < 0 || + !(*s->out = compound_text_tables[s->gr_encoding][c - 0xa0])) + *s->out = 0xFFFD; + for (int i = 0; --i > s->gr_encoding; ) { + if (!compound_text_read(s, &c) || c < 0xa0) + return false; + } + s->out++; + return true; +} + +int * +compound_text_to_ucs4(const char *compound_text, size_t length) +{ + // This is a good approximation, as well as the upper bound + int *result = calloc(sizeof *result, length + 1); + struct compound_text_state s = { + .in = compound_text, .end = compound_text + length, .out = result, + .gl_encoding = COMPOUND_TEXT_ASCII, + .gr_encoding = COMPOUND_TEXT_ISO8859_1_GR, + }; + + unsigned char c; + while (compound_text_read(&s, &c) && c != 0) { + bool ok = true; + if (c == '\t' || c == '\n') + *s.out++ = c; + else if (c == 0x1b) + ok = compound_text_ESC(&s); + else if (c == 0x9b) + ok = compound_text_CSI(&s); + else if ((c & ~0x80) < 0x20 /* C0, C1 */) + *s.out++ = 0xFFFD; + else if (c < 0x80) + ok = compound_text_GL(&s, c); + else + ok = compound_text_GR(&s, c); + + if (!ok) { + // TODO: consider returning partial results + free(result); + return NULL; + } + } + *s.out++ = 0; + return result; +} + +#if COMPOUND_TEXT_SELFTEST +// Build with -DCOMPOUND_TEXT_SELFTEST `pkg-config --cflags --libs x11 glib-2.0` +#include +#include + +int +main(void) +{ + Display *dpy = XOpenDisplay(NULL); GString *s = g_string_new(""); + while (1) { + g_string_truncate(s, 0); + for (gsize i = 0; i < 10; i++) { + int c = rand() & 0x10FFFF; + if ((c < 0xD800 || c > 0xDFFF) && // GLib rejects surrogates + (c != 0x9b) && // Xlib inserts a lone CSI (!) + (c >= 0x20)) // not allowed or disruptive + g_string_append_unichar(s, c); + } + + XTextProperty prop; + Xutf8TextListToTextProperty(dpy, (char **) &s->str, 1, + XCompoundTextStyle, &prop); + + int *ucs4 = NULL; char *x = NULL; + if (!(ucs4 = compound_text_to_ucs4((char *) prop.value, prop.nitems))) + g_printerr("parse error '%s' -> '%s'\n", s->str, prop.value); + else if (!(x = g_ucs4_to_utf8((gunichar *) ucs4, -1, NULL, NULL, NULL))) + g_printerr("total failure: %s\n", prop.value); + free(ucs4); free(x); XFree(prop.value); + } +} +#endif diff --git a/compound-text.h b/compound-text.h new file mode 100644 index 0000000..ecd3e67 --- /dev/null +++ b/compound-text.h @@ -0,0 +1,21 @@ +// +// compound-text.h: partial X11 COMPOUND_TEXT to UCS-4 transcoder +// +// Copyright (c) 2020, Přemysl Eric Janouch +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +// + +#include + +// Convert an X11 COMPOUND_TEXT string to a NUL-terminated UCS4 sequence +int *compound_text_to_ucs4(const char *compound_text, size_t length); diff --git a/meson.build b/meson.build index 62f58d0..95bc29e 100644 --- a/meson.build +++ b/meson.build @@ -19,14 +19,13 @@ dependencies = [ dependency('gio-unix-2.0'), dependency('gee-0.8'), dependency('sqlite3'), - dependency('x11'), - dependency('xext'), - dependency('xextproto'), + dependency('xcb'), + dependency('xcb-sync'), ] gui = static_library('gui', 'gui.vala', 'config.vapi', install : false, dependencies : dependencies) -executable('wdmtg', 'wdmtg.c', +executable('wdmtg', 'wdmtg.c', 'compound-text.c', install : true, link_with : [gui], dependencies : [dependencies]) diff --git a/wdmtg.c b/wdmtg.c index 9483ed2..1b8e7ec 100644 --- a/wdmtg.c +++ b/wdmtg.c @@ -28,14 +28,12 @@ #include #include -#include -#include -#include -#include -#include +#include +#include #include "config.h" #include "gui.h" +#include "compound-text.h" // --- Utilities --------------------------------------------------------------- @@ -67,6 +65,22 @@ get_xdg_config_dirs(void) return (GStrv) g_ptr_array_free(paths, false); } +static GString * +latin1_to_utf8(const gchar *latin1, gsize len) +{ + GString *s = g_string_new(NULL); + while (len--) { + guchar c = *latin1++; + if (c < 0x80) { + g_string_append_c(s, c); + } else { + g_string_append_c(s, 0xC0 | (c >> 6)); + g_string_append_c(s, 0x80 | (c & 0x3F)); + } + } + return s; +} + // --- Globals ----------------------------------------------------------------- struct event { @@ -90,86 +104,143 @@ struct { } g; struct { - Display *dpy; // X display handle + xcb_connection_t *X; // X display handle + xcb_screen_t *screen; // X screen information GThread *thread; // Worker thread - Atom net_active_window; // _NET_ACTIVE_WINDOW - Atom net_wm_name; // _NET_WM_NAME + xcb_atom_t atom_net_active_window; // _NET_ACTIVE_WINDOW + xcb_atom_t atom_net_wm_name; // _NET_WM_NAME + xcb_atom_t atom_utf8_string; // UTF8_STRING + xcb_atom_t atom_compound_text; // COMPOUND_TEXT // Window title tracking gchar *current_title; // Current window title or NULL - Window current_window; // Current window + xcb_window_t current_window; // Current window gboolean current_idle; // Current idle status // XSync activity tracking - int xsync_base_event_code; // XSync base event code - XSyncCounter idle_counter; // XSync IDLETIME counter - XSyncValue idle_timeout; // Idle timeout + const xcb_query_extension_reply_t *sync; // Sync extension + xcb_sync_counter_t idle_counter; // Sync IDLETIME counter + xcb_sync_int64_t idle_timeout; // Idle timeout - XSyncAlarm idle_alarm_inactive; // User is inactive - XSyncAlarm idle_alarm_active; // User is active + xcb_sync_alarm_t idle_alarm_inactive; // User is inactive + xcb_sync_alarm_t idle_alarm_active; // User is active } gen; -// --- X helpers --------------------------------------------------------------- +// --- XCB helpers ------------------------------------------------------------- -static XSyncCounter -get_counter(const char *name) +static xcb_atom_t +intern_atom(const char *atom) { - int n; - XSyncSystemCounter *counters = XSyncListSystemCounters(gen.dpy, &n); - XSyncCounter counter = None; - while (n--) { - if (!strcmp(counters[n].name, name)) - counter = counters[n].counter; - } - XSyncFreeSystemCounterList(counters); - return counter; -} - -static char * -x_text_property_to_utf8(XTextProperty *prop) -{ - Atom utf8_string = XInternAtom(gen.dpy, "UTF8_STRING", true); - if (prop->encoding == utf8_string) - return g_strdup((char *) prop->value); - - int n = 0; - char **list = NULL; - if (XmbTextPropertyToTextList(gen.dpy, prop, &list, &n) >= Success - && n > 0 && *list) { - char *result = g_locale_to_utf8(*list, -1, NULL, NULL, NULL); - XFreeStringList(list); - return result; - } - return NULL; -} - -static char * -x_text_property(Window window, Atom atom) -{ - XTextProperty name; - XGetTextProperty(gen.dpy, window, &name, atom); - if (!name.value) - return NULL; - - char *result = x_text_property_to_utf8(&name); - XFree(name.value); + xcb_intern_atom_reply_t *iar = xcb_intern_atom_reply(gen.X, + xcb_intern_atom(gen.X, false, strlen(atom), atom), NULL); + xcb_atom_t result = iar ? iar->atom : XCB_NONE; + free(iar); return result; } -// --- X error handling -------------------------------------------------------- - -static XErrorHandler g_default_x_error_handler; - -static int -on_x_error(Display *dpy, XErrorEvent *ee) +static xcb_sync_counter_t +find_counter(xcb_sync_list_system_counters_reply_t *slsr, const char *name) { - // This just is going to happen since those windows aren't ours - if (ee->error_code == BadWindow) - return 0; - return g_default_x_error_handler(dpy, ee); + // FIXME: https://gitlab.freedesktop.org/xorg/lib/libxcb/-/issues/36 + const size_t xcb_sync_systemcounter_t_len = 14; + + xcb_sync_systemcounter_iterator_t slsi = + xcb_sync_list_system_counters_counters_iterator(slsr); + while (slsi.rem--) { + xcb_sync_systemcounter_t *counter = slsi.data; + char *counter_name = (char *) counter + xcb_sync_systemcounter_t_len; + if (!strncmp(counter_name, name, counter->name_len) && + !name[counter->name_len]) + return counter->counter; + + slsi.data = (void *) counter + + ((xcb_sync_systemcounter_t_len + counter->name_len + 3) & ~3); + } + return XCB_NONE; +} + +static xcb_sync_counter_t +get_counter(const char *name) +{ + xcb_sync_list_system_counters_reply_t *slsr = + xcb_sync_list_system_counters_reply(gen.X, + xcb_sync_list_system_counters(gen.X), NULL); + + xcb_sync_counter_t counter = XCB_NONE; + if (slsr) { + counter = find_counter(slsr, name); + free(slsr); + } + return counter; +} + +// --- X helpers --------------------------------------------------------------- + +static GString * +x_text_property_to_utf8(GString *value, xcb_atom_t encoding) +{ + if (encoding == gen.atom_utf8_string) + return value; + + if (encoding == XCB_ATOM_STRING) { + // Could use g_convert() but this will certainly never fail + GString *utf8 = latin1_to_utf8(value->str, value->len); + g_string_free(value, true); + return utf8; + } + + // COMPOUND_TEXT doesn't deserve support for multiple NUL-separated items + int *ucs4 = NULL; + if (encoding == gen.atom_compound_text && + (ucs4 = compound_text_to_ucs4((char *) value->str, value->len))) { + g_string_free(value, true); + glong len = 0; + gchar *utf8 = g_ucs4_to_utf8((gunichar *) ucs4, -1, NULL, &len, NULL); + free(ucs4); + + // malloc failure or, rather theoretically, an out of range codepoint + if (utf8) { + value = g_string_new_len(utf8, len); + free(utf8); + return value; + } + } + + g_string_free(value, true); + return NULL; +} + +static GString * +x_text_property(xcb_window_t window, xcb_atom_t property) +{ + GString *buffer = g_string_new(NULL); + xcb_atom_t type = XCB_NONE; + uint32_t offset = 0; + + xcb_get_property_reply_t *gpr = NULL; + while ((gpr = xcb_get_property_reply(gen.X, + xcb_get_property(gen.X, false /* delete */, window, + property, XCB_GET_PROPERTY_TYPE_ANY, offset, 0x8000), NULL))) { + if (gpr->format != 8 || (type && gpr->type != type)) { + free(gpr); + break; + } + + int len = xcb_get_property_value_length(gpr); + g_string_append_len(buffer, xcb_get_property_value(gpr), len); + offset += len >> 2; + type = gpr->type; + + bool last = !gpr->bytes_after; + free(gpr); + if (last) + return x_text_property_to_utf8(buffer, type); + } + g_string_free(buffer, true); + return NULL; } // --- Generator --------------------------------------------------------------- @@ -184,13 +255,13 @@ push_event(void) { } static char * -x_window_title(Window window) +x_window_title(xcb_window_t window) { - char *title; - if (!(title = x_text_property(window, gen.net_wm_name)) - && !(title = x_text_property(window, XA_WM_NAME))) - title = g_strdup("broken"); - return title; + GString *title; + if (!(title = x_text_property(window, gen.atom_net_wm_name)) + && !(title = x_text_property(window, XCB_ATOM_WM_NAME))) + return g_strdup("broken"); + return g_string_free(title, false); } static bool @@ -206,27 +277,38 @@ update_window_title(char *new_title) static void update_current_window(void) { - Window root = DefaultRootWindow(gen.dpy); - - Atom dummy_type; int dummy_format; unsigned long nitems, dummy_bytes; - unsigned char *p = NULL; - if (XGetWindowProperty(gen.dpy, root, gen.net_active_window, - 0L, 1L, false, XA_WINDOW, &dummy_type, &dummy_format, - &nitems, &dummy_bytes, &p) != Success) + xcb_get_property_reply_t *gpr = xcb_get_property_reply(gen.X, + xcb_get_property(gen.X, false /* delete */, gen.screen->root, + gen.atom_net_active_window, XCB_ATOM_WINDOW, 0, 1), NULL); + if (!gpr) return; + // TODO: also consider watching XCB_ATOM_WM_CLASS + // - the first one is an "Instance name", the second one a "Class name" + // - don't know which one to pick, though probably the second one, + // since the instance name is Navigator/Mail for Firefox/Thunderbird + // - they are separated by a NUL character + // - it's not worth to watch for changes of this property + char *new_title = NULL; - if (nitems) { - Window active_window = *(Window *) p; - XFree(p); + if (xcb_get_property_value_length(gpr)) { + xcb_window_t active_window = + *(xcb_window_t *) xcb_get_property_value(gpr); + const uint32_t disable[] = { 0 }; if (gen.current_window != active_window && gen.current_window) - XSelectInput(gen.dpy, gen.current_window, 0); + (void) xcb_change_window_attributes(gen.X, + gen.current_window, XCB_CW_EVENT_MASK, disable); + + const uint32_t enable[] = { XCB_EVENT_MASK_PROPERTY_CHANGE }; + (void) xcb_change_window_attributes(gen.X, + active_window, XCB_CW_EVENT_MASK, enable); - XSelectInput(gen.dpy, active_window, PropertyChangeMask); new_title = x_window_title(active_window); gen.current_window = active_window; } + + free(gpr); if (update_window_title(new_title)) { printf("Window changed: %s\n", gen.current_title ? gen.current_title : "(none)"); @@ -235,19 +317,13 @@ update_current_window(void) } static void -on_x_property_notify(XPropertyEvent *ev) +on_x_property_notify(const xcb_property_notify_event_t *ev) { - // TODO: also consider watch WM_CLASS - // - the first one is an "Instance name", the second one a "Class name" - // - don't know which one to pick, though probably the second one, - // since the instance name is Navigator/Mail for Firefox/Thunderbird - // - perhaps only when the active window has changed, so u_c_window() - // This is from the EWMH specification, set by the window manager - if (ev->atom == gen.net_active_window) { + if (ev->atom == gen.atom_net_active_window) { update_current_window(); } else if (ev->window == gen.current_window && - ev->atom == gen.net_wm_name) { + ev->atom == gen.atom_net_wm_name) { if (update_window_title(x_window_title(ev->window))) { printf("Title changed: %s\n", gen.current_title); push_event(); @@ -256,67 +332,83 @@ on_x_property_notify(XPropertyEvent *ev) } static void -set_idle_alarm(XSyncAlarm *alarm, XSyncTestType test, XSyncValue value) +set_idle_alarm(xcb_sync_alarm_t *alarm, xcb_sync_testtype_t test, + xcb_sync_int64_t value) { - XSyncAlarmAttributes attr; - attr.trigger.counter = gen.idle_counter; - attr.trigger.test_type = test; - attr.trigger.wait_value = value; - XSyncIntToValue(&attr.delta, 0); + // TODO: consider xcb_sync_{change,create}_alarm_aux() + uint32_t values[] = { + gen.idle_counter, + value.hi, value.lo, + test, + 0, 0, + }; - long flags = XSyncCACounter | XSyncCATestType | XSyncCAValue | XSyncCADelta; - if (*alarm) - XSyncChangeAlarm(gen.dpy, *alarm, flags, &attr); - else - *alarm = XSyncCreateAlarm(gen.dpy, flags, &attr); + xcb_sync_ca_t flags = XCB_SYNC_CA_COUNTER | XCB_SYNC_CA_VALUE | + XCB_SYNC_CA_TEST_TYPE | XCB_SYNC_CA_DELTA; + if (*alarm) { + xcb_sync_change_alarm(gen.X, *alarm, flags, values); + } else { + *alarm = xcb_generate_id(gen.X); + xcb_sync_create_alarm(gen.X, *alarm, flags, values); + } } static void -on_x_alarm_notify(XSyncAlarmNotifyEvent *ev) +on_x_alarm_notify(const xcb_sync_alarm_notify_event_t *ev) { if (ev->alarm == gen.idle_alarm_inactive) { printf("User is inactive\n"); gen.current_idle = true; push_event(); - XSyncValue one, minus_one; - XSyncIntToValue(&one, 1); - - Bool overflow; - XSyncValueSubtract(&minus_one, ev->counter_value, one, &overflow); + xcb_sync_int64_t minus_one = ev->counter_value; + if (!~(--minus_one.lo)) + minus_one.hi--; // Set an alarm for IDLETIME <= current_idletime - 1 set_idle_alarm(&gen.idle_alarm_active, - XSyncNegativeComparison, minus_one); + XCB_SYNC_TESTTYPE_NEGATIVE_COMPARISON, minus_one); } else if (ev->alarm == gen.idle_alarm_active) { printf("User is active\n"); gen.current_idle = false; push_event(); set_idle_alarm(&gen.idle_alarm_inactive, - XSyncPositiveComparison, gen.idle_timeout); + XCB_SYNC_TESTTYPE_POSITIVE_COMPARISON, gen.idle_timeout); + } +} + +static gboolean +process_x11_event(xcb_generic_event_t *ev) +{ + int event_code = ev->response_type & 0x7f; + if (!event_code) { + xcb_generic_error_t *err = (xcb_generic_error_t *) ev; + // TODO: report the error + } else if (event_code == XCB_PROPERTY_NOTIFY) { + on_x_property_notify((const xcb_property_notify_event_t *) ev); + } else if (event_code == gen.sync->first_event + XCB_SYNC_ALARM_NOTIFY) { + on_x_alarm_notify((const xcb_sync_alarm_notify_event_t *) ev); } } static gboolean on_x_ready(G_GNUC_UNUSED gpointer user_data) { - XEvent ev; - while (XPending(gen.dpy)) { - if (XNextEvent(gen.dpy, &ev)) - exit_fatal("XNextEvent returned non-zero"); - else if (ev.type == PropertyNotify) - on_x_property_notify(&ev.xproperty); - else if (ev.type == gen.xsync_base_event_code + XSyncAlarmNotify) - on_x_alarm_notify((XSyncAlarmNotifyEvent *) &ev); + xcb_generic_event_t *event; + while ((event = xcb_poll_for_event(gen.X))) { + process_x11_event(event); + free(event); } - return G_SOURCE_CONTINUE; + (void) xcb_flush(gen.X); + // TODO: some form of error handling, this just silently stops working + return xcb_connection_has_error(gen.X) == 0; } static void generator_thread(void) { - GIOChannel *channel = g_io_channel_unix_new(ConnectionNumber(gen.dpy)); + GIOChannel *channel = g_io_channel_unix_new(xcb_get_file_descriptor(gen.X)); GSource *watch = g_io_create_watch(channel, G_IO_IN); g_source_set_callback(watch, on_x_ready, NULL, NULL); @@ -329,34 +421,50 @@ generator_thread(void) static void generator_init(void) { - if (!XSupportsLocale()) - exit_fatal("locale not supported by Xlib"); + int which_screen = -1, xcb_error; + gen.X = xcb_connect(NULL, &which_screen); + if ((xcb_error = xcb_connection_has_error(gen.X))) + exit_fatal("cannot open display (code %d)", xcb_error); - XInitThreads(); - if (!(gen.dpy = XOpenDisplay(NULL))) - exit_fatal("cannot open display"); - - gen.net_active_window = XInternAtom(gen.dpy, "_NET_ACTIVE_WINDOW", true); - gen.net_wm_name = XInternAtom(gen.dpy, "_NET_WM_NAME", true); + if (!(gen.atom_net_active_window = intern_atom("_NET_ACTIVE_WINDOW")) || + !(gen.atom_net_wm_name = intern_atom("_NET_WM_NAME")) || + !(gen.atom_utf8_string = intern_atom("UTF8_STRING")) || + !(gen.atom_compound_text = intern_atom("COMPOUND_TEXT"))) + exit_fatal("unable to resolve atoms"); // TODO: it is possible to employ a fallback mechanism via XScreenSaver // by polling the XScreenSaverInfo::idle field, see // https://www.x.org/releases/X11R7.5/doc/man/man3/Xss.3.html - int dummy; - if (!XSyncQueryExtension(gen.dpy, &gen.xsync_base_event_code, &dummy) - || !XSyncInitialize(gen.dpy, &dummy, &dummy)) - exit_fatal("cannot initialize XSync"); + gen.sync = xcb_get_extension_data(gen.X, &xcb_sync_id); + if (!gen.sync->present) + exit_fatal("missing Sync extension"); + + xcb_generic_error_t *err = NULL; + xcb_sync_initialize_cookie_t sic = xcb_sync_initialize(gen.X, + XCB_SYNC_MAJOR_VERSION, XCB_SYNC_MINOR_VERSION); + xcb_sync_initialize_reply_t *sir = + xcb_sync_initialize_reply(gen.X, sic, &err); + if (!sir) + exit_fatal("failed to initialise Sync extension"); + free(sir); // The idle counter is not guaranteed to exist, only SERVERTIME is if (!(gen.idle_counter = get_counter("IDLETIME"))) exit_fatal("idle counter is missing"); - Window root = DefaultRootWindow(gen.dpy); - XSelectInput(gen.dpy, root, PropertyChangeMask); - XSync(gen.dpy, False); - // TODO: what is the interaction with GTK+ here? - g_default_x_error_handler = XSetErrorHandler(on_x_error); + const xcb_setup_t *setup = xcb_get_setup(gen.X); + xcb_screen_iterator_t setup_iter = xcb_setup_roots_iterator(setup); + while (which_screen--) + xcb_screen_next(&setup_iter); + + gen.screen = setup_iter.data; + xcb_window_t root = gen.screen->root; + + const uint32_t values[] = { XCB_EVENT_MASK_PROPERTY_CHANGE }; + (void) xcb_change_window_attributes(gen.X, root, XCB_CW_EVENT_MASK, values); + (void) xcb_flush(gen.X); + // TODO: how are XCB errors handled? What if the last xcb_flush() fails? GKeyFile *kf = g_key_file_new(); gchar *subpath = g_build_filename(PROJECT_NAME, PROJECT_NAME ".conf", NULL); @@ -374,10 +482,13 @@ generator_init(void) g_free(subpath); g_key_file_free(kf); - XSyncIntToValue(&gen.idle_timeout, timeout * 1000); + gint64 timeout_ms = timeout * 1000; + gen.idle_timeout.hi = timeout_ms >> 32; + gen.idle_timeout.lo = timeout_ms; + update_current_window(); set_idle_alarm(&gen.idle_alarm_inactive, - XSyncPositiveComparison, gen.idle_timeout); + XCB_SYNC_TESTTYPE_POSITIVE_COMPARISON, gen.idle_timeout); } static void @@ -392,7 +503,7 @@ generator_cleanup(void) { g_thread_join(gen.thread); free(gen.current_title); - XCloseDisplay(gen.dpy); + xcb_disconnect(gen.X); } // --- Main -------------------------------------------------------------------- diff --git a/xext.vapi b/xcb-sync.vapi similarity index 100% rename from xext.vapi rename to xcb-sync.vapi diff --git a/xextproto.vapi b/xextproto.vapi deleted file mode 100644 index d5e2b49..0000000 --- a/xextproto.vapi +++ /dev/null @@ -1 +0,0 @@ -// https://github.com/mesonbuild/meson/issues/1195