| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766 |
- //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
- //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
- #include <Image/BsPixelData.h>
- #include <Image/BsPixelUtil.h>
- #include <X11/Xutil.h>
- #include <X11/Xatom.h>
- #include <String/BsUnicode.h>
- #include "BsUnixPlatform.h"
- #include "BsUnixWindow.h"
- namespace bs
- {
- Event<void(const Vector2I&, const OSPointerButtonStates&)> Platform::onCursorMoved;
- Event<void(const Vector2I&, OSMouseButton button, const OSPointerButtonStates&)> Platform::onCursorButtonPressed;
- Event<void(const Vector2I&, OSMouseButton button, const OSPointerButtonStates&)> Platform::onCursorButtonReleased;
- Event<void(const Vector2I&, const OSPointerButtonStates&)> Platform::onCursorDoubleClick;
- Event<void(InputCommandType)> Platform::onInputCommand;
- Event<void(float)> Platform::onMouseWheelScrolled;
- Event<void(UINT32)> Platform::onCharInput;
- Event<void(ct::RenderWindow*)> Platform::onMouseLeftWindow;
- Event<void()> Platform::onMouseCaptureChanged
- enum class X11CursorType
- {
- Arrow,
- ArrowDrag,
- ArrowLeftRight,
- Wait,
- IBeam,
- SizeTopLeft,
- SizeTopRight,
- SizeBotLeft,
- SizeBotRight,
- SizeLeft,
- SizeRight,
- SizeTop,
- SizeBottom,
- Deny,
- Count
- };;
- struct Platform::Pimpl
- {
- ::Display* xDisplay = nullptr;
- ::Window mainXWindow = 0;
- ::Window fullscreenXWindow = 0;
- std::unordered_map<::Window, LinuxWindow*> windowMap;
- XIM IM;
- XIC IC;
- Time lastButtonPressTime;
- Atom atomDeleteWindow;
- // Clipboard
- WString clipboardData;
- // Cursor
- ::Cursor currentCursor = None;
- ::Cursor emptyCursor = None;
- bool isCursorHidden = false;
- Rect2I cursorClipRect;
- LinuxWindow* cursorClipWindow = nullptr;
- bool cursorClipEnabled = false;
- };
- static const UINT32 DOUBLE_CLICK_MS = 500;
- Platform::Pimpl* Platform::mData = bs_new<Platform::Pimpl>();
- Platform::~Platform() { }
- Vector2I Platform::getCursorPosition()
- {
- UINT32 screenCount = XScreenCount(mData->xDisplay);
- Vector2I pos;
- for(UINT32 i = 0; i < screenCount; ++i)
- {
- ::Window outRoot, outChild;
- INT32 childX, childY;
- UINT32 mask;
- XQueryPointer(mData->xDisplay, XRootWindow(mData->xDisplay, i), &outRoot, &outChild, &pos.x,
- &pos.y, &childX, &childY, &mask);
- }
- return pos;
- }
- void Platform::setCursorPosition(const Vector2I& screenPos)
- {
- UINT32 screenCount = XScreenCount(mData->xDisplay);
- // Note assuming screens are laid out horizontally left to right
- INT32 screenX = 0;
- for(UINT32 i = 0; i < screenCount; ++i)
- {
- ::Window root = XRootWindow(mData->xDisplay, i);
- INT32 screenXEnd = screenX + XDisplayWidth(mData->xDisplay, i);
- if(screenPos.x >= screenX && screenPos.x < screenXEnd)
- {
- XWarpPointer(mData->xDisplay, None, root, 0, 0, 0, 0, screenPos.x, screenPos.y);
- XFlush(mData->xDisplay);
- return;
- }
- screenX = screenXEnd;
- }
- }
- void Platform::captureMouse(const RenderWindow& window)
- {
- // TODOPORT
- }
- void Platform::releaseMouseCapture()
- {
- // TODOPORT
- }
- bool Platform::isPointOverWindow(const RenderWindow& window, const Vector2I& screenPos)
- {
- // TODOPORT
- return false;
- }
- void applyCurrentCursor(Platform::Pimpl* data, ::Window window)
- {
- if(data->isCursorHidden)
- XDefineCursor(data->xDisplay, window, data->emptyCursor);
- else
- {
- if (data->currentCursor != None)
- XDefineCursor(data->xDisplay, window, data->currentCursor);
- else
- XUndefineCursor(data->xDisplay, window);
- }
- }
- void updateClipBounds(Platform::Pimpl* data, LinuxWindow* window)
- {
- if(!data->cursorClipEnabled || data->cursorClipWindow != window)
- return;
- data->cursorClipRect.x = window->getLeft();
- data->cursorClipRect.y = window->getTop();
- data->cursorClipRect.width = window->getWidth();
- data->cursorClipRect.height = window->getHeight();
- }
- bool clipCursor(Platform::Pimpl* data, Vector2I& pos)
- {
- if(!data->cursorClipEnabled)
- return false;
- int32_t clippedX = pos.x - data->cursorClipRect.x;
- int32_t clippedY = pos.y - data->cursorClipRect.y;
- if(clippedX < 0)
- clippedX = data->cursorClipRect.x + data->cursorClipRect.width + clippedX;
- else if(clippedX >= data->cursorClipRect.width)
- clippedX = data->cursorClipRect.x + (clippedX - data->cursorClipRect.width);
- else
- clippedX = data->cursorClipRect.x + clippedX;
- if(clippedY < 0)
- clippedY = data->cursorClipRect.y + data->cursorClipRect.height + clippedY;
- else if(clippedY >= data->cursorClipRect.height)
- clippedY = data->cursorClipRect.y + (clippedY - data->cursorClipRect.height);
- else
- clippedY = data->cursorClipRect.y + clippedY;
- if(clippedX != pos.x || clippedY != pos.y)
- {
- pos.x = clippedX;
- pos.y = clippedY;
- return true;
- }
- return false;
- }
- void Platform::hideCursor()
- {
- mData->isCursorHidden = true;
- for(auto& entry : mData->windowMap)
- applyCurrentCursor(mData, entry.first);
- }
- void Platform::showCursor()
- {
- mData->isCursorHidden = false;
- for(auto& entry : mData->windowMap)
- applyCurrentCursor(mData, entry.first);
- }
- bool Platform::isCursorHidden()
- {
- return mData->isCursorHidden;
- }
- void Platform::clipCursorToWindow(const RenderWindow& window)
- {
- mData->cursorClipEnabled = true;
- mData->cursorClipWindow = window->_getInternal();
- updateClipBounds(window);
- Vector2I pos = getCursorPosition();
- if(clipCursor(mData, pos))
- setCursorPosition(pos);
- }
- void Platform::clipCursorToRect(const Rect2I& screenRect)
- {
- mData->cursorClipEnabled = true;
- mData->cursorClipRect = screenRect;
- mData->cursorClipWindow = nullptr;
- Vector2I pos = getCursorPosition();
- if(clipCursor(mData, pos))
- setCursorPosition(pos);
- }
- void Platform::clipCursorDisable()
- {
- mData->cursorClipEnabled = false;
- mData->cursorClipWindow = None;
- }
- void Platform::setCursor(PixelData& pixelData, const Vector2I& hotSpot)
- {
- // TODOPORT
- }
- void Platform::setIcon(const PixelData& pixelData)
- {
- SPtr<PixelData> resizedData = PixelData::create(32, 32, 1, PF_RGBA8);
- PixelUtil::scale(pixelData, *resizedData);
- // TODOPORT
- }
- void Platform::setCaptionNonClientAreas(const ct::RenderWindow& window, const Vector<Rect2I>& nonClientAreas)
- {
- // TODOPORT
- }
- void Platform::setResizeNonClientAreas(const ct::RenderWindow& window, const Vector<NonClientResizeArea>& nonClientAreas)
- {
- // Do nothing, resize areas not supported on Linux (but they are provided even on undecorated windows by the WM)
- }
- void Platform::resetNonClientAreas(const ct::RenderWindow& window)
- {
- // Do nothing, resize areas not supported on Linux (but they are provided even on undecorated windows by the WM)
- }
- void Platform::sleep(UINT32 duration)
- {
- usleep(duration * 1000);
- }
- OSDropTarget& Platform::createDropTarget(const RenderWindow* window, int x, int y, unsigned int width, unsigned int height)
- {
- // TODOPORT
- }
- void Platform::destroyDropTarget(OSDropTarget& target)
- {
- // TODOPORT
- }
- void Platform::copyToClipboard(const WString& string)
- {
- mData->clipboardData = string;
- Atom clipboardAtom = XInternAtom(mData->xDisplay, "CLIPBOARD", 0);
- XSetSelectionOwner(mData->xDisplay, clipboardAtom, mData->mainXWindow, CurrentTime);
- }
- WString Platform::copyFromClipboard()
- {
- Atom clipboardAtom = XInternAtom(mData->xDisplay, "CLIPBOARD", 0);
- ::Window selOwner = XGetSelectionOwner(mData->xDisplay, clipboardAtom);
- if(selOwner == None)
- return L"";
- if(selOwner == mData->mainXWindow)
- return mData->clipboardData;
- XConvertSelection(mData->xDisplay, clipboardAtom, XA_STRING, clipboardAtom, mData->mainXWindow,
- CurrentTime);
- XFlush(mData->xDisplay);
- // Note: This might discard events if there are any in between the one we need. Ideally we let the
- // processEvents() handle them
- while(true)
- {
- XEvent event;
- XNextEvent(mData->xDisplay, &event);
- if(event.type == SelectionNotify && event.xselection.requestor == mData->mainXWindow)
- break;
- }
- Atom actualType;
- INT32 actualFormat;
- unsigned long length;
- unsigned long bytesRemaining;
- UINT8* data;
- XGetWindowProperty(mData->xDisplay, mData->mainXWindow, clipboardAtom,
- 0, 0, False, AnyPropertyType, &actualType, &actualFormat, &length, &bytesRemaining, &data);
- if(bytesRemaining > 0)
- {
- unsigned long unused;
- INT32 result = XGetWindowProperty(mData->xDisplay, mData->mainXWindow, clipboardAtom,
- 0, bytesRemaining, False, AnyPropertyType, &actualType, &actualFormat, &length,
- &unused, &data);
- if(result == Success)
- return UTF8::toWide(String((const char*)data));
- XFree(data);
- }
- return L"";
- }
- WString Platform::keyCodeToUnicode(UINT32 keyCode)
- {
- // TODOPORT
- return L"";
- }
- void Platform::_messagePump()
- {
- while(XPending(mData->xDisplay) > 0)
- {
- XEvent event;
- XNextEvent(mData->xDisplay, &event);
- switch (event.type)
- {
- case ClientMessage:
- {
- if(event.xclient.data.l[0] == mData->atomDeleteWindow)
- XDestroyWindow(mData->xDisplay, event.xclient.window);
- }
- break;
- case DestroyNotify:
- {
- auto iterFind = mData->windowMap.find(event.xdestroywindow.window);
- if (iterFind == mData->windowMap.end())
- break;
- LinuxWindow* window = iterFind->second;
- window->_cleanUp();
- if(mData->mainXWindow == 0)
- return;
- }
- break;
- case KeyPress:
- {
- // Process text input
- //// Check if input manager wants this event. If not, we process it.
- if(!XFilterEvent(&event, None))
- {
- Status status;
- uint8_t buffer[16];
- int32_t length = Xutf8LookupString(mData->IC, &event.xkey, (char*)buffer, sizeof(buffer), nullptr,
- &status);
- if(length > 0)
- {
- buffer[length] = '\0';
- // TODOPORT - Buffer now contains the UTF8 value of length 'length' bytes. I can consider converting
- // it to single UTF32 before returning
- }
- }
- // Process normal key press
- {
- static XComposeStatus keyboard;
- uint8_t buffer[16];
- KeySym symbol;
- XLookupString(&event.xkey, (char*)buffer, sizeof(buffer), &symbol, &keyboard);
- bool alt = event.xkey.state & Mod1Mask;
- bool control = event.xkey.state & ControlMask;
- bool shift = event.xkey.state & ShiftMask;
- // TODOPORT - Report key press
- }
- }
- break;
- case KeyRelease:
- {
- uint8_t buffer[16];
- KeySym symbol;
- XLookupString(&event.xkey, (char *) buffer, sizeof(buffer), &symbol, nullptr);
- bool alt = (event.xkey.state & Mod1Mask) != 0;
- bool control = (event.xkey.state & ControlMask) != 0;
- bool shift = (event.xkey.state & ShiftMask) != 0;
- // TODOPORT - Report key release
- }
- break;
- case ButtonPress:
- {
- uint32_t button = event.xbutton.button;
- switch(button)
- {
- // Button 4 & 5 is vertical wheel, 6 & 7 horizontal wheel, and we handle them elsewhere
- case Button1: // Left
- case Button2: // Middle
- case Button3: // Right
- case 8:
- case 9:
- int32_t x = event.xbutton.x_root;
- int32_t y = event.xbutton.y_root;
- bool alt = (event.xbutton.state & Mod1Mask) != 0;
- bool control = (event.xbutton.state & ControlMask) != 0;
- bool shift = (event.xbutton.state & ShiftMask) != 0;
- // TODOPORT - Report button press
- break;
- }
- // Handle double-click
- if(event.xbutton.time < (mData->lastButtonPressTime + DOUBLE_CLICK_MS))
- {
- // TODOPORT - Trigger double-click
- mData->lastButtonPressTime = 0;
- }
- else
- mData->lastButtonPressTime = event.xbutton.time;
- // Handle window dragging for windows without a title bar
- if(button == Button1)
- {
- auto iterFind = mData->windowMap.find(event.xbutton.window);
- if (iterFind != mData->windowMap.end())
- {
- LinuxWindow* window = iterFind->second;
- window->_dragStart(event.xbutton.x, event.xbutton.y);
- }
- }
- break;
- }
- case ButtonRelease:
- {
- uint32_t button = event.xbutton.button;
- bool alt = (event.xbutton.state & Mod1Mask) != 0;
- bool control = (event.xbutton.state & ControlMask) != 0;
- bool shift = (event.xbutton.state & ShiftMask) != 0;
- switch(button)
- {
- case Button2: // Middle
- case Button3: // Right
- case 8:
- case 9:
- {
- int32_t x = event.xbutton.x_root;
- int32_t y = event.xbutton.y_root;
- // TODOPORT - Report button release
- break;
- }
- case Button4: // Vertical mouse wheel
- case Button5:
- {
- int32_t delta = button == Button4 ? 1 : -1;
- // TODOPORT - Send mouse wheel scroll
- }
- break;
- case 6: // Horizontal mouse wheel
- case 7:
- {
- int32_t delta = button == 6 ? 1 : -1;
- // TODOPORT - Send mouse wheel scroll
- }
- break;
- }
- // Handle window dragging for windows without a title bar
- if(button == Button1)
- {
- auto iterFind = mData->windowMap.find(event.xbutton.window);
- if (iterFind != mData->windowMap.end())
- {
- LinuxWindow* window = iterFind->second;
- window->_dragEnd();
- }
- }
- break;
- }
- case MotionNotify:
- {
- Vector2I pos;
- pos.x = event.xmotion.x_root;
- pos.y = event.xmotion.y_root;
- // Handle clipping if enabled
- if(clipCursor(mData, pos))
- setCursorPosition(pos);
- // TODOPORT - Send mouse move event
- // Handle window dragging for windows without a title bar
- auto iterFind = mData->windowMap.find(event.xmotion.window);
- if (iterFind != mData->windowMap.end())
- {
- LinuxWindow* window = iterFind->second;
- window->_dragUpdate(event.xmotion.x, event.xmotion.y);
- }
- }
- break;
- case EnterNotify:
- if(event.xcrossing.mode == NotifyNormal)
- {
- // TODOPORT - Send mouse enter event
- }
- break;
- case LeaveNotify:
- if(event.xcrossing.mode == NotifyNormal)
- {
- Vector2I pos;
- pos.x = event.xcrossing.x_root;
- pos.y = event.xcrossing.y_root;
- if(clipCursor(mData, pos))
- setCursorPosition(pos);
- // TODOPORT - Send mouse leave event
- }
- break;
- case ConfigureNotify:
- {
- const XConfigureEvent &xce = event.xconfigure;
- auto iterFind = mData->windowMap.find(event.xdestroywindow.window);
- if (iterFind == mData->windowMap.end())
- break;
- LinuxWindow* window = iterFind->second;
- updateClipBounds(mData, window);
- // TODOPORT - Send move/resize event
- }
- break;
- case FocusIn:
- // Update input context focus
- XSetICFocus(mData->IC);
- break;
- case FocusOut:
- // Update input context focus
- XUnsetICFocus(mData->IC);
- break;
- case SelectionRequest:
- {
- // Send the data saved by the last clipboard copy operation
- Atom compoundTextAtom = XInternAtom(mData->xDisplay, "COMPOUND_TEXT", 0);
- Atom utf8StringAtom = XInternAtom(mData->xDisplay, "UTF8_STRING", 0);
- Atom targetsAtom = XInternAtom(mData->xDisplay, "TARGETS", 0);
- XSelectionRequestEvent& selReq = event.xselectionrequest;
- XEvent response;
- if(selReq.target == XA_STRING || selReq.target == compoundTextAtom || selReq.target == utf8StringAtom)
- {
- String utf8data = UTF8::fromWide(mData->clipboardData);
- const uint8_t* data = (const uint8_t*)utf8data.c_str();
- int32_t dataLength = utf8data.length();
- XChangeProperty(mData->xDisplay, selReq.requestor, selReq.property,
- selReq.target, 8, PropModeReplace, data, dataLength);
- response.xselection.property = selReq.property;
- }
- else if(selReq.target == targetsAtom)
- {
- Atom data[2];
- data[0] = utf8StringAtom;
- data[1] = XA_STRING;
- XChangeProperty (mData->xDisplay, selReq.requestor, selReq.property, selReq.target,
- 8, PropModeReplace, (unsigned char*)&data, sizeof (data));
- response.xselection.property = selReq.property;
- }
- else
- {
- response.xselection.property = None;
- }
- response.xselection.type = SelectionNotify;
- response.xselection.display = selReq.display;
- response.xselection.requestor = selReq.requestor;
- response.xselection.selection = selReq.selection;
- response.xselection.target = selReq.target;
- response.xselection.time = selReq.time;
- XSendEvent (mData->xDisplay, selReq.requestor, 0, 0, &response);
- XFlush (mData->xDisplay);
- }
- break;
- default:
- break;
- }
- }
- }
- void Platform::_startUp()
- {
- // XInitThreads(); // TODO: Not sure if this will be necessary
- mData->xDisplay = XOpenDisplay(nullptr);
- if(XSupportsLocale())
- {
- XSetLocaleModifiers("");
- mData->IM = XOpenIM(mData->xDisplay, nullptr, nullptr, nullptr);
- // Note: Currently our windows don't support pre-edit and status areas, which are used for more complex types
- // of character input. Later on it might be beneficial to support them.
- mData->IC = XCreateIC(mData->IM, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, nullptr);
- }
- mData->atomDeleteWindow = XInternAtom(mData->xDisplay, "WM_DELETE_WINDOW", False);
- // Create empty cursor
- char data[1];
- memset(data, 0, sizeof(data));
- Pixmap pixmap = XCreateBitmapFromData(mData->xDisplay, DefaultRootWindow(mData->xDisplay), data, 1, 1);
- XColor color;
- color.red = color.green = color.blue = 0;
- mData->emptyCursor = XCreatePixmapCursor(mData->xDisplay, pixmap, pixmap, &color, &color, 0, 0);
- XFreePixmap(mData->xDisplay, pixmap);
- }
- void Platform::_update()
- {
- // Do nothing
- }
- void Platform::_coreUpdate()
- {
- _messagePump();
- }
- void Platform::_shutDown()
- {
- // Free empty cursor
- XFreeCursor(mData->xDisplay, mData->emptyCursor);
- mData->emptyCursor = None;
- if(mData->IC)
- {
- XDestroyIC(mData->IC);
- mData->IC = 0;
- }
- if(mData->IM)
- {
- XCloseIM(mData->IM);
- mData->IM = 0;
- }
- XCloseDisplay(mData->xDisplay);
- mData->xDisplay = nullptr;
- bs_delete(mData);
- mData = nullptr;
- }
- ::Display* LinuxPlatform::getXDisplay()
- {
- return mData->xDisplay;
- }
- ::Window LinuxPlatform::getMainXWindow()
- {
- return mData->mainXWindow;
- }
- void LinuxPlatform::_registerWindow(::Window xWindow, LinuxWindow* window)
- {
- // First window is assumed to be the main
- if(mData->mainXWindow == 0)
- {
- mData->mainXWindow = xWindow;
- // Input context client window must be set before use
- XSetICValues(mData->IC,
- XNClientWindow, xWindow,
- XNFocusWindow, xWindow,
- nullptr);
- }
- mData->windowMap[xWindow] = window;
- applyCurrentCursor(mData, xWindow);
- }
- void LinuxPlatform::_unregisterWindow(::Window xWindow)
- {
- auto iterFind = mData->windowMap.find(xWindow);
- if(iterFind != mData->windowMap.end())
- {
- if(mData->cursorClipEnabled && mData->cursorClipWindow == iterFind->second)
- clipCursorDisable();
- mData->windowMap.erase(iterFind);
- }
- if(mData->mainXWindow == xWindow)
- mData->mainXWindow = 0;
- }
- Pixmap LinuxPlatform::createPixmap(const SPtr<PixelData>& data)
- {
- SPtr<PixelData> bgraData = PixelData::create(data->getWidth(), data->getHeight(), 1, PF_BGRA8);
- PixelUtil::bulkPixelConversion(*data, *bgraData);
- uint32_t depth = XDefaultDepth(mData->xDisplay, 0);
- XImage* image = XCreateImage(mData->xDisplay, XDefaultVisual(mData->xDisplay, 0), depth, ZPixmap, 0,
- (char*)bgraData->getData(), data->getWidth(), data->getHeight(), 32, 0);
- Pixmap pixmap = XCreatePixmap(mData->xDisplay, XDefaultRootWindow(mData->xDisplay),
- data->getWidth(), data->getHeight(), depth);
- XGCValues gcValues;
- GC gc = XCreateGC(mData->xDisplay, pixmap, 0, &gcValues);
- XPutImage(mData->xDisplay, pixmap, gc, image, 0, 0, 0, 0, data->getWidth(), data->getHeight());
- XFreeGC(mData->xDisplay, gc);
- // Make sure XDestroyImage doesn't free the data pointed to by 'data.bytes'
- image->data = nullptr;
- XDestroyImage(image);
- return pixmap;
- }
- }
|