From a70cc2c9d1461991db6a5d29af7b91004c1184cb Mon Sep 17 00:00:00 2001 From: Jack Andersen Date: Fri, 8 May 2015 19:33:48 -1000 Subject: [PATCH] much work on XCB events --- include/windowsys/IWindow.hpp | 7 +- libBoo.pri | 6 +- libBoo.pro | 2 +- src/CApplicationWayland.hpp | 5 + src/CApplicationXCB.hpp | 110 ++++++++- src/windowsys/CWindowCocoa.mm | 4 +- src/windowsys/CWindowWayland.cpp | 5 + src/windowsys/CWindowXCB.cpp | 378 +++++++++++++++++++++++++++++-- 8 files changed, 487 insertions(+), 30 deletions(-) diff --git a/include/windowsys/IWindow.hpp b/include/windowsys/IWindow.hpp index 4cd70d8..d100bec 100644 --- a/include/windowsys/IWindow.hpp +++ b/include/windowsys/IWindow.hpp @@ -78,7 +78,7 @@ public: virtual void mouseMove(const SWindowCoord& coord) {(void)coord;} virtual void scroll(const SScrollDelta& scroll) - {(void)scroll;}; + {(void)scroll;} virtual void touchDown(const SWindowCoord& coord, uintptr_t tid) {(void)coord;(void)tid;} @@ -123,8 +123,9 @@ public: virtual bool isFullscreen() const=0; virtual void setFullscreen(bool fs)=0; - virtual void* getPlatformHandle() const=0; - + virtual uintptr_t getPlatformHandle() const=0; + virtual void _incomingEvent(void* event) {(void)event;} + enum ETouchType { TOUCH_NONE = 0, diff --git a/libBoo.pri b/libBoo.pri index 36a9714..102e20f 100644 --- a/libBoo.pri +++ b/libBoo.pri @@ -26,9 +26,11 @@ SOURCES += \ $$PWD/src/inputdev/SDeviceSignature.cpp unix:!macx { + HEADERS += \ + $$PWD/src/CApplicationXCB.hpp \ + $$PWD/src/CApplicationWayland.hpp SOURCES += \ - $$PWD/src/CApplicationXCB.cpp \ - $$PWD/src/CApplicationWayland.cpp \ + $$PWD/src/CApplicationUnix.cpp \ $$PWD/src/windowsys/CWindowXCB.cpp \ $$PWD/src/windowsys/CWindowWayland.cpp \ $$PWD/src/windowsys/CGraphicsContextXCB.cpp \ diff --git a/libBoo.pro b/libBoo.pro index f26f9eb..4611b89 100644 --- a/libBoo.pro +++ b/libBoo.pro @@ -3,7 +3,7 @@ CONFIG += console #QMAKE_CXXFLAGS -= -std=c++0x #CONFIG += c++11 unix:QMAKE_CXXFLAGS += -std=c++11 -stdlib=libc++ -unix:LIBS += -std=c++11 -stdlib=libc++ -lc++abi +unix:LIBS += -std=c++11 -stdlib=libc++ -lc++abi -lxcb -lxcb-xkb -lxcb-keysyms -lxkbcommon -lxkbcommon-x11 win32:LIBS += Setupapi.lib winusb.lib User32.lib /SUBSYSTEM:Windows diff --git a/src/CApplicationWayland.hpp b/src/CApplicationWayland.hpp index 06556c6..4b821e9 100644 --- a/src/CApplicationWayland.hpp +++ b/src/CApplicationWayland.hpp @@ -40,6 +40,11 @@ public: void run() { + } + + void quit() + { + } const std::string& getProcessName() const diff --git a/src/CApplicationXCB.hpp b/src/CApplicationXCB.hpp index 1b0a7f5..21a5e1a 100644 --- a/src/CApplicationXCB.hpp +++ b/src/CApplicationXCB.hpp @@ -4,10 +4,68 @@ #include "IApplication.hpp" +#define explicit explicit_c +#include +#include +#include +#undef explicit + namespace boo { + +static xcb_window_t getWindowOfEvent(xcb_generic_event_t* event, bool& windowEvent) +{ + switch (event->response_type & ~0x80) + { + case XCB_EXPOSE: + { + xcb_expose_event_t* ev = (xcb_expose_event_t*)event; + windowEvent = true; + return ev->window; + } + case XCB_CONFIGURE_NOTIFY: + { + xcb_configure_notify_event_t* ev = (xcb_configure_notify_event_t*)event; + windowEvent = true; + return ev->window; + } + case XCB_KEY_PRESS: + { + xcb_key_press_event_t* ev = (xcb_key_press_event_t*)event; + windowEvent = true; + return ev->root; + } + case XCB_KEY_RELEASE: + { + xcb_key_release_event_t* ev = (xcb_key_release_event_t*)event; + windowEvent = true; + return ev->root; + } + case XCB_BUTTON_PRESS: + { + xcb_button_press_event_t* ev = (xcb_button_press_event_t*)event; + windowEvent = true; + return ev->root; + } + case XCB_BUTTON_RELEASE: + { + xcb_button_release_event_t* ev = (xcb_button_release_event_t*)event; + windowEvent = true; + return ev->root; + } + case XCB_MOTION_NOTIFY: + { + xcb_motion_notify_event_t* ev = (xcb_motion_notify_event_t*)event; + windowEvent = true; + return ev->root; + } + default: + windowEvent = false; + return 0; + } +} -IWindow* _CWindowXCBNew(const std::string& title); +IWindow* _CWindowXCBNew(const std::string& title, xcb_connection_t* conn); class CApplicationXCB final : public IApplication { @@ -15,10 +73,16 @@ class CApplicationXCB final : public IApplication const std::string m_friendlyName; const std::string m_pname; const std::vector m_args; + + /* All windows */ + std::unordered_map m_windows; + + xcb_connection_t* m_xcbConn; + bool m_running; void _deletedWindow(IWindow* window) { - (void)window; + m_windows.erase((xcb_window_t)window->getPlatformHandle()); } public: @@ -30,7 +94,25 @@ public: m_friendlyName(friendlyName), m_pname(pname), m_args(args) - {} + { + m_xcbConn = xcb_connect(NULL, NULL); + + /* The convoluted xkb extension requests that the X server does not + * send repeated keydown events when a key is held */ + xkb_x11_setup_xkb_extension(m_xcbConn, + XKB_X11_MIN_MAJOR_XKB_VERSION, + XKB_X11_MIN_MINOR_XKB_VERSION, + XKB_X11_SETUP_XKB_EXTENSION_NO_FLAGS, + NULL, NULL, NULL, NULL); + xcb_xkb_per_client_flags(m_xcbConn, XCB_XKB_ID_USE_CORE_KBD, + XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT, + XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT, 0, 0, 0); + } + + ~CApplicationXCB() + { + xcb_disconnect(m_xcbConn); + } EPlatformType getPlatformType() const { @@ -39,7 +121,23 @@ public: void run() { - + xcb_generic_event_t* event; + while (m_running && (event = xcb_wait_for_event(m_xcbConn))) + { + bool windowEvent; + xcb_window_t evWindow = getWindowOfEvent(event, windowEvent); + if (windowEvent) + { + IWindow* window = m_windows[evWindow]; + + } + free(event); + } + } + + void quit() + { + m_running = false; } const std::string& getProcessName() const @@ -54,7 +152,9 @@ public: IWindow* newWindow(const std::string& title) { - return _CWindowXCBNew(title); + IWindow* newWindow = _CWindowXCBNew(title, m_xcbConn); + m_windows[(xcb_window_t)newWindow->getPlatformHandle()] = newWindow; + return newWindow; } }; diff --git a/src/windowsys/CWindowCocoa.mm b/src/windowsys/CWindowCocoa.mm index 42b1819..0d092ec 100644 --- a/src/windowsys/CWindowCocoa.mm +++ b/src/windowsys/CWindowCocoa.mm @@ -118,9 +118,9 @@ public: return TOUCH_TRACKPAD; } - void* getPlatformHandle() const + uintptr_t getPlatformHandle() const { - return m_nsWindow; + return (uintptr_t)m_nsWindow; } }; diff --git a/src/windowsys/CWindowWayland.cpp b/src/windowsys/CWindowWayland.cpp index ffbd2d4..c335d36 100644 --- a/src/windowsys/CWindowWayland.cpp +++ b/src/windowsys/CWindowWayland.cpp @@ -76,6 +76,11 @@ public: void setFullscreen(bool fs) { + } + + uintptr_t getPlatformHandle() const + { + } ETouchType getTouchType() const diff --git a/src/windowsys/CWindowXCB.cpp b/src/windowsys/CWindowXCB.cpp index f30fc69..1a07b47 100644 --- a/src/windowsys/CWindowXCB.cpp +++ b/src/windowsys/CWindowXCB.cpp @@ -1,8 +1,133 @@ #include "windowsys/IWindow.hpp" #include "windowsys/IGraphicsContext.hpp" +#include "IApplication.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#define XK_MISCELLANY +#define XK_XKB_KEYS +#define XK_LATIN1 +#include + +#define REF_DPMM 3.7824 /* 96 DPI */ +#define FS_ATOM "_NET_WM_STATE_FULLSCREEN" namespace boo { + +static uint32_t translateKeysym(xcb_keysym_t sym, int& specialSym, int& modifierSym) +{ + specialSym = IWindowCallback::KEY_NONE; + modifierSym = IWindowCallback::MKEY_NONE; + if (sym >= XK_F1 && sym <= XK_F12) + specialSym = IWindowCallback::KEY_F1 + sym - XK_F1; + else if (sym == XK_Escape) + specialSym = IWindowCallback::KEY_ESC; + else if (sym == XK_Return) + specialSym = IWindowCallback::KEY_ENTER; + else if (sym == XK_BackSpace) + specialSym = IWindowCallback::KEY_BACKSPACE; + else if (sym == XK_Insert) + specialSym = IWindowCallback::KEY_INSERT; + else if (sym == XK_Delete) + specialSym = IWindowCallback::KEY_DELETE; + else if (sym == XK_Home) + specialSym = IWindowCallback::KEY_HOME; + else if (sym == XK_End) + specialSym = IWindowCallback::KEY_END; + else if (sym == XK_Page_Up) + specialSym = IWindowCallback::KEY_PGUP; + else if (sym == XK_Page_Down) + specialSym = IWindowCallback::KEY_PGDOWN; + else if (sym == XK_Left) + specialSym = IWindowCallback::KEY_LEFT; + else if (sym == XK_Right) + specialSym = IWindowCallback::KEY_RIGHT; + else if (sym == XK_Up) + specialSym = IWindowCallback::KEY_UP; + else if (sym == XK_Down) + specialSym = IWindowCallback::KEY_DOWN; + else if (sym == XK_Shift_L || sym == XK_Shift_R) + modifierSym = IWindowCallback::MKEY_SHIFT; + else if (sym == XK_Control_L || sym == XK_Control_R) + modifierSym = IWindowCallback::MKEY_CTRL; + else if (sym == XK_Alt_L || sym == XK_Alt_R) + modifierSym = IWindowCallback::MKEY_ALT; + else + return xkb_keysym_to_utf32(sym); + return 0; +} + +static int translateModifiers(unsigned state) +{ + int retval = 0; + if (state & XCB_MOD_MASK_SHIFT) + retval |= IWindowCallback::MKEY_SHIFT; + if (state & XCB_MOD_MASK_CONTROL) + retval |= IWindowCallback::MKEY_CTRL; + if (state & XCB_MOD_MASK_1) + retval |= IWindowCallback::MKEY_ALT; + return retval; +} + +static int translateButton(unsigned state) +{ + int retval = 0; + if (state & XCB_BUTTON_MASK_1) + retval = IWindowCallback::BUTTON_PRIMARY; + else if (state & XCB_BUTTON_MASK_2) + retval = IWindowCallback::BUTTON_SECONDARY; + else if (state & XCB_BUTTON_MASK_3) + retval = IWindowCallback::BUTTON_MIDDLE; + else if (state & XCB_BUTTON_MASK_4) + retval = IWindowCallback::BUTTON_AUX1; + else if (state & XCB_BUTTON_MASK_5) + retval = IWindowCallback::BUTTON_AUX2; + return retval; +} + +#define INTERN_ATOM(var, conn, name) \ +do {\ + xcb_intern_atom_cookie_t cookie = \ + xcb_intern_atom(conn, 0, sizeof(#name), #name); \ + xcb_intern_atom_reply_t* reply = \ + xcb_intern_atom_reply(conn, cookie, NULL); \ + var = reply->atom; \ +} while(0) + +struct SXCBAtoms +{ + xcb_atom_t m_netwmState = 0; + xcb_atom_t m_netwmStateFullscreen = 0; + xcb_atom_t m_netwmStateAdd = 0; + xcb_atom_t m_netwmStateRemove = 0; + xcb_key_symbols_t* m_keySyms = NULL; + SXCBAtoms(xcb_connection_t* conn) + { + INTERN_ATOM(m_netwmState, conn, _NET_WM_STATE); + INTERN_ATOM(m_netwmStateFullscreen, conn, _NET_WM_STATE_FULLSCREEN); + INTERN_ATOM(m_netwmStateAdd, conn, _NET_WM_STATE_ADD); + INTERN_ATOM(m_netwmStateRemove, conn, _NET_WM_STATE_REMOVE); + m_keySyms = xcb_key_symbols_alloc(conn); + } +}; +static SXCBAtoms* S_ATOMS = NULL; + +static void genFrameDefault(xcb_screen_t* screen, int* xOut, int* yOut, int* wOut, int* hOut) +{ + float width = screen->width_in_pixels * 2.0 / 3.0; + float height = screen->height_in_pixels * 2.0 / 3.0; + *xOut = (screen->width_in_pixels - width) / 2.0; + *yOut = (screen->height_in_pixels - height) / 2.0; + *wOut = width; + *hOut = height; +} IGraphicsContext* _CGraphicsContextXCBNew(IGraphicsContext::EGraphicsAPI api, IWindow* parentWindow); @@ -10,84 +135,303 @@ IGraphicsContext* _CGraphicsContextXCBNew(IGraphicsContext::EGraphicsAPI api, class CWindowXCB final : public IWindow { + xcb_connection_t* m_xcbConn; + xcb_window_t m_windowId; + IGraphicsContext* m_gfxCtx; + IWindowCallback* m_callback; + + /* Cached window rectangle (to avoid repeated X queries) */ + int m_wx, m_wy, m_ww, m_wh; + float m_pixelFactor; public: - CWindowXCB(const std::string& title) + CWindowXCB(const std::string& title, xcb_connection_t* conn) + : m_xcbConn(conn), m_callback(NULL) { - + if (!S_ATOMS) + S_ATOMS = new SXCBAtoms(conn); + m_windowId = xcb_generate_id(conn); + + /* Default screen */ + xcb_screen_t* screen = xcb_setup_roots_iterator(xcb_get_setup(m_xcbConn)).data; + m_pixelFactor = screen->width_in_pixels / (float)screen->width_in_millimeters / REF_DPMM; + + /* Create window */ + int x, y, w, h; + genFrameDefault(screen, &x, &y, &w, &h); + uint32_t valueMasks[] = + { + XCB_NONE, + XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE | + XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE | + XCB_EVENT_MASK_POINTER_MOTION | XCB_EVENT_MASK_EXPOSURE | + XCB_EVENT_MASK_STRUCTURE_NOTIFY + }; + xcb_create_window(m_xcbConn, 0, m_windowId, 0, x, y, w, h, 10, 0, screen->root_visual, + XCB_CW_BORDER_PIXEL | XCB_CW_EVENT_MASK, valueMasks); + + /* Set the title of the window */ + const char* c_title = title.c_str(); + xcb_change_property(m_xcbConn, XCB_PROP_MODE_REPLACE, m_windowId, + XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, + strlen(c_title), c_title); + + /* Set the title of the window icon */ + xcb_change_property(m_xcbConn, XCB_PROP_MODE_REPLACE, m_windowId, + XCB_ATOM_WM_ICON_NAME, XCB_ATOM_STRING, 8, + strlen(c_title), c_title); + + /* Construct graphics context */ + m_gfxCtx = _CGraphicsContextXCBNew(IGraphicsContext::API_OPENGL_3_3, this); + } ~CWindowXCB() { - + IApplicationInstance()->_deletedWindow(this); } void setCallback(IWindowCallback* cb) { - + m_callback = cb; } void showWindow() { - + xcb_map_window(m_xcbConn, m_windowId); + xcb_flush(m_xcbConn); } void hideWindow() { - + xcb_unmap_window(m_xcbConn, m_windowId); + xcb_flush(m_xcbConn); } std::string getTitle() { - + xcb_get_property_cookie_t cookie = + xcb_get_property(m_xcbConn, 0, m_windowId, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 0, 64); + xcb_get_property_reply_t* reply = + xcb_get_property_reply(m_xcbConn, cookie, NULL); + std::string retval((const char*)xcb_get_property_value(reply)); + free(reply); + return retval; } void setTitle(const std::string& title) { - + const char* c_title = title.c_str(); + xcb_change_property(m_xcbConn, XCB_PROP_MODE_REPLACE, m_windowId, + XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, + strlen(c_title), c_title); } void setWindowFrameDefault() { - + int x, y, w, h; + xcb_screen_t* screen = xcb_setup_roots_iterator(xcb_get_setup(m_xcbConn)).data; + genFrameDefault(screen, &x, &y, &w, &h); + uint32_t values[] = {(uint32_t)x, (uint32_t)y, (uint32_t)w, (uint32_t)h}; + xcb_configure_window(m_xcbConn, m_windowId, + XCB_CONFIG_WINDOW_X | + XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT, + values); } void getWindowFrame(float& xOut, float& yOut, float& wOut, float& hOut) const { - + xOut = m_wx; + yOut = m_wy; + wOut = m_ww; + hOut = m_wh; } void setWindowFrame(float x, float y, float w, float h) { - + uint32_t values[] = {(uint32_t)x, (uint32_t)y, (uint32_t)w, (uint32_t)h}; + xcb_configure_window(m_xcbConn, m_windowId, + XCB_CONFIG_WINDOW_X | + XCB_CONFIG_WINDOW_Y | + XCB_CONFIG_WINDOW_WIDTH | + XCB_CONFIG_WINDOW_HEIGHT, + values); } float getVirtualPixelFactor() const { - + return m_pixelFactor; } bool isFullscreen() const { - + xcb_get_property_cookie_t cookie = + xcb_get_property(m_xcbConn, 0, m_windowId, S_ATOMS->m_netwmState, XCB_ATOM_ATOM, 0, 32); + xcb_get_property_reply_t* reply = + xcb_get_property_reply(m_xcbConn, cookie, NULL); + char* props = (char*)xcb_get_property_value(reply); + char fullscreen = false; + for (int i ; ilength/4 ; ++i) + { + if (props[i] == S_ATOMS->m_netwmStateFullscreen) + { + fullscreen = true; + break; + } + } + free(reply); + return fullscreen; } void setFullscreen(bool fs) { - + xcb_client_message_event_t fsEvent = + { + XCB_CLIENT_MESSAGE, + 32, + 0, + m_windowId, + S_ATOMS->m_netwmState + }; + fsEvent.data.data32[0] = fs ? S_ATOMS->m_netwmStateAdd : S_ATOMS->m_netwmStateRemove; + fsEvent.data.data32[1] = S_ATOMS->m_netwmStateFullscreen; + xcb_send_event(m_xcbConn, 0, m_windowId, + XCB_EVENT_MASK_STRUCTURE_NOTIFY | + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, + (const char*)&fsEvent); + + } + + uintptr_t getPlatformHandle() const + { + return (uintptr_t)m_windowId; + } + + void _incomingEvent(void* e) + { + xcb_generic_event_t* event = (xcb_generic_event_t*)e; + switch (event->response_type & ~0x80) + { + case XCB_EXPOSE: + { + xcb_expose_event_t* ev = (xcb_expose_event_t*)event; + m_wx = ev->x; + m_wy = ev->y; + m_ww = ev->width; + m_wh = ev->height; + } + case XCB_CONFIGURE_NOTIFY: + { + xcb_configure_notify_event_t* ev = (xcb_configure_notify_event_t*)event; + m_wx = ev->x; + m_wy = ev->y; + m_ww = ev->width; + m_wh = ev->height; + } + case XCB_KEY_PRESS: + { + xcb_key_press_event_t* ev = (xcb_key_press_event_t*)event; + if (m_callback) + { + int specialKey; + int modifierKey; + wchar_t charCode = translateKeysym(xcb_key_press_lookup_keysym(S_ATOMS->m_keySyms, ev, 0), + specialKey, modifierKey); + int modifierMask = translateModifiers(ev->state); + if (charCode) + m_callback->charKeyDown(charCode, + (IWindowCallback::EModifierKey)modifierMask, false); + else if (specialKey) + m_callback->specialKeyDown((IWindowCallback::ESpecialKey)specialKey, + (IWindowCallback::EModifierKey)modifierMask, false); + else if (modifierKey) + m_callback->modKeyDown((IWindowCallback::EModifierKey)modifierKey, false); + } + } + case XCB_KEY_RELEASE: + { + xcb_key_release_event_t* ev = (xcb_key_release_event_t*)event; + if (m_callback) + { + int specialKey; + int modifierKey; + wchar_t charCode = translateKeysym(xcb_key_release_lookup_keysym(S_ATOMS->m_keySyms, ev, 0), + specialKey, modifierKey); + int modifierMask = translateModifiers(ev->state); + if (charCode) + m_callback->charKeyUp(charCode, + (IWindowCallback::EModifierKey)modifierMask); + else if (specialKey) + m_callback->specialKeyUp((IWindowCallback::ESpecialKey)specialKey, + (IWindowCallback::EModifierKey)modifierMask); + else if (modifierKey) + m_callback->modKeyUp((IWindowCallback::EModifierKey)modifierKey); + } + } + case XCB_BUTTON_PRESS: + { + xcb_button_press_event_t* ev = (xcb_button_press_event_t*)event; + int button = translateButton(ev->state); + if (m_callback && button) + { + int modifierMask = translateModifiers(ev->state); + IWindowCallback::SWindowCoord coord = + { + {(unsigned)ev->root_x, (unsigned)ev->root_y}, + {(unsigned)(ev->root_x / m_pixelFactor), (unsigned)(ev->root_y / m_pixelFactor)}, + {ev->root_x / (float)m_ww, ev->root_y / (float)m_wh} + }; + m_callback->mouseDown(coord, (IWindowCallback::EMouseButton)button, + (IWindowCallback::EModifierKey)modifierMask); + } + } + case XCB_BUTTON_RELEASE: + { + xcb_button_release_event_t* ev = (xcb_button_release_event_t*)event; + int button = translateButton(ev->state); + if (m_callback && button) + { + int modifierMask = translateModifiers(ev->state); + IWindowCallback::SWindowCoord coord = + { + {(unsigned)ev->root_x, (unsigned)ev->root_y}, + {(unsigned)(ev->root_x / m_pixelFactor), (unsigned)(ev->root_y / m_pixelFactor)}, + {ev->root_x / (float)m_ww, ev->root_y / (float)m_wh} + }; + m_callback->mouseUp(coord, (IWindowCallback::EMouseButton)button, + (IWindowCallback::EModifierKey)modifierMask); + } + } + case XCB_MOTION_NOTIFY: + { + xcb_motion_notify_event_t* ev = (xcb_motion_notify_event_t*)event; + if (m_callback) + { + IWindowCallback::SWindowCoord coord = + { + {(unsigned)ev->root_x, (unsigned)ev->root_y}, + {(unsigned)(ev->root_x / m_pixelFactor), (unsigned)(ev->root_y / m_pixelFactor)}, + {ev->root_x / (float)m_ww, ev->root_y / (float)m_wh} + }; + m_callback->mouseMove(coord); + } + } + } } ETouchType getTouchType() const { - + return TOUCH_NONE; } }; -IWindow* _CWindowXCBNew(const std::string& title) +IWindow* _CWindowXCBNew(const std::string& title, xcb_connection_t* conn) { - return new CWindowXCB(title); + return new CWindowXCB(title, conn); } }