// // 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 "x11/X11Window.h" #include "common/debug.h" #include "system_utils.h" #include "Timer.h" namespace { Bool WaitForMapNotify(Display *dpy, XEvent *event, XPointer window) { return event->type == MapNotify && event->xmap.window == reinterpret_cast(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) { } 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(NULL); 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; } 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); Timer* timer = CreateTimer(); 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); } delete timer; 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(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); } void X11Window::processEvent(const XEvent &xEvent) { // TODO(cwallez) text events switch (xEvent.type) { case ButtonPress: { Event event; MouseButton button = MOUSEBUTTON_UNKNOWN; int wheelX = 0; 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: wheelX = 1; break; case 7: wheelX = -1; 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(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; } }