From 843ab552234a4c3d2b6e617d480e98861191eb1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Moreau?= Date: Sun, 8 Jan 2012 20:32:50 -0500 Subject: [PATCH] xfreerdp-server: add server-side keyboard mapping --- include/freerdp/kbd/kbd.h | 1 + libfreerdp-kbd/layouts.c | 31 +++++++++--------- libfreerdp-kbd/layouts_xkb.c | 61 +++++++++++++++++++----------------- libfreerdp-kbd/layouts_xkb.h | 20 ++++-------- libfreerdp-kbd/libkbd.c | 54 +++++++++++++++++++++---------- libfreerdp-kbd/locales.c | 29 ++++++++--------- server/X11/CMakeLists.txt | 1 + server/X11/xf_peer.c | 39 ++++++++++++++--------- 8 files changed, 131 insertions(+), 105 deletions(-) diff --git a/include/freerdp/kbd/kbd.h b/include/freerdp/kbd/kbd.h index c4d71d68f..82d102438 100644 --- a/include/freerdp/kbd/kbd.h +++ b/include/freerdp/kbd/kbd.h @@ -36,6 +36,7 @@ typedef struct rdp_keyboard_layout FREERDP_API rdpKeyboardLayout* freerdp_kbd_get_layouts(int types); FREERDP_API uint32 freerdp_kbd_init(void *dpy, uint32 keyboard_layout_id); FREERDP_API uint8 freerdp_kbd_get_scancode_by_keycode(uint8 keycode, boolean* extended); +FREERDP_API uint8 freerdp_kbd_get_keycode_by_scancode(uint8 scancode, boolean extended); FREERDP_API uint8 freerdp_kbd_get_scancode_by_virtualkey(int vkcode, boolean* extended); #endif /* __FREERDP_KBD_H */ diff --git a/libfreerdp-kbd/layouts.c b/libfreerdp-kbd/layouts.c index 55c1c52d1..1f8a5ae20 100644 --- a/libfreerdp-kbd/layouts.c +++ b/libfreerdp-kbd/layouts.c @@ -231,10 +231,8 @@ static const keyboardIME keyboardIMEs[] = rdpKeyboardLayout* get_keyboard_layouts(int types) { + int num, len, i; rdpKeyboardLayout * layouts; - int num; - int len; - int i; num = 0; layouts = (rdpKeyboardLayout *) malloc((num + 1) * sizeof(rdpKeyboardLayout)); @@ -263,6 +261,7 @@ rdpKeyboardLayout* get_keyboard_layouts(int types) { len = sizeof(keyboardIMEs) / sizeof(keyboardIME); layouts = (rdpKeyboardLayout *) realloc(layouts, (num + len + 1) * sizeof(rdpKeyboardLayout)); + for (i = 0; i < len; i++, num++) { layouts[num].code = keyboardIMEs[i].code; @@ -278,22 +277,24 @@ rdpKeyboardLayout* get_keyboard_layouts(int types) const char* get_layout_name(unsigned int keyboardLayoutID) { int i; - for(i = 0; i < sizeof(keyboardLayouts) / sizeof(keyboardLayout); i++) - if(keyboardLayouts[i].code == keyboardLayoutID) - { + + for (i = 0; i < sizeof(keyboardLayouts) / sizeof(keyboardLayout); i++) + { + if (keyboardLayouts[i].code == keyboardLayoutID) return keyboardLayouts[i].name; - } + } - for(i = 0; i < sizeof(keyboardLayoutVariants) / sizeof(keyboardLayoutVariant); i++) - if(keyboardLayoutVariants[i].code == keyboardLayoutID) - { + for (i = 0; i < sizeof(keyboardLayoutVariants) / sizeof(keyboardLayoutVariant); i++) + { + if (keyboardLayoutVariants[i].code == keyboardLayoutID) return keyboardLayoutVariants[i].name; - } + } - for(i = 0; i < sizeof(keyboardIMEs) / sizeof(keyboardIME); i++) - if(keyboardIMEs[i].code == keyboardLayoutID) - { + for (i = 0; i < sizeof(keyboardIMEs) / sizeof(keyboardIME); i++) + { + if (keyboardIMEs[i].code == keyboardLayoutID) return keyboardIMEs[i].name; - } + } + return "unknown"; } diff --git a/libfreerdp-kbd/layouts_xkb.c b/libfreerdp-kbd/layouts_xkb.c index b9ad07f49..dc0896ba8 100644 --- a/libfreerdp-kbd/layouts_xkb.c +++ b/libfreerdp-kbd/layouts_xkb.c @@ -106,7 +106,7 @@ unsigned int detect_keyboard_layout_from_xkb(void* dpy) return keyboard_layout; } -int init_keycodes_from_xkb(void *dpy, RdpKeycodes x_keycode_to_rdp_keycode) +int init_keycodes_from_xkb(void* dpy, RdpScancodes x_keycode_to_rdp_scancode, uint8 rdp_scancode_to_x_keycode[256][2]) { int ret = 0; XkbDescPtr xkb; @@ -115,35 +115,41 @@ int init_keycodes_from_xkb(void *dpy, RdpKeycodes x_keycode_to_rdp_keycode) { if (XkbGetNames(dpy, XkbKeyNamesMask, xkb) == Success) { - char buf[5] = {42, 42, 42, 42, 0}; /* end-of-string at pos 5 */ int i, j; + char buf[5] = {42, 42, 42, 42, 0}; /* end-of-string at pos 5 */ - memset(x_keycode_to_rdp_keycode, 0, sizeof(x_keycode_to_rdp_keycode)); for (i = xkb->min_key_code; i <= xkb->max_key_code; i++) { memcpy(buf, xkb->names->keys[i].name, 4); /* TODO: Use more efficient search ... but it is so fast that it doesn't matter */ j = sizeof(virtualKeyboard) / sizeof(virtualKeyboard[0]) - 1; + while (j >= 0) { - if (virtualKeyboard[j].x_keyname && - !strcmp(buf, virtualKeyboard[j].x_keyname)) + if (virtualKeyboard[j].x_keyname && !strcmp(buf, virtualKeyboard[j].x_keyname)) break; j--; } + if (j >= 0) { - DEBUG_KBD("X key code %3d has keyname %-4s -> RDP keycode %d/%d", + DEBUG_KBD("X keycode %3d has keyname %-4s -> RDP scancode %d/%d", i, buf, virtualKeyboard[j].extended, virtualKeyboard[j].scancode); - x_keycode_to_rdp_keycode[i].extended = virtualKeyboard[j].extended; - x_keycode_to_rdp_keycode[i].keycode = virtualKeyboard[j].scancode; -#ifdef WITH_DEBUG_KBD - x_keycode_to_rdp_keycode[i].keyname = virtualKeyboard[j].x_keyname; -#endif + + x_keycode_to_rdp_scancode[i].extended = virtualKeyboard[j].extended; + x_keycode_to_rdp_scancode[i].keycode = virtualKeyboard[j].scancode; + x_keycode_to_rdp_scancode[i].keyname = virtualKeyboard[j].x_keyname; + + if (x_keycode_to_rdp_scancode[i].extended) + rdp_scancode_to_x_keycode[virtualKeyboard[j].scancode][1] = i; + else + rdp_scancode_to_x_keycode[virtualKeyboard[j].scancode][0] = i; } else + { DEBUG_KBD("X key code %3d has keyname %-4s -> ??? - not found", i, buf); + } } ret = 1; } @@ -196,14 +202,13 @@ static int load_xkb_keyboard(KeycodeToVkcode map, char* kbd) beg = kbd; - /* Extract file name and keymap name */ if ((end = strrchr(kbd, '(')) != NULL) { strncpy(xkbfile, &kbd[beg - kbd], end - beg); beg = end + 1; - if((end = strrchr(kbd, ')')) != NULL) + if ((end = strrchr(kbd, ')')) != NULL) { strncpy(xkbmap, &kbd[beg - kbd], end - beg); xkbmap[end - beg] = '\0'; @@ -228,36 +233,36 @@ static int load_xkb_keyboard(KeycodeToVkcode map, char* kbd) * when it is for reading only. */ - if((fp = fopen(xkbfilepath, "r")) == NULL) + if ((fp = fopen(xkbfilepath, "r")) == NULL) { /* Look first in path given at compile time (install path) */ snprintf(xkbfilepath, sizeof(xkbfilepath), "%s/%s", KEYMAP_PATH, xkbfile); - if((fp = fopen(xkbfilepath, "r")) == NULL) + if ((fp = fopen(xkbfilepath, "r")) == NULL) { /* If ran from the source tree, the keymaps will be in the parent directory */ snprintf(xkbfilepath, sizeof(xkbfilepath), "../keymaps/%s", xkbfile); - if((fp = fopen(xkbfilepath, "r")) == NULL) + if ((fp = fopen(xkbfilepath, "r")) == NULL) { /* File wasn't found in the source tree, try ~/.freerdp/ folder */ - if((home = getenv("HOME")) == NULL) + if ((home = getenv("HOME")) == NULL) return 0; /* Get path to file in ~/.freerdp/ folder */ snprintf(xkbfilepath, sizeof(xkbfilepath), "%s/.freerdp/keymaps/%s", home, xkbfile); - if((fp = fopen(xkbfilepath, "r")) == NULL) + if ((fp = fopen(xkbfilepath, "r")) == NULL) { /* Try /usr/share/freerdp folder */ snprintf(xkbfilepath, sizeof(xkbfilepath), "/usr/share/freerdp/keymaps/%s", xkbfile); - if((fp = fopen(xkbfilepath, "r")) == NULL) + if ((fp = fopen(xkbfilepath, "r")) == NULL) { /* Try /usr/local/share/freerdp folder */ snprintf(xkbfilepath, sizeof(xkbfilepath), "/usr/local/share/freerdp/keymaps/%s", xkbfile); - if((fp = fopen(xkbfilepath, "r")) == NULL) + if ((fp = fopen(xkbfilepath, "r")) == NULL) { /* Error: Could not find keymap */ DEBUG_KBD("keymaps for %s not found", xkbfile); @@ -273,12 +278,12 @@ static int load_xkb_keyboard(KeycodeToVkcode map, char* kbd) while(fgets(buffer, sizeof(buffer), fp) != NULL) { - if(buffer[0] == '#') + if (buffer[0] == '#') { continue; /* Skip comments */ } - if(kbdFound) + if (kbdFound) { /* Closing curly bracket and semicolon */ if ((pch = strstr(buffer, "};")) != NULL) @@ -296,12 +301,12 @@ static int load_xkb_keyboard(KeycodeToVkcode map, char* kbd) vkcodeName[end - beg] = '\0'; /* Now we want to extract the virtual key code itself which is in between '<' and '>' */ - if((beg = strchr(pch + 3, '<')) == NULL) + if ((beg = strchr(pch + 3, '<')) == NULL) break; else beg++; - if((end = strchr(beg, '>')) == NULL) + if ((end = strchr(beg, '>')) == NULL) break; /* We copy the string representing the number in a string */ @@ -312,13 +317,13 @@ static int load_xkb_keyboard(KeycodeToVkcode map, char* kbd) keycode = atoi(keycodeString); /* Make sure it is a valid keycode */ - if(keycode < 0 || keycode > 255) + if (keycode < 0 || keycode > 255) break; /* Load this key mapping in the keyboard mapping */ for(i = 0; i < sizeof(virtualKeyboard) / sizeof(virtualKey); i++) { - if(strcmp(vkcodeName, virtualKeyboard[i].name) == 0) + if (strcmp(vkcodeName, virtualKeyboard[i].name) == 0) { map[keycode] = i; } @@ -351,14 +356,14 @@ static int load_xkb_keyboard(KeycodeToVkcode map, char* kbd) break; beg++; - if((end = strchr(beg, '"')) == NULL) + if ((end = strchr(beg, '"')) == NULL) break; pch = beg; buffer[end - beg] = '\0'; /* Does it match our keymap name? */ - if(strncmp(xkbmap, pch, strlen(xkbmap)) == 0) + if (strncmp(xkbmap, pch, strlen(xkbmap)) == 0) kbdFound = 1; } } diff --git a/libfreerdp-kbd/layouts_xkb.h b/libfreerdp-kbd/layouts_xkb.h index d714e62ab..dc0c7697e 100644 --- a/libfreerdp-kbd/layouts_xkb.h +++ b/libfreerdp-kbd/layouts_xkb.h @@ -26,26 +26,18 @@ typedef struct { unsigned char extended; unsigned char keycode; -#ifdef WITH_DEBUG_KBD - char *keyname; -#endif -} RdpKeycodeRec, RdpKeycodes[256]; + char* keyname; +} RdpKeycodeRec, RdpScancodes[256]; #ifdef WITH_XKBFILE -int -init_xkb(void *dpy); - -unsigned int -detect_keyboard_layout_from_xkb(void *dpy); - -int -init_keycodes_from_xkb(void *dpy, RdpKeycodes x_keycode_to_rdp_keycode); +int init_xkb(void *dpy); +unsigned int detect_keyboard_layout_from_xkb(void *dpy); +int init_keycodes_from_xkb(void* dpy, RdpScancodes x_keycode_to_rdp_scancode, uint8 rdp_scancode_to_x_keycode[256][2]); #else -void -load_keyboard_map(KeycodeToVkcode keycodeToVkcode, char *xkbfile); +void load_keyboard_map(KeycodeToVkcode keycodeToVkcode, char *xkbfile); #endif diff --git a/libfreerdp-kbd/libkbd.c b/libfreerdp-kbd/libkbd.c index 956445580..156b30fdb 100644 --- a/libfreerdp-kbd/libkbd.c +++ b/libfreerdp-kbd/libkbd.c @@ -36,7 +36,9 @@ * but it only depends on which keycodes the X servers keyboard driver uses and is thus very static. */ -RdpKeycodes x_keycode_to_rdp_keycode; +RdpScancodes x_keycode_to_rdp_scancode; + +uint8 rdp_scancode_to_x_keycode[256][2]; #ifndef WITH_XKBFILE @@ -48,14 +50,14 @@ static unsigned int detect_keyboard(void* dpy, unsigned int keyboardLayoutID, ch DEBUG_KBD("keyboard layout configuration: %X", keyboardLayoutID); #if defined(sun) - if(keyboardLayoutID == 0) + if (keyboardLayoutID == 0) { keyboardLayoutID = detect_keyboard_type_and_layout_sunos(xkbfile, xkbfilelength); DEBUG_KBD("detect_keyboard_type_and_layout_sunos: %X %s", keyboardLayoutID, xkbfile); } #endif - if(keyboardLayoutID == 0) + if (keyboardLayoutID == 0) { keyboardLayoutID = detect_keyboard_layout_from_locale(); DEBUG_KBD("detect_keyboard_layout_from_locale: %X", keyboardLayoutID); @@ -85,6 +87,9 @@ static unsigned int detect_keyboard(void* dpy, unsigned int keyboardLayoutID, ch unsigned int freerdp_kbd_init(void* dpy, unsigned int keyboard_layout_id) { + memset(x_keycode_to_rdp_scancode, 0, sizeof(x_keycode_to_rdp_scancode)); + memset(rdp_scancode_to_x_keycode, '\0', sizeof(rdp_scancode_to_x_keycode)); + #ifdef WITH_XKBFILE if (!init_xkb(dpy)) { @@ -96,11 +101,12 @@ unsigned int freerdp_kbd_init(void* dpy, unsigned int keyboard_layout_id) keyboard_layout_id = detect_keyboard_layout_from_xkb(dpy); DEBUG_KBD("detect_keyboard_layout_from_xkb: %X", keyboard_layout_id); } - init_keycodes_from_xkb(dpy, x_keycode_to_rdp_keycode); + init_keycodes_from_xkb(dpy, x_keycode_to_rdp_scancode, rdp_scancode_to_x_keycode); #else + int vkcode; + int keycode; char xkbfile[256]; KeycodeToVkcode keycodeToVkcode; - int keycode; if (keyboard_layout_id == 0) keyboard_layout_id = detect_keyboard(dpy, keyboard_layout_id, xkbfile, sizeof(xkbfile)); @@ -110,18 +116,22 @@ unsigned int freerdp_kbd_init(void* dpy, unsigned int keyboard_layout_id) load_keyboard_map(keycodeToVkcode, xkbfile); - for (keycode=0; keycode<256; keycode++) + for (keycode = 0; keycode < 256; keycode++) { - int vkcode; vkcode = keycodeToVkcode[keycode]; - DEBUG_KBD("X key code %3d VK %3d %-19s-> RDP keycode %d/%d", + + DEBUG_KBD("X keycode %3d VK %3d %-19s-> RDP scancode %d/%d", keycode, vkcode, virtualKeyboard[vkcode].name, virtualKeyboard[vkcode].extended, virtualKeyboard[vkcode].scancode); - x_keycode_to_rdp_keycode[keycode].keycode = virtualKeyboard[vkcode].scancode; - x_keycode_to_rdp_keycode[keycode].extended = virtualKeyboard[vkcode].extended; -#ifdef WITH_DEBUG_KBD - x_keycode_to_rdp_keycode[keycode].keyname = virtualKeyboard[vkcode].name; -#endif + + x_keycode_to_rdp_scancode[keycode].keycode = virtualKeyboard[vkcode].scancode; + x_keycode_to_rdp_scancode[keycode].extended = virtualKeyboard[vkcode].extended; + x_keycode_to_rdp_scancode[keycode].keyname = virtualKeyboard[vkcode].name; + + if (x_keycode_to_rdp_scancode[keycode].extended) + rdp_scancode_to_x_keycode[virtualKeyboard[vkcode].scancode][1] = keycode; + else + rdp_scancode_to_x_keycode[virtualKeyboard[vkcode].scancode][0] = keycode; } #endif @@ -135,10 +145,20 @@ rdpKeyboardLayout* freerdp_kbd_get_layouts(int types) uint8 freerdp_kbd_get_scancode_by_keycode(uint8 keycode, boolean* extended) { - DEBUG_KBD("%2x %4s -> %d/%d", keycode, x_keycode_to_rdp_keycode[keycode].keyname, - x_keycode_to_rdp_keycode[keycode].extended, x_keycode_to_rdp_keycode[keycode].keycode); - *extended = x_keycode_to_rdp_keycode[keycode].extended; - return x_keycode_to_rdp_keycode[keycode].keycode; + DEBUG_KBD("%2x %4s -> %d/%d", keycode, x_keycode_to_rdp_scancode[keycode].keyname, + x_keycode_to_rdp_scancode[keycode].extended, x_keycode_to_rdp_scancode[keycode].keycode); + + *extended = x_keycode_to_rdp_scancode[keycode].extended; + + return x_keycode_to_rdp_scancode[keycode].keycode; +} + +uint8 freerdp_kbd_get_keycode_by_scancode(uint8 scancode, boolean extended) +{ + if (extended) + return rdp_scancode_to_x_keycode[scancode][1]; + else + return rdp_scancode_to_x_keycode[scancode][0]; } uint8 freerdp_kbd_get_scancode_by_virtualkey(int vkcode, boolean* extended) diff --git a/libfreerdp-kbd/locales.c b/libfreerdp-kbd/locales.c index bfaa381c8..653b99014 100644 --- a/libfreerdp-kbd/locales.c +++ b/libfreerdp-kbd/locales.c @@ -423,24 +423,21 @@ static const localeAndKeyboardLayout defaultKeyboardLayouts[] = unsigned int detect_keyboard_layout_from_locale() { - int i; - int j; - int k; int dot; + int i, j, k; int underscore; - char language[4]; char country[10]; /* LANG = _. */ char* envLang = getenv("LANG"); /* Get locale from environment variable LANG */ - if(envLang == NULL) + if (envLang == NULL) return 0; /* LANG environment variable was not set */ underscore = strcspn(envLang, "_"); - if(underscore > 3) + if (underscore > 3) return 0; /* The language name should not be more than 3 letters long */ else { @@ -454,13 +451,13 @@ unsigned int detect_keyboard_layout_from_locale() * In this case, use a U.S. keyboard and a U.S. keyboard layout */ - if((strcmp(language, "C") == 0) || (strcmp(language, "POSIX") == 0)) + if ((strcmp(language, "C") == 0) || (strcmp(language, "POSIX") == 0)) return ENGLISH_UNITED_STATES; /* U.S. Keyboard Layout */ dot = strcspn(envLang, "."); /* Get country code */ - if(dot > underscore) + if (dot > underscore) { strncpy(country, &envLang[underscore + 1], dot - underscore - 1); country[dot - underscore - 1] = '\0'; @@ -468,26 +465,26 @@ unsigned int detect_keyboard_layout_from_locale() else return 0; /* Invalid locale */ - for(i = 0; i < sizeof(locales) / sizeof(locale); i++) + for (i = 0; i < sizeof(locales) / sizeof(locale); i++) { - if((strcmp(language, locales[i].language) == 0) && (strcmp(country, locales[i].country) == 0)) + if ((strcmp(language, locales[i].language) == 0) && (strcmp(country, locales[i].country) == 0)) break; } DEBUG_KBD("Found locale : %s_%s", locales[i].language, locales[i].country); - for(j = 0; j < sizeof(defaultKeyboardLayouts) / sizeof(localeAndKeyboardLayout); j++) + for (j = 0; j < sizeof(defaultKeyboardLayouts) / sizeof(localeAndKeyboardLayout); j++) { - if(defaultKeyboardLayouts[j].locale == locales[i].code) + if (defaultKeyboardLayouts[j].locale == locales[i].code) { /* Locale found in list of default keyboard layouts */ - for(k = 0; k < 5; k++) + for (k = 0; k < 5; k++) { - if(defaultKeyboardLayouts[j].keyboardLayouts[k] == ENGLISH_UNITED_STATES) + if (defaultKeyboardLayouts[j].keyboardLayouts[k] == ENGLISH_UNITED_STATES) { continue; /* Skip, try to get a more localized keyboard layout */ } - else if(defaultKeyboardLayouts[j].keyboardLayouts[k] == 0) + else if (defaultKeyboardLayouts[j].keyboardLayouts[k] == 0) { break; /* No more keyboard layouts */ } @@ -502,7 +499,7 @@ unsigned int detect_keyboard_layout_from_locale() * other possible keyboard layout for the locale, we end up here with k > 1 */ - if(k >= 1) + if (k >= 1) return ENGLISH_UNITED_STATES; else return 0; diff --git a/server/X11/CMakeLists.txt b/server/X11/CMakeLists.txt index 230df3922..e12a19f95 100644 --- a/server/X11/CMakeLists.txt +++ b/server/X11/CMakeLists.txt @@ -56,4 +56,5 @@ target_link_libraries(xfreerdp-server freerdp-core) target_link_libraries(xfreerdp-server freerdp-codec) target_link_libraries(xfreerdp-server freerdp-utils) target_link_libraries(xfreerdp-server freerdp-gdi) +target_link_libraries(xfreerdp-server freerdp-kbd) target_link_libraries(xfreerdp-server ${X11_LIBRARIES}) diff --git a/server/X11/xf_peer.c b/server/X11/xf_peer.c index 606444667..6bda04d36 100644 --- a/server/X11/xf_peer.c +++ b/server/X11/xf_peer.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -177,6 +178,8 @@ xfInfo* xf_info_init() xf_xdamage_init(xfi); #endif + freerdp_kbd_init(xfi->display, 0); + return xfi; } @@ -616,26 +619,32 @@ void xf_peer_synchronize_event(rdpInput* input, uint32 flags) void xf_peer_keyboard_event(rdpInput* input, uint16 flags, uint16 code) { - freerdp_peer* client = (freerdp_peer*) input->context->peer; - rdpUpdate* update = client->update; + unsigned int keycode; + boolean extended = false; xfPeerContext* xfp = (xfPeerContext*) input->context; + xfInfo* xfi = xfp->info; printf("Client sent a keyboard event (flags:0x%X code:0x%X)\n", flags, code); - if ((flags & 0x4000) && code == 0x1F) /* 's' key */ + if (flags & KBD_FLAGS_EXTENDED) + extended = true; + + keycode = freerdp_kbd_get_keycode_by_scancode(code, extended); + + printf("keycode: %d\n", keycode); + + if (keycode != 0) { - if (client->settings->width != 800) - { - client->settings->width = 800; - client->settings->height = 600; - } - else - { - client->settings->width = 640; - client->settings->height = 480; - } - update->DesktopResize(update->context); - xfp->activated = false; +#ifdef WITH_XTEST + pthread_mutex_lock(&(xfp->mutex)); + + if (flags & KBD_FLAGS_DOWN) + XTestFakeKeyEvent(xfi->display, keycode, True, 0); + else if (flags & KBD_FLAGS_RELEASE) + XTestFakeKeyEvent(xfi->display, keycode, False, 0); + + pthread_mutex_unlock(&(xfp->mutex)); +#endif } }