BsLinuxPlatform.cpp 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. #include "Image/BsPixelData.h"
  4. #include "Image/BsPixelUtil.h"
  5. #include "String/BsUnicode.h"
  6. #include "Linux/BsLinuxPlatform.h"
  7. #include "Linux/BsLinuxWindow.h"
  8. #include "RenderAPI/BsRenderWindow.h"
  9. #include "BsLinuxDropTarget.h"
  10. #include "BsCoreApplication.h"
  11. #include <X11/X.h>
  12. #include <X11/Xatom.h>
  13. #include <X11/Xcursor/Xcursor.h>
  14. #include <X11/Xlib.h>
  15. #include <X11/XKBlib.h>
  16. namespace bs
  17. {
  18. Event<void(const Vector2I&, const OSPointerButtonStates&)> Platform::onCursorMoved;
  19. Event<void(const Vector2I&, OSMouseButton button, const OSPointerButtonStates&)> Platform::onCursorButtonPressed;
  20. Event<void(const Vector2I&, OSMouseButton button, const OSPointerButtonStates&)> Platform::onCursorButtonReleased;
  21. Event<void(const Vector2I&, const OSPointerButtonStates&)> Platform::onCursorDoubleClick;
  22. Event<void(InputCommandType)> Platform::onInputCommand;
  23. Event<void(float)> Platform::onMouseWheelScrolled;
  24. Event<void(UINT32)> Platform::onCharInput;
  25. Event<void()> Platform::onMouseCaptureChanged;
  26. enum class X11CursorType
  27. {
  28. Arrow,
  29. ArrowDrag,
  30. ArrowLeftRight,
  31. Wait,
  32. IBeam,
  33. SizeTopLeft,
  34. SizeTopRight,
  35. SizeBotLeft,
  36. SizeBotRight,
  37. SizeLeft,
  38. SizeRight,
  39. SizeTop,
  40. SizeBottom,
  41. Deny,
  42. Count
  43. };;
  44. struct Platform::Pimpl
  45. {
  46. ::Display* xDisplay = nullptr;
  47. ::Window mainXWindow = 0;
  48. ::Window fullscreenXWindow = 0;
  49. UnorderedMap<::Window, LinuxWindow*> windowMap;
  50. Vector<::Window> toDestroy;
  51. Mutex lock;
  52. XIM IM;
  53. XIC IC;
  54. Time lastButtonPressTime;
  55. Atom atomDeleteWindow;
  56. Atom atomWmState;
  57. Atom atomWmStateHidden;
  58. Atom atomWmStateMaxVert;
  59. Atom atomWmStateMaxHorz;
  60. // Clipboard
  61. WString clipboardData;
  62. // Cursor
  63. ::Cursor currentCursor = None;
  64. ::Cursor emptyCursor = None;
  65. bool isCursorHidden = false;
  66. Rect2I cursorClipRect;
  67. LinuxWindow* cursorClipWindow = nullptr;
  68. bool cursorClipEnabled = false;
  69. };
  70. static const UINT32 DOUBLE_CLICK_MS = 500;
  71. Vector2I _getCursorPosition(Platform::Pimpl* data)
  72. {
  73. Vector2I pos;
  74. UINT32 screenCount = (UINT32)XScreenCount(data->xDisplay);
  75. for (UINT32 i = 0; i < screenCount; ++i)
  76. {
  77. ::Window outRoot, outChild;
  78. INT32 childX, childY;
  79. UINT32 mask;
  80. if(XQueryPointer(data->xDisplay, XRootWindow(data->xDisplay, i), &outRoot, &outChild, &pos.x,
  81. &pos.y, &childX, &childY, &mask))
  82. break;
  83. }
  84. return pos;
  85. }
  86. void _setCursorPosition(Platform::Pimpl* data, const Vector2I& screenPos)
  87. {
  88. UINT32 screenCount = (UINT32)XScreenCount(data->xDisplay);
  89. // Note assuming screens are laid out horizontally left to right
  90. INT32 screenX = 0;
  91. for(UINT32 i = 0; i < screenCount; ++i)
  92. {
  93. ::Window root = XRootWindow(data->xDisplay, i);
  94. INT32 screenXEnd = screenX + XDisplayWidth(data->xDisplay, i);
  95. if(screenPos.x >= screenX && screenPos.x < screenXEnd)
  96. {
  97. XWarpPointer(data->xDisplay, None, root, 0, 0, 0, 0, screenPos.x, screenPos.y);
  98. XFlush(data->xDisplay);
  99. return;
  100. }
  101. screenX = screenXEnd;
  102. }
  103. }
  104. void applyCurrentCursor(Platform::Pimpl* data, ::Window window)
  105. {
  106. if(data->isCursorHidden)
  107. XDefineCursor(data->xDisplay, window, data->emptyCursor);
  108. else
  109. {
  110. if (data->currentCursor != None)
  111. XDefineCursor(data->xDisplay, window, data->currentCursor);
  112. else
  113. XUndefineCursor(data->xDisplay, window);
  114. }
  115. }
  116. void updateClipBounds(Platform::Pimpl* data, LinuxWindow* window)
  117. {
  118. if(!data->cursorClipEnabled || data->cursorClipWindow != window)
  119. return;
  120. data->cursorClipRect.x = window->getLeft();
  121. data->cursorClipRect.y = window->getTop();
  122. data->cursorClipRect.width = window->getWidth();
  123. data->cursorClipRect.height = window->getHeight();
  124. }
  125. bool clipCursor(Platform::Pimpl* data, Vector2I& pos)
  126. {
  127. if(!data->cursorClipEnabled)
  128. return false;
  129. INT32 clippedX = pos.x - data->cursorClipRect.x;
  130. INT32 clippedY = pos.y - data->cursorClipRect.y;
  131. if(clippedX < 0)
  132. clippedX = data->cursorClipRect.x + data->cursorClipRect.width + clippedX;
  133. else if(clippedX >= (INT32)data->cursorClipRect.width)
  134. clippedX = data->cursorClipRect.x + (clippedX - data->cursorClipRect.width);
  135. else
  136. clippedX = data->cursorClipRect.x + clippedX;
  137. if(clippedY < 0)
  138. clippedY = data->cursorClipRect.y + data->cursorClipRect.height + clippedY;
  139. else if(clippedY >= (INT32)data->cursorClipRect.height)
  140. clippedY = data->cursorClipRect.y + (clippedY - data->cursorClipRect.height);
  141. else
  142. clippedY = data->cursorClipRect.y + clippedY;
  143. if(clippedX != pos.x || clippedY != pos.y)
  144. {
  145. pos.x = clippedX;
  146. pos.y = clippedY;
  147. return true;
  148. }
  149. return false;
  150. }
  151. void setCurrentCursor(Platform::Pimpl* data, ::Cursor cursor)
  152. {
  153. if(data->currentCursor)
  154. XFreeCursor(data->xDisplay, data->currentCursor);
  155. data->currentCursor = cursor;
  156. for(auto& entry : data->windowMap)
  157. applyCurrentCursor(data, entry.first);
  158. }
  159. /**
  160. * Searches the window hierarchy, from top to bottom, looking for the top-most window that contains the specified
  161. * point. Returns 0 if one is not found.
  162. */
  163. ::Window getWindowUnderPoint(::Display* display, ::Window rootWindow, ::Window window, const Vector2I& screenPos)
  164. {
  165. ::Window outRoot, outParent;
  166. ::Window* children;
  167. UINT32 numChildren;
  168. XQueryTree(display, window, &outRoot, &outParent, &children, &numChildren);
  169. if(children == nullptr || numChildren == 0)
  170. return window;
  171. for(UINT32 j = 0; j < numChildren; j++)
  172. {
  173. ::Window curWindow = children[numChildren - j - 1];
  174. XWindowAttributes xwa;
  175. XGetWindowAttributes(display, curWindow, &xwa);
  176. if(xwa.map_state != IsViewable || xwa.c_class != InputOutput)
  177. continue;
  178. // Get position in root window coordinates
  179. ::Window outChild;
  180. Vector2I pos;
  181. if(!XTranslateCoordinates(display, curWindow, rootWindow, 0, 0, &pos.x, &pos.y, &outChild))
  182. continue;
  183. Rect2I area(pos.x, pos.y, (UINT32)xwa.width, (UINT32)xwa.height);
  184. if(area.contains(screenPos))
  185. {
  186. XFree(children);
  187. return getWindowUnderPoint(display, rootWindow, curWindow, screenPos);
  188. }
  189. }
  190. XFree(children);
  191. return 0;
  192. }
  193. int x11ErrorHandler(::Display* display, XErrorEvent* event)
  194. {
  195. // X11 by default crashes the app on error, even though some errors can be just fine. So we provide our own handler.
  196. char buffer[256];
  197. XGetErrorText(display, event->error_code, buffer, sizeof(buffer));
  198. LOGWRN("X11 error: " + String(buffer));
  199. return 0;
  200. }
  201. Platform::Pimpl* Platform::mData = bs_new<Platform::Pimpl>();
  202. Platform::~Platform()
  203. { }
  204. Vector2I Platform::getCursorPosition()
  205. {
  206. Lock lock(mData->lock);
  207. return _getCursorPosition(mData);
  208. }
  209. void Platform::setCursorPosition(const Vector2I& screenPos)
  210. {
  211. Lock lock(mData->lock);
  212. _setCursorPosition(mData, screenPos);
  213. }
  214. void Platform::captureMouse(const RenderWindow& window)
  215. {
  216. Lock lock(mData->lock);
  217. LinuxWindow* linuxWindow;
  218. window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
  219. UINT32 mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask;
  220. XGrabPointer(mData->xDisplay, linuxWindow->_getXWindow(), False, mask, GrabModeAsync,
  221. GrabModeAsync, None, None, CurrentTime);
  222. XSync(mData->xDisplay, False);
  223. }
  224. void Platform::releaseMouseCapture()
  225. {
  226. Lock lock(mData->lock);
  227. XUngrabPointer(mData->xDisplay, CurrentTime);
  228. XSync(mData->xDisplay, False);
  229. }
  230. bool Platform::isPointOverWindow(const RenderWindow& window, const Vector2I& screenPos)
  231. {
  232. Lock lock(mData->lock);
  233. LinuxWindow* linuxWindow;
  234. window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
  235. ::Window xWindow = linuxWindow->_getXWindow();
  236. UINT32 screenCount = (UINT32)XScreenCount(mData->xDisplay);
  237. for (UINT32 i = 0; i < screenCount; ++i)
  238. {
  239. ::Window rootWindow = XRootWindow(mData->xDisplay, i);
  240. ::Window curWindow = getWindowUnderPoint(mData->xDisplay, rootWindow, rootWindow, screenPos);
  241. return curWindow == xWindow;
  242. }
  243. return false;
  244. }
  245. void Platform::hideCursor()
  246. {
  247. Lock lock(mData->lock);
  248. mData->isCursorHidden = true;
  249. for(auto& entry : mData->windowMap)
  250. applyCurrentCursor(mData, entry.first);
  251. }
  252. void Platform::showCursor()
  253. {
  254. Lock lock(mData->lock);
  255. mData->isCursorHidden = false;
  256. for(auto& entry : mData->windowMap)
  257. applyCurrentCursor(mData, entry.first);
  258. }
  259. bool Platform::isCursorHidden()
  260. {
  261. Lock lock(mData->lock);
  262. return mData->isCursorHidden;
  263. }
  264. void Platform::clipCursorToWindow(const RenderWindow& window)
  265. {
  266. Lock lock(mData->lock);
  267. LinuxWindow* linuxWindow;
  268. window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
  269. mData->cursorClipEnabled = true;
  270. mData->cursorClipWindow = linuxWindow;
  271. updateClipBounds(mData, linuxWindow);
  272. Vector2I pos = _getCursorPosition(mData);
  273. if(clipCursor(mData, pos))
  274. _setCursorPosition(mData, pos);
  275. }
  276. void Platform::clipCursorToRect(const Rect2I& screenRect)
  277. {
  278. Lock lock(mData->lock);
  279. mData->cursorClipEnabled = true;
  280. mData->cursorClipRect = screenRect;
  281. mData->cursorClipWindow = nullptr;
  282. Vector2I pos = _getCursorPosition(mData);
  283. if(clipCursor(mData, pos))
  284. _setCursorPosition(mData, pos);
  285. }
  286. void Platform::clipCursorDisable()
  287. {
  288. Lock lock(mData->lock);
  289. mData->cursorClipEnabled = false;
  290. mData->cursorClipWindow = None;
  291. }
  292. void Platform::setCursor(PixelData& pixelData, const Vector2I& hotSpot)
  293. {
  294. SPtr<PixelData> bgraData = PixelData::create(pixelData.getWidth(), pixelData.getHeight(), 1, PF_BGRA8);
  295. PixelUtil::bulkPixelConversion(pixelData, *bgraData);
  296. Lock lock(mData->lock);
  297. XcursorImage* image = XcursorImageCreate((int)bgraData->getWidth(), (int)bgraData->getHeight());
  298. image->xhot = (XcursorDim)hotSpot.x;
  299. image->yhot = (XcursorDim)hotSpot.y;
  300. image->delay = 0;
  301. memcpy(image->pixels, bgraData->getData(), bgraData->getSize());
  302. ::Cursor cursor = XcursorImageLoadCursor(mData->xDisplay, image);
  303. XcursorImageDestroy(image);
  304. setCurrentCursor(mData, cursor);
  305. }
  306. void Platform::setIcon(const PixelData& pixelData)
  307. {
  308. SPtr<PixelData> resizedData = PixelData::create(32, 32, 1, PF_RGBA8);
  309. PixelUtil::scale(pixelData, *resizedData);
  310. if(!mData->mainXWindow)
  311. return;
  312. auto iterFind = mData->windowMap.find(mData->mainXWindow);
  313. if(iterFind == mData->windowMap.end())
  314. return;
  315. LinuxWindow* mainLinuxWindow = iterFind->second;
  316. Lock lock(mData->lock);
  317. mainLinuxWindow->setIcon(pixelData);
  318. }
  319. void Platform::setCaptionNonClientAreas(const ct::RenderWindow& window, const Vector<Rect2I>& nonClientAreas)
  320. {
  321. if(nonClientAreas.size() == 0)
  322. return;
  323. Lock lock(mData->lock);
  324. LinuxWindow* linuxWindow;
  325. window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
  326. // Note: Only supporting a single area
  327. linuxWindow->_setDragZone(nonClientAreas[0]);
  328. }
  329. void Platform::setResizeNonClientAreas(const ct::RenderWindow& window, const Vector<NonClientResizeArea>& nonClientAreas)
  330. {
  331. // Do nothing, resize areas not supported on Linux (but they are provided even on undecorated windows by the WM)
  332. }
  333. void Platform::resetNonClientAreas(const ct::RenderWindow& window)
  334. {
  335. // Do nothing, resize areas not supported on Linux (but they are provided even on undecorated windows by the WM)
  336. }
  337. void Platform::sleep(UINT32 duration)
  338. {
  339. usleep(duration * 1000);
  340. }
  341. void Platform::copyToClipboard(const WString& string)
  342. {
  343. Lock lock(mData->lock);
  344. mData->clipboardData = string;
  345. Atom clipboardAtom = XInternAtom(mData->xDisplay, "CLIPBOARD", 0);
  346. XSetSelectionOwner(mData->xDisplay, clipboardAtom, mData->mainXWindow, CurrentTime);
  347. }
  348. WString Platform::copyFromClipboard()
  349. {
  350. Lock lock(mData->lock);
  351. Atom clipboardAtom = XInternAtom(mData->xDisplay, "CLIPBOARD", 0);
  352. ::Window selOwner = XGetSelectionOwner(mData->xDisplay, clipboardAtom);
  353. if(selOwner == None)
  354. return L"";
  355. if(selOwner == mData->mainXWindow)
  356. return mData->clipboardData;
  357. XConvertSelection(mData->xDisplay, clipboardAtom, XA_STRING, clipboardAtom, mData->mainXWindow,
  358. CurrentTime);
  359. XFlush(mData->xDisplay);
  360. // Note: This might discard events if there are any in between the one we need. Ideally we let the
  361. // processEvents() handle them
  362. while(true)
  363. {
  364. XEvent event;
  365. XNextEvent(mData->xDisplay, &event);
  366. if(event.type == SelectionNotify && event.xselection.requestor == mData->mainXWindow)
  367. break;
  368. }
  369. Atom actualType;
  370. INT32 actualFormat;
  371. unsigned long length;
  372. unsigned long bytesRemaining;
  373. UINT8* data;
  374. XGetWindowProperty(mData->xDisplay, mData->mainXWindow, clipboardAtom,
  375. 0, 0, False, AnyPropertyType, &actualType, &actualFormat, &length, &bytesRemaining, &data);
  376. if(bytesRemaining > 0)
  377. {
  378. unsigned long unused;
  379. INT32 result = XGetWindowProperty(mData->xDisplay, mData->mainXWindow, clipboardAtom,
  380. 0, bytesRemaining, False, AnyPropertyType, &actualType, &actualFormat, &length,
  381. &unused, &data);
  382. if(result == Success)
  383. return UTF8::toWide(String((const char*)data));
  384. XFree(data);
  385. }
  386. return L"";
  387. }
  388. WString Platform::keyCodeToUnicode(UINT32 keyCode)
  389. {
  390. Lock lock(mData->lock);
  391. // Note: Assuming the keyCode is equal to X11 KeySym. Which it is because that's how our input manager reports them.
  392. KeySym keySym = (KeySym)keyCode;
  393. XKeyPressedEvent event;
  394. bs_zero_out(event);
  395. event.keycode = XKeysymToKeycode(mData->xDisplay, keySym);
  396. event.display = mData->xDisplay;
  397. Status status;
  398. char buffer[16];
  399. INT32 length = Xutf8LookupString(mData->IC, &event, buffer, sizeof(buffer), nullptr, &status);
  400. if(length > 0)
  401. {
  402. buffer[length] = '\0';
  403. return UTF8::toWide(String(buffer));
  404. }
  405. return L"";
  406. }
  407. void Platform::openFolder(const Path& path)
  408. {
  409. String pathString = path.toString();
  410. const char* commandPattern = "xdg-open '%s'";
  411. char* commandStr = (char*)bs_stack_alloc((UINT32)pathString.size() + (UINT32)strlen(commandPattern) + 1);
  412. sprintf(commandStr, commandPattern, pathString.c_str());
  413. system(commandStr);
  414. bs_stack_free(commandStr);
  415. }
  416. /**
  417. * Converts an X11 KeySym code into an input command, if possible. Returns true if conversion was done.
  418. *
  419. * @param[in] keySym KeySym to try to translate to a command.
  420. * @param[in] shift True if the shift key was held down when the key was pressed.
  421. * @param[out] command Input command. Only valid if function returns true.
  422. * @return True if the KeySym is an input command.
  423. */
  424. bool parseInputCommand(KeySym keySym, bool shift, InputCommandType& command)
  425. {
  426. switch (keySym)
  427. {
  428. case XK_Left:
  429. command = shift ? InputCommandType::SelectLeft : InputCommandType::CursorMoveLeft;
  430. return true;
  431. case XK_Right:
  432. command = shift ? InputCommandType::SelectRight : InputCommandType::CursorMoveRight;
  433. return true;
  434. case XK_Up:
  435. command = shift ? InputCommandType::SelectUp : InputCommandType::CursorMoveUp;
  436. return true;
  437. case XK_Down:
  438. command = shift ? InputCommandType::SelectDown : InputCommandType::CursorMoveDown;
  439. return true;
  440. case XK_Escape:
  441. command = InputCommandType::Escape;
  442. return true;
  443. case XK_ISO_Enter:
  444. command = shift ? InputCommandType::Return : InputCommandType::Confirm;
  445. return true;
  446. case XK_BackSpace:
  447. command = InputCommandType::Backspace;
  448. return true;
  449. case XK_Delete:
  450. command = InputCommandType::Delete;
  451. return true;
  452. }
  453. return false;
  454. }
  455. /** Returns a LinuxWindow from a native X11 window handle. */
  456. LinuxWindow* getLinuxWindow(LinuxPlatform::Pimpl* data, ::Window xWindow)
  457. {
  458. auto iterFind = data->windowMap.find(xWindow);
  459. if (iterFind != data->windowMap.end())
  460. {
  461. LinuxWindow* window = iterFind->second;
  462. return window;
  463. }
  464. return nullptr;
  465. }
  466. /** Returns a RenderWindow from a native X11 window handle. Returns null if the window isn't a RenderWindow */
  467. ct::RenderWindow* getRenderWindow(LinuxPlatform::Pimpl* data, ::Window xWindow)
  468. {
  469. LinuxWindow* linuxWindow = getLinuxWindow(data, xWindow);
  470. if(linuxWindow != nullptr)
  471. return (ct::RenderWindow*)linuxWindow->_getUserData();
  472. return nullptr;
  473. }
  474. void Platform::_messagePump()
  475. {
  476. while(true)
  477. {
  478. Lock lock(mData->lock);
  479. if(XPending(mData->xDisplay) <= 0)
  480. {
  481. // No more events, destroy any queued windows
  482. for(auto& entry : mData->toDestroy)
  483. XDestroyWindow(mData->xDisplay, entry);
  484. mData->toDestroy.clear();
  485. XSync(mData->xDisplay, false);
  486. break;
  487. }
  488. XEvent event;
  489. XNextEvent(mData->xDisplay, &event);
  490. switch (event.type)
  491. {
  492. case ClientMessage:
  493. {
  494. if(LinuxDragAndDrop::handleClientMessage(event.xclient))
  495. break;
  496. if((Atom)event.xclient.data.l[0] == mData->atomDeleteWindow)
  497. {
  498. // We queue the window for destruction as soon as we process all current events (since some of those
  499. // events could still refer to this window)
  500. mData->toDestroy.push_back(event.xclient.window);
  501. XUnmapWindow(mData->xDisplay, event.xclient.window);
  502. XSync(mData->xDisplay, false);
  503. }
  504. }
  505. break;
  506. case DestroyNotify:
  507. {
  508. LinuxWindow* window = getLinuxWindow(mData, event.xdestroywindow.window);
  509. if(window != nullptr)
  510. {
  511. CoreApplication::instance().quitRequested();
  512. window->_cleanUp();
  513. if (mData->mainXWindow == 0)
  514. return;
  515. }
  516. }
  517. break;
  518. case KeyPress:
  519. {
  520. // Process text input
  521. //// Check if input manager wants this event. If not, we process it.
  522. if(!XFilterEvent(&event, None))
  523. {
  524. Status status;
  525. char buffer[16];
  526. INT32 length = Xutf8LookupString(mData->IC, &event.xkey, buffer, sizeof(buffer), nullptr,
  527. &status);
  528. if(length > 0)
  529. {
  530. buffer[length] = '\0';
  531. U32String utfStr = UTF8::toUTF32(String(buffer));
  532. if(utfStr.length() > 0)
  533. onCharInput((UINT32)utfStr[0]);
  534. }
  535. }
  536. // Handle input commands
  537. InputCommandType command = InputCommandType::Backspace;
  538. KeySym keySym = XkbKeycodeToKeysym(mData->xDisplay, (KeyCode)event.xkey.keycode, 0, 0);
  539. bool shift = (event.xkey.state & ShiftMask) != 0;
  540. if(parseInputCommand(keySym, shift, command))
  541. {
  542. if(!onInputCommand.empty())
  543. onInputCommand(command);
  544. }
  545. }
  546. break;
  547. case KeyRelease:
  548. // Do nothing
  549. break;
  550. case ButtonPress:
  551. {
  552. UINT32 button = event.xbutton.button;
  553. OSMouseButton mouseButton;
  554. bool validPress = false;
  555. switch(button)
  556. {
  557. case Button1:
  558. mouseButton = OSMouseButton::Left;
  559. validPress = true;
  560. break;
  561. case Button2:
  562. mouseButton = OSMouseButton::Middle;
  563. validPress = true;
  564. break;
  565. case Button3:
  566. mouseButton = OSMouseButton::Right;
  567. validPress = true;
  568. break;
  569. default:
  570. break;
  571. }
  572. if(validPress)
  573. {
  574. // Send event
  575. Vector2I pos;
  576. pos.x = event.xbutton.x_root;
  577. pos.y = event.xbutton.y_root;
  578. OSPointerButtonStates btnStates;
  579. btnStates.ctrl = (event.xbutton.state & ControlMask) != 0;
  580. btnStates.shift = (event.xbutton.state & ShiftMask) != 0;
  581. btnStates.mouseButtons[0] = (event.xbutton.state & Button1Mask) != 0;
  582. btnStates.mouseButtons[1] = (event.xbutton.state & Button2Mask) != 0;
  583. btnStates.mouseButtons[2] = (event.xbutton.state & Button3Mask) != 0;
  584. onCursorButtonPressed(pos, mouseButton, btnStates);
  585. // Handle double-click
  586. if(button == Button1)
  587. {
  588. if (event.xbutton.time < (mData->lastButtonPressTime + DOUBLE_CLICK_MS))
  589. {
  590. onCursorDoubleClick(pos, btnStates);
  591. mData->lastButtonPressTime = 0;
  592. }
  593. else
  594. mData->lastButtonPressTime = event.xbutton.time;
  595. }
  596. }
  597. // Handle window dragging for windows without a title bar
  598. if(button == Button1)
  599. {
  600. LinuxWindow* window = getLinuxWindow(mData, event.xbutton.window);
  601. if(window != nullptr)
  602. window->_dragStart(event.xbutton.x, event.xbutton.y);
  603. }
  604. break;
  605. }
  606. case ButtonRelease:
  607. {
  608. UINT32 button = event.xbutton.button;
  609. Vector2I pos;
  610. pos.x = event.xbutton.x_root;
  611. pos.y = event.xbutton.y_root;
  612. OSPointerButtonStates btnStates;
  613. btnStates.ctrl = (event.xbutton.state & ControlMask) != 0;
  614. btnStates.shift = (event.xbutton.state & ShiftMask) != 0;
  615. btnStates.mouseButtons[0] = (event.xbutton.state & Button1Mask) != 0;
  616. btnStates.mouseButtons[1] = (event.xbutton.state & Button2Mask) != 0;
  617. btnStates.mouseButtons[2] = (event.xbutton.state & Button3Mask) != 0;
  618. switch(button)
  619. {
  620. case Button1:
  621. onCursorButtonReleased(pos, OSMouseButton::Left, btnStates);
  622. break;
  623. case Button2:
  624. onCursorButtonReleased(pos, OSMouseButton::Middle, btnStates);
  625. break;
  626. case Button3:
  627. onCursorButtonReleased(pos, OSMouseButton::Right, btnStates);
  628. break;
  629. case Button4: // Vertical mouse wheel
  630. case Button5:
  631. {
  632. INT32 delta = button == Button4 ? 1 : -1;
  633. onMouseWheelScrolled((float)delta);
  634. }
  635. break;
  636. default:
  637. break;
  638. }
  639. // Handle window dragging for windows without a title bar
  640. if(button == Button1)
  641. {
  642. LinuxWindow* window = getLinuxWindow(mData, event.xbutton.window);
  643. if(window != nullptr)
  644. window->_dragEnd();
  645. }
  646. break;
  647. }
  648. case MotionNotify:
  649. {
  650. Vector2I pos;
  651. pos.x = event.xmotion.x_root;
  652. pos.y = event.xmotion.y_root;
  653. // Handle clipping if enabled
  654. if(clipCursor(mData, pos))
  655. _setCursorPosition(mData, pos);
  656. // Send event
  657. OSPointerButtonStates btnStates;
  658. btnStates.ctrl = (event.xmotion.state & ControlMask) != 0;
  659. btnStates.shift = (event.xmotion.state & ShiftMask) != 0;
  660. btnStates.mouseButtons[0] = (event.xmotion.state & Button1Mask) != 0;
  661. btnStates.mouseButtons[1] = (event.xmotion.state & Button2Mask) != 0;
  662. btnStates.mouseButtons[2] = (event.xmotion.state & Button3Mask) != 0;
  663. onCursorMoved(pos, btnStates);
  664. // Handle window dragging for windows without a title bar
  665. LinuxWindow* window = getLinuxWindow(mData, event.xmotion.window);
  666. if(window != nullptr)
  667. window->_dragUpdate(event.xmotion.x, event.xmotion.y);
  668. }
  669. break;
  670. case EnterNotify:
  671. // Do nothing
  672. break;
  673. case LeaveNotify:
  674. {
  675. if (event.xcrossing.mode == NotifyNormal)
  676. {
  677. Vector2I pos;
  678. pos.x = event.xcrossing.x_root;
  679. pos.y = event.xcrossing.y_root;
  680. if (clipCursor(mData, pos))
  681. _setCursorPosition(mData, pos);
  682. }
  683. ct::RenderWindow* renderWindow = getRenderWindow(mData, event.xcrossing.window);
  684. if(renderWindow != nullptr)
  685. renderWindow->_notifyMouseLeft();
  686. }
  687. break;
  688. case ConfigureNotify:
  689. {
  690. LinuxWindow* window = getLinuxWindow(mData, event.xconfigure.window);
  691. if(window != nullptr)
  692. {
  693. updateClipBounds(mData, window);
  694. ct::RenderWindow* renderWindow = (ct::RenderWindow*)window->_getUserData();
  695. if(renderWindow != nullptr)
  696. renderWindow->_windowMovedOrResized();
  697. }
  698. }
  699. break;
  700. case FocusIn:
  701. {
  702. // Update input context focus
  703. XSetICFocus(mData->IC);
  704. // Send event to render window
  705. ct::RenderWindow* renderWindow = getRenderWindow(mData, event.xfocus.window);
  706. // Not a render window, so it doesn't care about these events
  707. if (renderWindow != nullptr)
  708. {
  709. if (!renderWindow->getProperties().hasFocus)
  710. renderWindow->_windowFocusReceived();
  711. }
  712. }
  713. break;
  714. case FocusOut:
  715. {
  716. // Update input context focus
  717. XUnsetICFocus(mData->IC);
  718. // Send event to render window
  719. ct::RenderWindow* renderWindow = getRenderWindow(mData, event.xfocus.window);
  720. // Not a render window, so it doesn't care about these events
  721. if (renderWindow != nullptr)
  722. {
  723. if (renderWindow->getProperties().hasFocus)
  724. renderWindow->_windowFocusLost();
  725. }
  726. }
  727. break;
  728. case SelectionNotify:
  729. LinuxDragAndDrop::handleSelectionNotify(event.xselection);
  730. break;
  731. case SelectionRequest:
  732. {
  733. // Send the data saved by the last clipboard copy operation
  734. Atom compoundTextAtom = XInternAtom(mData->xDisplay, "COMPOUND_TEXT", 0);
  735. Atom utf8StringAtom = XInternAtom(mData->xDisplay, "UTF8_STRING", 0);
  736. Atom targetsAtom = XInternAtom(mData->xDisplay, "TARGETS", 0);
  737. XSelectionRequestEvent& selReq = event.xselectionrequest;
  738. XEvent response;
  739. if(selReq.target == XA_STRING || selReq.target == compoundTextAtom || selReq.target == utf8StringAtom)
  740. {
  741. String utf8data = UTF8::fromWide(mData->clipboardData);
  742. const UINT8* data = (const UINT8*)utf8data.c_str();
  743. INT32 dataLength = (INT32)utf8data.length();
  744. XChangeProperty(mData->xDisplay, selReq.requestor, selReq.property,
  745. selReq.target, 8, PropModeReplace, data, dataLength);
  746. response.xselection.property = selReq.property;
  747. }
  748. else if(selReq.target == targetsAtom)
  749. {
  750. Atom data[2];
  751. data[0] = utf8StringAtom;
  752. data[1] = XA_STRING;
  753. XChangeProperty (mData->xDisplay, selReq.requestor, selReq.property, selReq.target,
  754. 8, PropModeReplace, (unsigned char*)&data, sizeof (data));
  755. response.xselection.property = selReq.property;
  756. }
  757. else
  758. {
  759. response.xselection.property = None;
  760. }
  761. response.xselection.type = SelectionNotify;
  762. response.xselection.display = selReq.display;
  763. response.xselection.requestor = selReq.requestor;
  764. response.xselection.selection = selReq.selection;
  765. response.xselection.target = selReq.target;
  766. response.xselection.time = selReq.time;
  767. XSendEvent (mData->xDisplay, selReq.requestor, 0, 0, &response);
  768. XFlush (mData->xDisplay);
  769. }
  770. break;
  771. case PropertyNotify:
  772. // Report minimize, maximize and restore events
  773. if(event.xproperty.atom == mData->atomWmState)
  774. {
  775. Atom type;
  776. INT32 format;
  777. unsigned long count, bytesRemaining;
  778. UINT8* data = nullptr;
  779. INT32 result = XGetWindowProperty(mData->xDisplay, event.xproperty.window, mData->atomWmState,
  780. 0, 1024, False, AnyPropertyType, &type, &format,
  781. &count, &bytesRemaining, &data);
  782. if (result == Success)
  783. {
  784. ct::RenderWindow* renderWindow = getRenderWindow(mData, event.xproperty.window);
  785. // Not a render window, so it doesn't care about these events
  786. if(renderWindow == nullptr)
  787. continue;
  788. Atom* atoms = (Atom*)data;
  789. bool foundHorz = false;
  790. bool foundVert = false;
  791. for (unsigned long i = 0; i < count; i++)
  792. {
  793. if (atoms[i] == mData->atomWmStateMaxHorz) foundHorz = true;
  794. if (atoms[i] == mData->atomWmStateMaxVert) foundVert = true;
  795. if (foundVert && foundHorz)
  796. {
  797. if(event.xproperty.state == PropertyNewValue)
  798. renderWindow->_notifyMaximized();
  799. else
  800. renderWindow->_notifyRestored();
  801. }
  802. if(atoms[i] == mData->atomWmStateHidden)
  803. {
  804. if(event.xproperty.state == PropertyNewValue)
  805. renderWindow->_notifyMinimized();
  806. else
  807. renderWindow->_notifyRestored();
  808. }
  809. }
  810. XFree(atoms);
  811. }
  812. }
  813. break;
  814. default:
  815. break;
  816. }
  817. }
  818. }
  819. void Platform::_startUp()
  820. {
  821. Lock lock(mData->lock);
  822. mData->xDisplay = XOpenDisplay(nullptr);
  823. XSetErrorHandler(x11ErrorHandler);
  824. if(XSupportsLocale())
  825. {
  826. XSetLocaleModifiers("");
  827. mData->IM = XOpenIM(mData->xDisplay, nullptr, nullptr, nullptr);
  828. // Note: Currently our windows don't support pre-edit and status areas, which are used for more complex types
  829. // of character input. Later on it might be beneficial to support them.
  830. mData->IC = XCreateIC(mData->IM, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, nullptr);
  831. }
  832. mData->atomDeleteWindow = XInternAtom(mData->xDisplay, "WM_DELETE_WINDOW", False);
  833. mData->atomWmState = XInternAtom(mData->xDisplay, "_NET_WM_STATE", False);
  834. mData->atomWmStateHidden = XInternAtom(mData->xDisplay, "_NET_WM_STATE_HIDDEN", False);
  835. mData->atomWmStateMaxHorz = XInternAtom(mData->xDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
  836. mData->atomWmStateMaxVert = XInternAtom(mData->xDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", False);
  837. // Drag and drop
  838. LinuxDragAndDrop::startUp(mData->xDisplay);
  839. // Create empty cursor
  840. char data[1];
  841. memset(data, 0, sizeof(data));
  842. Pixmap pixmap = XCreateBitmapFromData(mData->xDisplay, DefaultRootWindow(mData->xDisplay), data, 1, 1);
  843. XColor color;
  844. color.red = color.green = color.blue = 0;
  845. mData->emptyCursor = XCreatePixmapCursor(mData->xDisplay, pixmap, pixmap, &color, &color, 0, 0);
  846. XFreePixmap(mData->xDisplay, pixmap);
  847. }
  848. void Platform::_update()
  849. {
  850. LinuxDragAndDrop::update();
  851. }
  852. void Platform::_coreUpdate()
  853. {
  854. _messagePump();
  855. }
  856. void Platform::_shutDown()
  857. {
  858. Lock lock(mData->lock);
  859. // Free empty cursor
  860. XFreeCursor(mData->xDisplay, mData->emptyCursor);
  861. mData->emptyCursor = None;
  862. // Shutdown drag and drop
  863. LinuxDragAndDrop::shutDown();
  864. if(mData->IC)
  865. {
  866. XDestroyIC(mData->IC);
  867. mData->IC = 0;
  868. }
  869. if(mData->IM)
  870. {
  871. XCloseIM(mData->IM);
  872. mData->IM = 0;
  873. }
  874. XCloseDisplay(mData->xDisplay);
  875. mData->xDisplay = nullptr;
  876. bs_delete(mData);
  877. mData = nullptr;
  878. }
  879. ::Display* LinuxPlatform::getXDisplay()
  880. {
  881. return mData->xDisplay;
  882. }
  883. ::Window LinuxPlatform::getMainXWindow()
  884. {
  885. return mData->mainXWindow;
  886. }
  887. void LinuxPlatform::lockX()
  888. {
  889. mData->lock.lock();
  890. }
  891. void LinuxPlatform::unlockX()
  892. {
  893. mData->lock.unlock();
  894. }
  895. void LinuxPlatform::_registerWindow(::Window xWindow, LinuxWindow* window)
  896. {
  897. // First window is assumed to be the main
  898. if(mData->mainXWindow == 0)
  899. {
  900. mData->mainXWindow = xWindow;
  901. // Input context client window must be set before use
  902. XSetICValues(mData->IC,
  903. XNClientWindow, xWindow,
  904. XNFocusWindow, xWindow,
  905. nullptr);
  906. }
  907. mData->windowMap[xWindow] = window;
  908. applyCurrentCursor(mData, xWindow);
  909. }
  910. void LinuxPlatform::_unregisterWindow(::Window xWindow)
  911. {
  912. auto iterFind = mData->windowMap.find(xWindow);
  913. if(iterFind != mData->windowMap.end())
  914. {
  915. if(mData->cursorClipEnabled && mData->cursorClipWindow == iterFind->second)
  916. clipCursorDisable();
  917. mData->windowMap.erase(iterFind);
  918. }
  919. if(mData->mainXWindow == xWindow)
  920. mData->mainXWindow = 0;
  921. }
  922. Pixmap LinuxPlatform::createPixmap(const PixelData& data)
  923. {
  924. SPtr<PixelData> bgraData = PixelData::create(data.getWidth(), data.getHeight(), 1, PF_BGRA8);
  925. PixelUtil::bulkPixelConversion(data, *bgraData);
  926. UINT32 depth = (UINT32)XDefaultDepth(mData->xDisplay, 0);
  927. XImage* image = XCreateImage(mData->xDisplay, XDefaultVisual(mData->xDisplay, 0), depth, ZPixmap, 0,
  928. (char*)bgraData->getData(), data.getWidth(), data.getHeight(), 32, 0);
  929. Pixmap pixmap = XCreatePixmap(mData->xDisplay, XDefaultRootWindow(mData->xDisplay),
  930. data.getWidth(), data.getHeight(), depth);
  931. XGCValues gcValues;
  932. GC gc = XCreateGC(mData->xDisplay, pixmap, 0, &gcValues);
  933. XPutImage(mData->xDisplay, pixmap, gc, image, 0, 0, 0, 0, data.getWidth(), data.getHeight());
  934. XFreeGC(mData->xDisplay, gc);
  935. // Make sure XDestroyImage doesn't free the data pointed to by 'data.bytes'
  936. image->data = nullptr;
  937. XDestroyImage(image);
  938. return pixmap;
  939. }
  940. }