| // |
| // Copyright 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 "util/x11/X11Window.h" |
| |
| #include "common/debug.h" |
| #include "util/Timer.h" |
| #include "util/test_utils.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); |
| |
| KeySym 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; |
| } |
| |
| } // namespace |
| |
| X11Window::X11Window() |
| : WM_DELETE_WINDOW(None), |
| WM_PROTOCOLS(None), |
| TEST_EVENT(None), |
| mDisplay(nullptr), |
| mWindow(0), |
| mRequestedVisualId(-1), |
| mVisible(false) |
| {} |
| |
| 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, int width, int height) |
| { |
| destroy(); |
| |
| mDisplay = XOpenDisplay(nullptr); |
| if (!mDisplay) |
| { |
| return false; |
| } |
| |
| { |
| int screen = DefaultScreen(mDisplay); |
| Window root = 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, root, 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, root, 0, 0, width, height, 0, depth, InputOutput, visual, |
| attributeMask, &attributes); |
| XFreeColormap(mDisplay, colormap); |
| } |
| |
| 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; |
| } |
| |
| // Create an atom to identify our test event |
| TEST_EVENT = XInternAtom(mDisplay, "ANGLE_TEST_EVENT", False); |
| if (TEST_EVENT == None) |
| { |
| destroy(); |
| return false; |
| } |
| |
| XFlush(mDisplay); |
| |
| mX = 0; |
| mY = 0; |
| mWidth = width; |
| mHeight = height; |
| |
| return true; |
| } |
| |
| void X11Window::destroy() |
| { |
| if (mWindow) |
| { |
| XDestroyWindow(mDisplay, mWindow); |
| mWindow = 0; |
| } |
| if (mDisplay) |
| { |
| XCloseDisplay(mDisplay); |
| mDisplay = nullptr; |
| } |
| WM_DELETE_WINDOW = None; |
| WM_PROTOCOLS = None; |
| } |
| |
| void X11Window::resetNativeWindow() {} |
| |
| 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); |
| } |
| |
| 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); |
| |
| Timer timer; |
| timer.start(); |
| |
| // 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) && timer.getElapsedTime() < kResizeWaitDelay) |
| { |
| messageLoop(); |
| angle::Sleep(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::signalTestEvent() |
| { |
| XEvent event; |
| event.type = ClientMessage; |
| event.xclient.message_type = TEST_EVENT; |
| // Format needs to be valid or a BadValue is generated |
| event.xclient.format = 32; |
| |
| // Hijack StructureNotifyMask as we know we will be listening for it. |
| XSendEvent(mDisplay, mWindow, False, StructureNotifyMask, &event); |
| |
| // For test events, the tests want to check that it really did arrive, and they don't wait |
| // long. XSync here makes sure the event is sent by the time the messageLoop() is called. |
| XSync(mDisplay, false); |
| } |
| |
| void X11Window::processEvent(const XEvent &xEvent) |
| { |
| // TODO(cwallez) text events |
| switch (xEvent.type) |
| { |
| case ButtonPress: |
| { |
| Event event; |
| MouseButton button = MOUSEBUTTON_UNKNOWN; |
| int wheelY = 0; |
| |
| // The mouse wheel updates are sent via button events. |
| switch (xEvent.xbutton.button) |
| { |
| case Button4: |
| wheelY = 1; |
| break; |
| case Button5: |
| wheelY = -1; |
| break; |
| case 6: |
| break; |
| case 7: |
| break; |
| |
| case Button1: |
| button = MOUSEBUTTON_LEFT; |
| break; |
| case Button2: |
| button = MOUSEBUTTON_MIDDLE; |
| break; |
| case Button3: |
| button = MOUSEBUTTON_RIGHT; |
| break; |
| case 8: |
| button = MOUSEBUTTON_BUTTON4; |
| break; |
| case 9: |
| button = MOUSEBUTTON_BUTTON5; |
| break; |
| |
| default: |
| break; |
| } |
| |
| if (wheelY != 0) |
| { |
| event.Type = Event::EVENT_MOUSE_WHEEL_MOVED; |
| event.MouseWheel.Delta = wheelY; |
| pushEvent(event); |
| } |
| |
| if (button != MOUSEBUTTON_UNKNOWN) |
| { |
| event.Type = Event::EVENT_MOUSE_BUTTON_RELEASED; |
| event.MouseButton.Button = button; |
| event.MouseButton.X = xEvent.xbutton.x; |
| event.MouseButton.Y = xEvent.xbutton.y; |
| pushEvent(event); |
| } |
| } |
| break; |
| |
| case ButtonRelease: |
| { |
| Event event; |
| MouseButton button = MOUSEBUTTON_UNKNOWN; |
| |
| switch (xEvent.xbutton.button) |
| { |
| case Button1: |
| button = MOUSEBUTTON_LEFT; |
| break; |
| case Button2: |
| button = MOUSEBUTTON_MIDDLE; |
| break; |
| case Button3: |
| button = MOUSEBUTTON_RIGHT; |
| break; |
| case 8: |
| button = MOUSEBUTTON_BUTTON4; |
| break; |
| case 9: |
| button = MOUSEBUTTON_BUTTON5; |
| break; |
| |
| default: |
| break; |
| } |
| |
| if (button != MOUSEBUTTON_UNKNOWN) |
| { |
| event.Type = Event::EVENT_MOUSE_BUTTON_RELEASED; |
| event.MouseButton.Button = button; |
| event.MouseButton.X = xEvent.xbutton.x; |
| event.MouseButton.Y = xEvent.xbutton.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 EnterNotify: |
| { |
| Event event; |
| event.Type = Event::EVENT_MOUSE_ENTERED; |
| pushEvent(event); |
| } |
| break; |
| |
| case LeaveNotify: |
| { |
| Event event; |
| event.Type = Event::EVENT_MOUSE_LEFT; |
| pushEvent(event); |
| } |
| break; |
| |
| case MotionNotify: |
| { |
| Event event; |
| event.Type = Event::EVENT_MOUSE_MOVED; |
| event.MouseMove.X = xEvent.xmotion.x; |
| event.MouseMove.Y = xEvent.xmotion.y; |
| 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) |
| { |
| Event event; |
| event.Type = Event::EVENT_GAINED_FOCUS; |
| pushEvent(event); |
| } |
| break; |
| |
| case FocusOut: |
| if (xEvent.xfocus.mode == NotifyNormal || xEvent.xfocus.mode == NotifyWhileGrabbed) |
| { |
| Event event; |
| event.Type = Event::EVENT_LOST_FOCUS; |
| pushEvent(event); |
| } |
| 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); |
| } |
| else if (xEvent.xclient.message_type == TEST_EVENT) |
| { |
| Event event; |
| event.Type = Event::EVENT_TEST; |
| pushEvent(event); |
| } |
| break; |
| } |
| } |
| |
| // static |
| OSWindow *OSWindow::New() |
| { |
| return new X11Window(); |
| } |