/* * Copyright © 2025 Pierre Le Marre * SPDX-License-Identifier: MIT */ #include "config.h" #include "test-config.h" #include "test.h" #ifdef _WIN32 int main(void) { test_init(); return SKIP_TEST; } #else #include #include #include #include #include #include "xkbcommon/xkbcommon-keysyms.h" #include "xkbcommon/xkbcommon.h" #include "utils.h" #include "src/keysym.h" static void test_unicode_keysyms_consistency(uint32_t start, uint32_t end) { static_assert(XKB_KEYSYM_NAME_MAX_SIZE > XKB_KEYSYM_UTF8_MAX_SIZE, "Buffer too small"); char buffer[XKB_KEYSYM_NAME_MAX_SIZE] = {0}; char utf8[XKB_KEYSYM_UTF8_MAX_SIZE] = {0}; for (uint32_t cp = start; cp <= end; cp++) { xkb_keysym_t unicode = XKB_KEYSYM_UNICODE_OFFSET + cp; xkb_keysym_t canonical = xkb_utf32_to_keysym(cp); int count = xkb_keysym_get_name(unicode, buffer, sizeof(buffer)); assert(count > 0); if (cp == 0 || is_surrogate(cp)) { /* * Invalid code points */ /* No conversion from code point */ assert(canonical == XKB_KEY_NoSymbol); /* No conversion to code point */ assert(xkb_keysym_to_utf32(unicode) == 0); char *end_ptr = NULL; if (cp == 0) { /* Corresponding name hexadecimal format, unchanged */ assert(strtoull(buffer, &end_ptr, 16) == unicode); } else { /* Unicode notation */ assert(count == 5 && buffer[0] == 'U'); assert(strtoull(buffer + 1, &end_ptr, 16) == cp); } assert(*end_ptr == '\0'); /* Roundtrip hexadecimal name */ xkb_keysym_t ks = xkb_keysym_from_name(buffer, XKB_KEYSYM_NO_FLAGS); assert(ks == unicode); /* Check Unicode format */ snprintf(buffer, sizeof(buffer), "U%"PRIX32, cp); ks = xkb_keysym_from_name(buffer, XKB_KEYSYM_NO_FLAGS); assert((cp == 0 && ks == XKB_KEY_NoSymbol) ^ (is_surrogate(cp) && ks == unicode)); /* Cannot convert to UTF-8 */ count = xkb_keysym_to_utf8(unicode, buffer, sizeof(buffer)); assert(count == 0); } else { /* * Valid code points */ /* Canonical keysym may be different but is set */ assert_printf((canonical == unicode) ^ ((0x20 <= cp && cp <= 0x100 && cp != 0x7f && canonical == cp) || (canonical != unicode && canonical != XKB_KEY_NoSymbol && (canonical != cp || canonical == XKB_KEY_EuroSign))), "Code point: U+%04"PRIX32", Unicode: %#"PRIx32", " "canonical: %#"PRIx32"\n", cp, unicode, canonical); /* Conversion to code point has the same expected result */ assert(xkb_keysym_to_utf32(unicode) == cp); assert(xkb_keysym_to_utf32(canonical) == cp); /* roundtrip */ /* Check name roundtrip */ xkb_keysym_t ks = xkb_keysym_from_name(buffer, XKB_KEYSYM_NO_FLAGS); assert((unicode != canonical && ks == canonical) ^ (ks == unicode)); /* Can use Unicode format */ if (buffer[0] == 'U' && count > 4 && is_digit(buffer[1])) { /* Name already a Unicode notation: skip. * Note that the heuristic may fail to detect a Unicode notation, * but it is only meant to make the overall test faster. */ } else { snprintf(buffer, sizeof(buffer), "U%"PRIX32, cp); ks = xkb_keysym_from_name(buffer, XKB_KEYSYM_NO_FLAGS); assert((unicode != canonical && ks == canonical) ^ (ks == unicode)); } /* Roundtrip: numeric hexadecimal format for Unicode keysym */ assert(snprintf(buffer, sizeof(buffer), "%#"PRIx32, unicode) == 9); ks = xkb_keysym_from_name(buffer, XKB_KEYSYM_NO_FLAGS); assert(ks == unicode); /* Can convert to UTF-8 (Unicode keysym) */ count = xkb_keysym_to_utf8(unicode, buffer, sizeof(buffer)); assert(count > 0); if (canonical != unicode) { /* Canonical keysym convert to same UTF-8 */ const int count2 = xkb_keysym_to_utf8(canonical, utf8, sizeof(utf8)); assert(count2 == count); assert(strcmp(buffer, utf8) == 0); /* Roundtrip: numeric hexadecimal format for canonical keysym */ count = xkb_keysym_get_name(canonical, buffer, sizeof(buffer)); assert(count > 0); assert(xkb_keysym_from_name(buffer, XKB_KEYSYM_NO_FLAGS) == canonical); assert(snprintf(buffer, sizeof(buffer), "%#"PRIx32, canonical) > 2); assert(xkb_keysym_from_name(buffer, XKB_KEYSYM_NO_FLAGS) == canonical); } } } } int main(int argc, char *argv[]) { test_init(); unsigned long int NUM_PROCESSES = 0; if (argc > 1) { NUM_PROCESSES = strtoul(argv[1], NULL, 10); } if (NUM_PROCESSES == 0 || NUM_PROCESSES > 32) NUM_PROCESSES = 4; const uint32_t max_codepoint = 0x10ffff; const uint32_t chunk = max_codepoint / NUM_PROCESSES; pid_t pids[NUM_PROCESSES]; for (unsigned long int i = 0; i < NUM_PROCESSES; i++) { pid_t pid = fork(); if (pid == 0) { /* Child */ const uint32_t start = (uint32_t)i * chunk; const uint32_t end = (i == NUM_PROCESSES - 1) ? max_codepoint : start + chunk; test_unicode_keysyms_consistency(start, end); exit(EXIT_SUCCESS); } else if (pid > 0) { /* Parent */ pids[i] = pid; } else { perror("fork"); return TEST_SETUP_FAILURE; } } /* Wait for children */ int exit_code = EXIT_SUCCESS; for (unsigned long int i = 0; i < NUM_PROCESSES; i++) { int status; if (waitpid(pids[i], &status, 0) == -1) { perror("waitpid"); exit_code = EXIT_FAILURE; } else if (WIFEXITED(status)) { int code = WEXITSTATUS(status); if (code != EXIT_SUCCESS) { fprintf(stderr, "Child PID %d exited with code %d\n", pids[i], code); exit_code = EXIT_FAILURE; } } else if (WIFSIGNALED(status)) { fprintf(stderr, "Child PID %d terminated by signal %d\n", pids[i], WTERMSIG(status)); exit_code = EXIT_FAILURE; } } return exit_code; } #endif