зеркало из https://github.com/AvaloniaUI/angle.git
716 строки
18 KiB
C++
716 строки
18 KiB
C++
//
|
|
// 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/system_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 = 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<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();
|
|
}
|