| // | |
| // Copyright (c) 2015 The ANGLE Project Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // | |
| // X11Window.cpp: Implementation of OSWindow for X11 | |
| #include "X11Window.h" | |
| #include <cassert> | |
| #include <chrono> | |
| #include <thread> | |
| #include <X11/Xlib.h> | |
| #include <X11/extensions/XInput2.h> | |
| namespace { | |
| Bool WaitForMapNotify(Display *dpy, XEvent *event, XPointer window) | |
| { | |
| return event->type == MapNotify && event->xmap.window == reinterpret_cast<Window>(window); | |
| } | |
| static Key X11CodeToKey(Display *display, unsigned int scancode) | |
| { | |
| int temp; | |
| KeySym *keySymbols; | |
| keySymbols = XGetKeyboardMapping(display, scancode, 1, &temp); | |
| unsigned int keySymbol = keySymbols[0]; | |
| XFree(keySymbols); | |
| switch (keySymbol) | |
| { | |
| case XK_Shift_L: return KEY_LSHIFT; | |
| case XK_Shift_R: return KEY_RSHIFT; | |
| case XK_Alt_L: return KEY_LALT; | |
| case XK_Alt_R: return KEY_RALT; | |
| case XK_Control_L: return KEY_LCONTROL; | |
| case XK_Control_R: return KEY_RCONTROL; | |
| case XK_Super_L: return KEY_LSYSTEM; | |
| case XK_Super_R: return KEY_RSYSTEM; | |
| case XK_Menu: return KEY_MENU; | |
| case XK_semicolon: return KEY_SEMICOLON; | |
| case XK_slash: return KEY_SLASH; | |
| case XK_equal: return KEY_EQUAL; | |
| case XK_minus: return KEY_DASH; | |
| case XK_bracketleft: return KEY_LBRACKET; | |
| case XK_bracketright:return KEY_RBRACKET; | |
| case XK_comma: return KEY_COMMA; | |
| case XK_period: return KEY_PERIOD; | |
| case XK_backslash: return KEY_BACKSLASH; | |
| case XK_asciitilde: return KEY_TILDE; | |
| case XK_Escape: return KEY_ESCAPE; | |
| case XK_space: return KEY_SPACE; | |
| case XK_Return: return KEY_RETURN; | |
| case XK_BackSpace: return KEY_BACK; | |
| case XK_Tab: return KEY_TAB; | |
| case XK_Page_Up: return KEY_PAGEUP; | |
| case XK_Page_Down: return KEY_PAGEDOWN; | |
| case XK_End: return KEY_END; | |
| case XK_Home: return KEY_HOME; | |
| case XK_Insert: return KEY_INSERT; | |
| case XK_Delete: return KEY_DELETE; | |
| case XK_KP_Add: return KEY_ADD; | |
| case XK_KP_Subtract: return KEY_SUBTRACT; | |
| case XK_KP_Multiply: return KEY_MULTIPLY; | |
| case XK_KP_Divide: return KEY_DIVIDE; | |
| case XK_Pause: return KEY_PAUSE; | |
| case XK_F1: return KEY_F1; | |
| case XK_F2: return KEY_F2; | |
| case XK_F3: return KEY_F3; | |
| case XK_F4: return KEY_F4; | |
| case XK_F5: return KEY_F5; | |
| case XK_F6: return KEY_F6; | |
| case XK_F7: return KEY_F7; | |
| case XK_F8: return KEY_F8; | |
| case XK_F9: return KEY_F9; | |
| case XK_F10: return KEY_F10; | |
| case XK_F11: return KEY_F11; | |
| case XK_F12: return KEY_F12; | |
| case XK_F13: return KEY_F13; | |
| case XK_F14: return KEY_F14; | |
| case XK_F15: return KEY_F15; | |
| case XK_Left: return KEY_LEFT; | |
| case XK_Right: return KEY_RIGHT; | |
| case XK_Down: return KEY_DOWN; | |
| case XK_Up: return KEY_UP; | |
| case XK_KP_Insert: return KEY_NUMPAD0; | |
| case XK_KP_End: return KEY_NUMPAD1; | |
| case XK_KP_Down: return KEY_NUMPAD2; | |
| case XK_KP_Page_Down:return KEY_NUMPAD3; | |
| case XK_KP_Left: return KEY_NUMPAD4; | |
| case XK_KP_5: return KEY_NUMPAD5; | |
| case XK_KP_Right: return KEY_NUMPAD6; | |
| case XK_KP_Home: return KEY_NUMPAD7; | |
| case XK_KP_Up: return KEY_NUMPAD8; | |
| case XK_KP_Page_Up: return KEY_NUMPAD9; | |
| case XK_a: return KEY_A; | |
| case XK_b: return KEY_B; | |
| case XK_c: return KEY_C; | |
| case XK_d: return KEY_D; | |
| case XK_e: return KEY_E; | |
| case XK_f: return KEY_F; | |
| case XK_g: return KEY_G; | |
| case XK_h: return KEY_H; | |
| case XK_i: return KEY_I; | |
| case XK_j: return KEY_J; | |
| case XK_k: return KEY_K; | |
| case XK_l: return KEY_L; | |
| case XK_m: return KEY_M; | |
| case XK_n: return KEY_N; | |
| case XK_o: return KEY_O; | |
| case XK_p: return KEY_P; | |
| case XK_q: return KEY_Q; | |
| case XK_r: return KEY_R; | |
| case XK_s: return KEY_S; | |
| case XK_t: return KEY_T; | |
| case XK_u: return KEY_U; | |
| case XK_v: return KEY_V; | |
| case XK_w: return KEY_W; | |
| case XK_x: return KEY_X; | |
| case XK_y: return KEY_Y; | |
| case XK_z: return KEY_Z; | |
| case XK_1: return KEY_NUM1; | |
| case XK_2: return KEY_NUM2; | |
| case XK_3: return KEY_NUM3; | |
| case XK_4: return KEY_NUM4; | |
| case XK_5: return KEY_NUM5; | |
| case XK_6: return KEY_NUM6; | |
| case XK_7: return KEY_NUM7; | |
| case XK_8: return KEY_NUM8; | |
| case XK_9: return KEY_NUM9; | |
| case XK_0: return KEY_NUM0; | |
| } | |
| return Key(0); | |
| } | |
| static void AddX11KeyStateToEvent(Event *event, unsigned int state) | |
| { | |
| event->Key.Shift = state & ShiftMask; | |
| event->Key.Control = state & ControlMask; | |
| event->Key.Alt = state & Mod1Mask; | |
| event->Key.System = state & Mod4Mask; | |
| } | |
| } | |
| X11Window::X11Window() | |
| : WM_DELETE_WINDOW(None), | |
| WM_PROTOCOLS(None), | |
| TEST_EVENT(None), | |
| mDisplay(nullptr), | |
| mWindow(0), | |
| mRequestedVisualId(-1), | |
| mVisible(false), | |
| mCursor(static_cast<Cursor>(0)) | |
| { | |
| } | |
| X11Window::X11Window(int visualId) | |
| : WM_DELETE_WINDOW(None), | |
| WM_PROTOCOLS(None), | |
| TEST_EVENT(None), | |
| mDisplay(nullptr), | |
| mWindow(0), | |
| mRequestedVisualId(visualId), | |
| mVisible(false) | |
| { | |
| } | |
| X11Window::~X11Window() | |
| { | |
| destroy(); | |
| } | |
| bool X11Window::initialize(const std::string &name, size_t width, size_t height) | |
| { | |
| destroy(); | |
| mDisplay = XOpenDisplay(nullptr); | |
| if (!mDisplay) | |
| { | |
| return false; | |
| } | |
| { | |
| int screen = DefaultScreen(mDisplay); | |
| rootWindow = RootWindow(mDisplay, screen); | |
| Visual *visual; | |
| if (mRequestedVisualId == -1) | |
| { | |
| visual = DefaultVisual(mDisplay, screen); | |
| } | |
| else | |
| { | |
| XVisualInfo visualTemplate; | |
| visualTemplate.visualid = mRequestedVisualId; | |
| int numVisuals = 0; | |
| XVisualInfo *visuals = XGetVisualInfo(mDisplay, VisualIDMask, &visualTemplate, &numVisuals); | |
| if (numVisuals <= 0) | |
| { | |
| return false; | |
| } | |
| assert(numVisuals == 1); | |
| visual = visuals[0].visual; | |
| XFree(visuals); | |
| } | |
| int depth = DefaultDepth(mDisplay, screen); | |
| Colormap colormap = XCreateColormap(mDisplay, rootWindow, visual, AllocNone); | |
| XSetWindowAttributes attributes; | |
| unsigned long attributeMask = CWBorderPixel | CWColormap | CWEventMask; | |
| attributes.event_mask = StructureNotifyMask | PointerMotionMask | ButtonPressMask | | |
| ButtonReleaseMask | FocusChangeMask | EnterWindowMask | | |
| LeaveWindowMask | KeyPressMask | KeyReleaseMask; | |
| attributes.border_pixel = 0; | |
| attributes.colormap = colormap; | |
| mWindow = XCreateWindow(mDisplay, rootWindow, 0, 0, width, height, 0, depth, InputOutput, | |
| visual, attributeMask, &attributes); | |
| Event event; | |
| event.Type = Event::EVENT_CREATED; | |
| pushEvent(event); | |
| XFreeColormap(mDisplay, colormap); | |
| Pixmap csr; | |
| XColor xcolor; | |
| static char csr_bits[] = {0x00}; | |
| csr= XCreateBitmapFromData(mDisplay,mWindow,csr_bits,1,1); | |
| mCursor = XCreatePixmapCursor(mDisplay,csr,csr,&xcolor,&xcolor,1,1); | |
| int addEvent, error; | |
| if(XQueryExtension(mDisplay, "XInputExtension", &mXiOpcode, &addEvent, &error)) | |
| { | |
| int versionMajor = 2; | |
| int versionMinor = 0; | |
| if(XIQueryVersion(mDisplay, &versionMajor, &versionMinor) != Success) | |
| { | |
| destroy(); | |
| return false; | |
| } | |
| } | |
| } | |
| if (!mWindow) | |
| { | |
| destroy(); | |
| return false; | |
| } | |
| // Tell the window manager to notify us when the user wants to close the | |
| // window so we can do it ourselves. | |
| WM_DELETE_WINDOW = XInternAtom(mDisplay, "WM_DELETE_WINDOW", False); | |
| WM_PROTOCOLS = XInternAtom(mDisplay, "WM_PROTOCOLS", False); | |
| if (WM_DELETE_WINDOW == None || WM_PROTOCOLS == None) | |
| { | |
| destroy(); | |
| return false; | |
| } | |
| if(XSetWMProtocols(mDisplay, mWindow, &WM_DELETE_WINDOW, 1) == 0) | |
| { | |
| destroy(); | |
| return false; | |
| } | |
| XFlush(mDisplay); | |
| mX = 0; | |
| mY = 0; | |
| mWidth = width; | |
| mHeight = height; | |
| return true; | |
| } | |
| void X11Window::destroy() | |
| { | |
| if(mCursor) | |
| { | |
| XFreeCursor(mDisplay, mCursor); | |
| } | |
| if (mWindow) | |
| { | |
| XDestroyWindow(mDisplay, mWindow); | |
| mWindow = 0; | |
| } | |
| if (mDisplay) | |
| { | |
| XCloseDisplay(mDisplay); | |
| mDisplay = nullptr; | |
| } | |
| WM_DELETE_WINDOW = None; | |
| WM_PROTOCOLS = None; | |
| } | |
| EGLNativeWindowType X11Window::getNativeWindow() const | |
| { | |
| return mWindow; | |
| } | |
| EGLNativeDisplayType X11Window::getNativeDisplay() const | |
| { | |
| return mDisplay; | |
| } | |
| void X11Window::messageLoop() | |
| { | |
| int eventCount = XPending(mDisplay); | |
| while (eventCount--) | |
| { | |
| XEvent event; | |
| XNextEvent(mDisplay, &event); | |
| processEvent(event); | |
| } | |
| } | |
| /* | |
| void X11Window::setMousePosition(int x, int y) | |
| { | |
| XWarpPointer(mDisplay, None, mWindow, 0, 0, 0, 0, x, y); | |
| } | |
| */ | |
| OSWindow *CreateOSWindow() | |
| { | |
| return new X11Window(); | |
| } | |
| bool X11Window::setPosition(int x, int y) | |
| { | |
| XMoveWindow(mDisplay, mWindow, x, y); | |
| XFlush(mDisplay); | |
| return true; | |
| } | |
| bool X11Window::resize(int width, int height) | |
| { | |
| XResizeWindow(mDisplay, mWindow, width, height); | |
| XFlush(mDisplay); | |
| using std::chrono::system_clock; | |
| auto startTime = system_clock::now(); | |
| // Wait until the window as actually been resized so that the code calling resize | |
| // can assume the window has been resized. | |
| const double kResizeWaitDelay = 0.2; | |
| while (mHeight != height && mWidth != width && (startTime - system_clock::now()).count() < kResizeWaitDelay) | |
| { | |
| messageLoop(); | |
| std::this_thread::sleep_for(std::chrono::milliseconds(10)); | |
| } | |
| return true; | |
| } | |
| void X11Window::setVisible(bool isVisible) | |
| { | |
| if (mVisible == isVisible) | |
| { | |
| return; | |
| } | |
| if (isVisible) | |
| { | |
| XMapWindow(mDisplay, mWindow); | |
| // Wait until we get an event saying this window is mapped so that the | |
| // code calling setVisible can assume the window is visible. | |
| // This is important when creating a framebuffer as the framebuffer content | |
| // is undefined when the window is not visible. | |
| XEvent dummyEvent; | |
| XIfEvent(mDisplay, &dummyEvent, WaitForMapNotify, reinterpret_cast<XPointer>(mWindow)); | |
| } | |
| else | |
| { | |
| XUnmapWindow(mDisplay, mWindow); | |
| XFlush(mDisplay); | |
| } | |
| mVisible = isVisible; | |
| } | |
| void X11Window::processEvent(const XEvent &xEvent) | |
| { | |
| // TODO(cwallez) text events | |
| switch (xEvent.type) | |
| { | |
| case GenericEvent: { | |
| auto cookie = xEvent.xcookie; | |
| if (xEvent.xcookie.extension == mXiOpcode && XGetEventData(mDisplay, &cookie) | |
| && cookie.evtype == XI_RawMotion) { | |
| XIRawEvent *re = static_cast<XIRawEvent *>(cookie.data); | |
| if (re->valuators.mask_len) { | |
| const double *values = re->raw_values; | |
| // double xpos = window->virtualCursorPosX; | |
| // double ypos = window->virtualCursorPosY; | |
| double x = 0,y = 0; | |
| if (XIMaskIsSet(re->valuators.mask, 0)) { | |
| x = *values; | |
| values++; | |
| } | |
| if (XIMaskIsSet(re->valuators.mask, 1)) {} | |
| y = *values; | |
| Event event; | |
| event.Type = Event::EVENT_MOUSE_MOVED; | |
| event.MouseMove.X = static_cast<int8_t>(x); | |
| event.MouseMove.Y = static_cast<int8_t>(y); | |
| // int x = mWidth / 2; | |
| //int y = mHeight / 2; | |
| // XWarpPointer(mDisplay, None, mWindow, 0, 0, 0, 0, x, y); | |
| pushEvent(event); | |
| } | |
| } | |
| } | |
| break; | |
| case KeyPress: | |
| { | |
| Event event; | |
| event.Type = Event::EVENT_KEY_PRESSED; | |
| event.Key.Code = X11CodeToKey(mDisplay, xEvent.xkey.keycode); | |
| AddX11KeyStateToEvent(&event, xEvent.xkey.state); | |
| pushEvent(event); | |
| } | |
| break; | |
| case KeyRelease: | |
| { | |
| Event event; | |
| event.Type = Event::EVENT_KEY_RELEASED; | |
| event.Key.Code = X11CodeToKey(mDisplay, xEvent.xkey.keycode); | |
| AddX11KeyStateToEvent(&event, xEvent.xkey.state); | |
| pushEvent(event); | |
| } | |
| break; | |
| case ConfigureNotify: | |
| { | |
| if (xEvent.xconfigure.width != mWidth || xEvent.xconfigure.height != mHeight) | |
| { | |
| Event event; | |
| event.Type = Event::EVENT_RESIZED; | |
| event.Size.Width = xEvent.xconfigure.width; | |
| event.Size.Height = xEvent.xconfigure.height; | |
| pushEvent(event); | |
| } | |
| if (xEvent.xconfigure.x != mX || xEvent.xconfigure.y != mY) | |
| { | |
| // Sometimes, the window manager reparents our window (for example | |
| // when resizing) then the X and Y coordinates will be with respect to | |
| // the new parent and not what the user wants to know. Use | |
| // XTranslateCoordinates to get the coordinates on the screen. | |
| int screen = DefaultScreen(mDisplay); | |
| Window root = RootWindow(mDisplay, screen); | |
| int x, y; | |
| Window child; | |
| XTranslateCoordinates(mDisplay, mWindow, root, 0, 0, &x, &y, &child); | |
| if (x != mX || y != mY) | |
| { | |
| Event event; | |
| event.Type = Event::EVENT_MOVED; | |
| event.Move.X = x; | |
| event.Move.Y = y; | |
| pushEvent(event); | |
| } | |
| } | |
| } | |
| break; | |
| case FocusIn: | |
| if (xEvent.xfocus.mode == NotifyNormal || xEvent.xfocus.mode == NotifyWhileGrabbed) | |
| { | |
| XIEventMask em; | |
| unsigned char mask[XIMaskLen(XI_RawMotion)] = { 0 }; | |
| em.deviceid = XIAllMasterDevices; | |
| em.mask_len = sizeof(mask); | |
| em.mask = mask; | |
| XISetMask(mask, XI_RawMotion); | |
| XISelectEvents(mDisplay, rootWindow, &em, 1); | |
| // XGrabPointer(mDisplay, mWindow, true, PointerMotionMask, GrabModeAsync, GrabModeAsync, mWindow, mCursor, CurrentTime); | |
| XDefineCursor(mDisplay, mWindow, mCursor); | |
| Event event; | |
| event.Type = Event::EVENT_GAINED_FOCUS; | |
| pushEvent(event); | |
| XFlush(mDisplay); | |
| } | |
| break; | |
| case FocusOut: | |
| if (xEvent.xfocus.mode == NotifyNormal || xEvent.xfocus.mode == NotifyWhileGrabbed) | |
| { | |
| XIEventMask em; | |
| unsigned char mask[] = { 0 }; | |
| em.deviceid = XIAllMasterDevices; | |
| em.mask_len = sizeof(mask); | |
| em.mask = mask; | |
| XISelectEvents(mDisplay, rootWindow, &em, 1); | |
| // XUngrabPointer(mDisplay, CurrentTime); | |
| XUndefineCursor(mDisplay, mWindow); | |
| Event event; | |
| event.Type = Event::EVENT_LOST_FOCUS; | |
| pushEvent(event); | |
| XFlush(mDisplay); | |
| } | |
| break; | |
| case DestroyNotify: | |
| // We already received WM_DELETE_WINDOW | |
| break; | |
| case ClientMessage: | |
| if (xEvent.xclient.message_type == WM_PROTOCOLS && | |
| static_cast<Atom>(xEvent.xclient.data.l[0]) == WM_DELETE_WINDOW) | |
| { | |
| Event event; | |
| event.Type = Event::EVENT_CLOSED; | |
| pushEvent(event); | |
| } | |
| break; | |
| case MotionNotify: { | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment