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.
This commit is contained in:
Přemysl Eric Janouch 2020-09-23 16:58:30 +02:00
parent 93c61425b3
commit 6e3f3c950d
Signed by: p
GPG Key ID: A0420B94F92B9493
7 changed files with 913 additions and 149 deletions

View File

@ -28,7 +28,7 @@ endif (OPTION_NOINSTALL)
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
find_package (Vala 0.12 REQUIRED) find_package (Vala 0.12 REQUIRED)
find_package (PkgConfig 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 # Precompile Vala sources
include (ValaPrecompile) include (ValaPrecompile)
@ -57,7 +57,8 @@ set (project_SOURCES ${project_VALA_SOURCES} ${project_VALA_C} ${symbols_path})
# Build the executable and install it # Build the executable and install it
include_directories (${dependencies_INCLUDE_DIRS}) include_directories (${dependencies_INCLUDE_DIRS})
link_directories (${dependencies_LIBRARY_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}) target_link_libraries (${PROJECT_NAME} ${dependencies_LIBRARIES})
install (TARGETS ${PROJECT_NAME} DESTINATION bin) install (TARGETS ${PROJECT_NAME} DESTINATION bin)

633
compound-text.c Normal file
View File

@ -0,0 +1,633 @@
//
// compound-text.c: partial X11 COMPOUND_TEXT to UCS-4 transcoder
//
// Copyright (c) 2020, Přemysl Eric Janouch <p@janouch.name>
//
// 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 <stdbool.h>
#include <stdlib.h> // malloc, free, NULL, size_t
#include <glib.h> // 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|^<U(.*?)> +/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 <X11/Xlib.h>
#include <X11/Xutil.h>
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

21
compound-text.h Normal file
View File

@ -0,0 +1,21 @@
//
// compound-text.h: partial X11 COMPOUND_TEXT to UCS-4 transcoder
//
// Copyright (c) 2020, Přemysl Eric Janouch <p@janouch.name>
//
// 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 <stdlib.h>
// Convert an X11 COMPOUND_TEXT string to a NUL-terminated UCS4 sequence
int *compound_text_to_ucs4(const char *compound_text, size_t length);

View File

@ -19,14 +19,13 @@ dependencies = [
dependency('gio-unix-2.0'), dependency('gio-unix-2.0'),
dependency('gee-0.8'), dependency('gee-0.8'),
dependency('sqlite3'), dependency('sqlite3'),
dependency('x11'), dependency('xcb'),
dependency('xext'), dependency('xcb-sync'),
dependency('xextproto'),
] ]
gui = static_library('gui', 'gui.vala', 'config.vapi', gui = static_library('gui', 'gui.vala', 'config.vapi',
install : false, install : false,
dependencies : dependencies) dependencies : dependencies)
executable('wdmtg', 'wdmtg.c', executable('wdmtg', 'wdmtg.c', 'compound-text.c',
install : true, install : true,
link_with : [gui], link_with : [gui],
dependencies : [dependencies]) dependencies : [dependencies])

373
wdmtg.c
View File

@ -28,14 +28,12 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/un.h> #include <sys/un.h>
#include <X11/Xlib.h> #include <xcb/xcb.h>
#include <X11/Xatom.h> #include <xcb/sync.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/extensions/sync.h>
#include "config.h" #include "config.h"
#include "gui.h" #include "gui.h"
#include "compound-text.h"
// --- Utilities --------------------------------------------------------------- // --- Utilities ---------------------------------------------------------------
@ -67,6 +65,22 @@ get_xdg_config_dirs(void)
return (GStrv) g_ptr_array_free(paths, false); 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 ----------------------------------------------------------------- // --- Globals -----------------------------------------------------------------
struct event { struct event {
@ -90,86 +104,143 @@ struct {
} g; } g;
struct { struct {
Display *dpy; // X display handle xcb_connection_t *X; // X display handle
xcb_screen_t *screen; // X screen information
GThread *thread; // Worker thread GThread *thread; // Worker thread
Atom net_active_window; // _NET_ACTIVE_WINDOW xcb_atom_t atom_net_active_window; // _NET_ACTIVE_WINDOW
Atom net_wm_name; // _NET_WM_NAME 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 // Window title tracking
gchar *current_title; // Current window title or NULL 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 gboolean current_idle; // Current idle status
// XSync activity tracking // XSync activity tracking
int xsync_base_event_code; // XSync base event code const xcb_query_extension_reply_t *sync; // Sync extension
XSyncCounter idle_counter; // XSync IDLETIME counter xcb_sync_counter_t idle_counter; // Sync IDLETIME counter
XSyncValue idle_timeout; // Idle timeout xcb_sync_int64_t idle_timeout; // Idle timeout
XSyncAlarm idle_alarm_inactive; // User is inactive xcb_sync_alarm_t idle_alarm_inactive; // User is inactive
XSyncAlarm idle_alarm_active; // User is active xcb_sync_alarm_t idle_alarm_active; // User is active
} gen; } gen;
// --- X helpers --------------------------------------------------------------- // --- XCB helpers -------------------------------------------------------------
static XSyncCounter static xcb_atom_t
intern_atom(const char *atom)
{
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;
}
static xcb_sync_counter_t
find_counter(xcb_sync_list_system_counters_reply_t *slsr, const char *name)
{
// 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) get_counter(const char *name)
{ {
int n; xcb_sync_list_system_counters_reply_t *slsr =
XSyncSystemCounter *counters = XSyncListSystemCounters(gen.dpy, &n); xcb_sync_list_system_counters_reply(gen.X,
XSyncCounter counter = None; xcb_sync_list_system_counters(gen.X), NULL);
while (n--) {
if (!strcmp(counters[n].name, name)) xcb_sync_counter_t counter = XCB_NONE;
counter = counters[n].counter; if (slsr) {
counter = find_counter(slsr, name);
free(slsr);
} }
XSyncFreeSystemCounterList(counters);
return counter; return counter;
} }
static char * // --- X helpers ---------------------------------------------------------------
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; static GString *
char **list = NULL; x_text_property_to_utf8(GString *value, xcb_atom_t encoding)
if (XmbTextPropertyToTextList(gen.dpy, prop, &list, &n) >= Success {
&& n > 0 && *list) { if (encoding == gen.atom_utf8_string)
char *result = g_locale_to_utf8(*list, -1, NULL, NULL, NULL); return value;
XFreeStringList(list);
return result; 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; return NULL;
} }
static char * static GString *
x_text_property(Window window, Atom atom) x_text_property(xcb_window_t window, xcb_atom_t property)
{ {
XTextProperty name; GString *buffer = g_string_new(NULL);
XGetTextProperty(gen.dpy, window, &name, atom); xcb_atom_t type = XCB_NONE;
if (!name.value) uint32_t offset = 0;
return NULL;
char *result = x_text_property_to_utf8(&name); xcb_get_property_reply_t *gpr = NULL;
XFree(name.value); while ((gpr = xcb_get_property_reply(gen.X,
return result; 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;
} }
// --- X error handling -------------------------------------------------------- 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;
static XErrorHandler g_default_x_error_handler; bool last = !gpr->bytes_after;
free(gpr);
static int if (last)
on_x_error(Display *dpy, XErrorEvent *ee) return x_text_property_to_utf8(buffer, type);
{ }
// This just is going to happen since those windows aren't ours g_string_free(buffer, true);
if (ee->error_code == BadWindow) return NULL;
return 0;
return g_default_x_error_handler(dpy, ee);
} }
// --- Generator --------------------------------------------------------------- // --- Generator ---------------------------------------------------------------
@ -184,13 +255,13 @@ push_event(void) {
} }
static char * static char *
x_window_title(Window window) x_window_title(xcb_window_t window)
{ {
char *title; GString *title;
if (!(title = x_text_property(window, gen.net_wm_name)) if (!(title = x_text_property(window, gen.atom_net_wm_name))
&& !(title = x_text_property(window, XA_WM_NAME))) && !(title = x_text_property(window, XCB_ATOM_WM_NAME)))
title = g_strdup("broken"); return g_strdup("broken");
return title; return g_string_free(title, false);
} }
static bool static bool
@ -206,27 +277,38 @@ update_window_title(char *new_title)
static void static void
update_current_window(void) update_current_window(void)
{ {
Window root = DefaultRootWindow(gen.dpy); xcb_get_property_reply_t *gpr = xcb_get_property_reply(gen.X,
xcb_get_property(gen.X, false /* delete */, gen.screen->root,
Atom dummy_type; int dummy_format; unsigned long nitems, dummy_bytes; gen.atom_net_active_window, XCB_ATOM_WINDOW, 0, 1), NULL);
unsigned char *p = NULL; if (!gpr)
if (XGetWindowProperty(gen.dpy, root, gen.net_active_window,
0L, 1L, false, XA_WINDOW, &dummy_type, &dummy_format,
&nitems, &dummy_bytes, &p) != Success)
return; 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; char *new_title = NULL;
if (nitems) { if (xcb_get_property_value_length(gpr)) {
Window active_window = *(Window *) p; xcb_window_t active_window =
XFree(p); *(xcb_window_t *) xcb_get_property_value(gpr);
const uint32_t disable[] = { 0 };
if (gen.current_window != active_window && gen.current_window) 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); new_title = x_window_title(active_window);
gen.current_window = active_window; gen.current_window = active_window;
} }
free(gpr);
if (update_window_title(new_title)) { if (update_window_title(new_title)) {
printf("Window changed: %s\n", printf("Window changed: %s\n",
gen.current_title ? gen.current_title : "(none)"); gen.current_title ? gen.current_title : "(none)");
@ -235,19 +317,13 @@ update_current_window(void)
} }
static 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 // 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(); update_current_window();
} else if (ev->window == gen.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))) { if (update_window_title(x_window_title(ev->window))) {
printf("Title changed: %s\n", gen.current_title); printf("Title changed: %s\n", gen.current_title);
push_event(); push_event();
@ -256,67 +332,83 @@ on_x_property_notify(XPropertyEvent *ev)
} }
static void 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; // TODO: consider xcb_sync_{change,create}_alarm_aux()
attr.trigger.counter = gen.idle_counter; uint32_t values[] = {
attr.trigger.test_type = test; gen.idle_counter,
attr.trigger.wait_value = value; value.hi, value.lo,
XSyncIntToValue(&attr.delta, 0); test,
0, 0,
};
long flags = XSyncCACounter | XSyncCATestType | XSyncCAValue | XSyncCADelta; xcb_sync_ca_t flags = XCB_SYNC_CA_COUNTER | XCB_SYNC_CA_VALUE |
if (*alarm) XCB_SYNC_CA_TEST_TYPE | XCB_SYNC_CA_DELTA;
XSyncChangeAlarm(gen.dpy, *alarm, flags, &attr); if (*alarm) {
else xcb_sync_change_alarm(gen.X, *alarm, flags, values);
*alarm = XSyncCreateAlarm(gen.dpy, flags, &attr); } else {
*alarm = xcb_generate_id(gen.X);
xcb_sync_create_alarm(gen.X, *alarm, flags, values);
}
} }
static void 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) { if (ev->alarm == gen.idle_alarm_inactive) {
printf("User is inactive\n"); printf("User is inactive\n");
gen.current_idle = true; gen.current_idle = true;
push_event(); push_event();
XSyncValue one, minus_one; xcb_sync_int64_t minus_one = ev->counter_value;
XSyncIntToValue(&one, 1); if (!~(--minus_one.lo))
minus_one.hi--;
Bool overflow;
XSyncValueSubtract(&minus_one, ev->counter_value, one, &overflow);
// Set an alarm for IDLETIME <= current_idletime - 1 // Set an alarm for IDLETIME <= current_idletime - 1
set_idle_alarm(&gen.idle_alarm_active, 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) { } else if (ev->alarm == gen.idle_alarm_active) {
printf("User is active\n"); printf("User is active\n");
gen.current_idle = false; gen.current_idle = false;
push_event(); push_event();
set_idle_alarm(&gen.idle_alarm_inactive, 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 static gboolean
on_x_ready(G_GNUC_UNUSED gpointer user_data) on_x_ready(G_GNUC_UNUSED gpointer user_data)
{ {
XEvent ev; xcb_generic_event_t *event;
while (XPending(gen.dpy)) { while ((event = xcb_poll_for_event(gen.X))) {
if (XNextEvent(gen.dpy, &ev)) process_x11_event(event);
exit_fatal("XNextEvent returned non-zero"); free(event);
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);
} }
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 static void
generator_thread(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); GSource *watch = g_io_create_watch(channel, G_IO_IN);
g_source_set_callback(watch, on_x_ready, NULL, NULL); g_source_set_callback(watch, on_x_ready, NULL, NULL);
@ -329,34 +421,50 @@ generator_thread(void)
static void static void
generator_init(void) generator_init(void)
{ {
if (!XSupportsLocale()) int which_screen = -1, xcb_error;
exit_fatal("locale not supported by Xlib"); 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.atom_net_active_window = intern_atom("_NET_ACTIVE_WINDOW")) ||
if (!(gen.dpy = XOpenDisplay(NULL))) !(gen.atom_net_wm_name = intern_atom("_NET_WM_NAME")) ||
exit_fatal("cannot open display"); !(gen.atom_utf8_string = intern_atom("UTF8_STRING")) ||
!(gen.atom_compound_text = intern_atom("COMPOUND_TEXT")))
gen.net_active_window = XInternAtom(gen.dpy, "_NET_ACTIVE_WINDOW", true); exit_fatal("unable to resolve atoms");
gen.net_wm_name = XInternAtom(gen.dpy, "_NET_WM_NAME", true);
// TODO: it is possible to employ a fallback mechanism via XScreenSaver // TODO: it is possible to employ a fallback mechanism via XScreenSaver
// by polling the XScreenSaverInfo::idle field, see // by polling the XScreenSaverInfo::idle field, see
// https://www.x.org/releases/X11R7.5/doc/man/man3/Xss.3.html // https://www.x.org/releases/X11R7.5/doc/man/man3/Xss.3.html
int dummy; gen.sync = xcb_get_extension_data(gen.X, &xcb_sync_id);
if (!XSyncQueryExtension(gen.dpy, &gen.xsync_base_event_code, &dummy) if (!gen.sync->present)
|| !XSyncInitialize(gen.dpy, &dummy, &dummy)) exit_fatal("missing Sync extension");
exit_fatal("cannot initialize XSync");
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 // The idle counter is not guaranteed to exist, only SERVERTIME is
if (!(gen.idle_counter = get_counter("IDLETIME"))) if (!(gen.idle_counter = get_counter("IDLETIME")))
exit_fatal("idle counter is missing"); exit_fatal("idle counter is missing");
Window root = DefaultRootWindow(gen.dpy); const xcb_setup_t *setup = xcb_get_setup(gen.X);
XSelectInput(gen.dpy, root, PropertyChangeMask); xcb_screen_iterator_t setup_iter = xcb_setup_roots_iterator(setup);
XSync(gen.dpy, False); while (which_screen--)
// TODO: what is the interaction with GTK+ here? xcb_screen_next(&setup_iter);
g_default_x_error_handler = XSetErrorHandler(on_x_error);
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(); GKeyFile *kf = g_key_file_new();
gchar *subpath = g_build_filename(PROJECT_NAME, PROJECT_NAME ".conf", NULL); gchar *subpath = g_build_filename(PROJECT_NAME, PROJECT_NAME ".conf", NULL);
@ -374,10 +482,13 @@ generator_init(void)
g_free(subpath); g_free(subpath);
g_key_file_free(kf); 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(); update_current_window();
set_idle_alarm(&gen.idle_alarm_inactive, set_idle_alarm(&gen.idle_alarm_inactive,
XSyncPositiveComparison, gen.idle_timeout); XCB_SYNC_TESTTYPE_POSITIVE_COMPARISON, gen.idle_timeout);
} }
static void static void
@ -392,7 +503,7 @@ generator_cleanup(void)
{ {
g_thread_join(gen.thread); g_thread_join(gen.thread);
free(gen.current_title); free(gen.current_title);
XCloseDisplay(gen.dpy); xcb_disconnect(gen.X);
} }
// --- Main -------------------------------------------------------------------- // --- Main --------------------------------------------------------------------

View File

@ -1 +0,0 @@
// https://github.com/mesonbuild/meson/issues/1195