BsLinuxPlatform.cpp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287
  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 = 0;
  133. else if(clippedX >= (INT32)data->cursorClipRect.width)
  134. clippedX = data->cursorClipRect.width > 0 ? data->cursorClipRect.width - 1 : 0;
  135. if(clippedY < 0)
  136. clippedY = 0;
  137. else if(clippedY >= (INT32)data->cursorClipRect.height)
  138. clippedY = data->cursorClipRect.height > 0 ? data->cursorClipRect.height - 1 : 0;
  139. clippedX += data->cursorClipRect.x;
  140. clippedY += data->cursorClipRect.y;
  141. if(clippedX != pos.x || clippedY != pos.y)
  142. {
  143. pos.x = clippedX;
  144. pos.y = clippedY;
  145. return true;
  146. }
  147. return false;
  148. }
  149. void setCurrentCursor(Platform::Pimpl* data, ::Cursor cursor)
  150. {
  151. if(data->currentCursor)
  152. XFreeCursor(data->xDisplay, data->currentCursor);
  153. data->currentCursor = cursor;
  154. for(auto& entry : data->windowMap)
  155. applyCurrentCursor(data, entry.first);
  156. }
  157. /**
  158. * Searches the window hierarchy, from top to bottom, looking for the top-most window that contains the specified
  159. * point. Returns 0 if one is not found.
  160. */
  161. ::Window getWindowUnderPoint(::Display* display, ::Window rootWindow, ::Window window, const Vector2I& screenPos)
  162. {
  163. ::Window outRoot, outParent;
  164. ::Window* children;
  165. UINT32 numChildren;
  166. XQueryTree(display, window, &outRoot, &outParent, &children, &numChildren);
  167. if(children == nullptr || numChildren == 0)
  168. return window;
  169. for(UINT32 j = 0; j < numChildren; j++)
  170. {
  171. ::Window curWindow = children[numChildren - j - 1];
  172. XWindowAttributes xwa;
  173. XGetWindowAttributes(display, curWindow, &xwa);
  174. if(xwa.map_state != IsViewable || xwa.c_class != InputOutput)
  175. continue;
  176. // Get position in root window coordinates
  177. ::Window outChild;
  178. Vector2I pos;
  179. if(!XTranslateCoordinates(display, curWindow, rootWindow, 0, 0, &pos.x, &pos.y, &outChild))
  180. continue;
  181. Rect2I area(pos.x, pos.y, (UINT32)xwa.width, (UINT32)xwa.height);
  182. if(area.contains(screenPos))
  183. {
  184. XFree(children);
  185. return getWindowUnderPoint(display, rootWindow, curWindow, screenPos);
  186. }
  187. }
  188. XFree(children);
  189. return 0;
  190. }
  191. int x11ErrorHandler(::Display* display, XErrorEvent* event)
  192. {
  193. // X11 by default crashes the app on error, even though some errors can be just fine. So we provide our own handler.
  194. char buffer[256];
  195. XGetErrorText(display, event->error_code, buffer, sizeof(buffer));
  196. LOGWRN("X11 error: " + String(buffer));
  197. return 0;
  198. }
  199. Platform::Pimpl* Platform::mData = bs_new<Platform::Pimpl>();
  200. Platform::~Platform()
  201. { }
  202. Vector2I Platform::getCursorPosition()
  203. {
  204. Lock lock(mData->lock);
  205. return _getCursorPosition(mData);
  206. }
  207. void Platform::setCursorPosition(const Vector2I& screenPos)
  208. {
  209. Lock lock(mData->lock);
  210. _setCursorPosition(mData, screenPos);
  211. }
  212. void Platform::captureMouse(const RenderWindow& window)
  213. {
  214. Lock lock(mData->lock);
  215. LinuxWindow* linuxWindow;
  216. window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
  217. UINT32 mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask;
  218. XGrabPointer(mData->xDisplay, linuxWindow->_getXWindow(), False, mask, GrabModeAsync,
  219. GrabModeAsync, None, None, CurrentTime);
  220. XSync(mData->xDisplay, False);
  221. }
  222. void Platform::releaseMouseCapture()
  223. {
  224. Lock lock(mData->lock);
  225. XUngrabPointer(mData->xDisplay, CurrentTime);
  226. XSync(mData->xDisplay, False);
  227. }
  228. bool Platform::isPointOverWindow(const RenderWindow& window, const Vector2I& screenPos)
  229. {
  230. Lock lock(mData->lock);
  231. LinuxWindow* linuxWindow;
  232. window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
  233. ::Window xWindow = linuxWindow->_getXWindow();
  234. UINT32 screenCount = (UINT32)XScreenCount(mData->xDisplay);
  235. for (UINT32 i = 0; i < screenCount; ++i)
  236. {
  237. ::Window rootWindow = XRootWindow(mData->xDisplay, i);
  238. ::Window curWindow = getWindowUnderPoint(mData->xDisplay, rootWindow, rootWindow, screenPos);
  239. return curWindow == xWindow;
  240. }
  241. return false;
  242. }
  243. void Platform::hideCursor()
  244. {
  245. Lock lock(mData->lock);
  246. mData->isCursorHidden = true;
  247. for(auto& entry : mData->windowMap)
  248. applyCurrentCursor(mData, entry.first);
  249. }
  250. void Platform::showCursor()
  251. {
  252. Lock lock(mData->lock);
  253. mData->isCursorHidden = false;
  254. for(auto& entry : mData->windowMap)
  255. applyCurrentCursor(mData, entry.first);
  256. }
  257. bool Platform::isCursorHidden()
  258. {
  259. Lock lock(mData->lock);
  260. return mData->isCursorHidden;
  261. }
  262. void Platform::clipCursorToWindow(const RenderWindow& window)
  263. {
  264. Lock lock(mData->lock);
  265. LinuxWindow* linuxWindow;
  266. window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
  267. mData->cursorClipEnabled = true;
  268. mData->cursorClipWindow = linuxWindow;
  269. updateClipBounds(mData, linuxWindow);
  270. Vector2I pos = _getCursorPosition(mData);
  271. if(clipCursor(mData, pos))
  272. _setCursorPosition(mData, pos);
  273. }
  274. void Platform::clipCursorToRect(const Rect2I& screenRect)
  275. {
  276. Lock lock(mData->lock);
  277. mData->cursorClipEnabled = true;
  278. mData->cursorClipRect = screenRect;
  279. mData->cursorClipWindow = nullptr;
  280. Vector2I pos = _getCursorPosition(mData);
  281. if(clipCursor(mData, pos))
  282. _setCursorPosition(mData, pos);
  283. }
  284. void Platform::clipCursorDisable()
  285. {
  286. Lock lock(mData->lock);
  287. mData->cursorClipEnabled = false;
  288. mData->cursorClipWindow = None;
  289. }
  290. void Platform::setCursor(PixelData& pixelData, const Vector2I& hotSpot)
  291. {
  292. SPtr<PixelData> bgraData = PixelData::create(pixelData.getWidth(), pixelData.getHeight(), 1, PF_BGRA8);
  293. PixelUtil::bulkPixelConversion(pixelData, *bgraData);
  294. Lock lock(mData->lock);
  295. XcursorImage* image = XcursorImageCreate((int)bgraData->getWidth(), (int)bgraData->getHeight());
  296. image->xhot = (XcursorDim)hotSpot.x;
  297. image->yhot = (XcursorDim)hotSpot.y;
  298. image->delay = 0;
  299. memcpy(image->pixels, bgraData->getData(), bgraData->getSize());
  300. ::Cursor cursor = XcursorImageLoadCursor(mData->xDisplay, image);
  301. XcursorImageDestroy(image);
  302. setCurrentCursor(mData, cursor);
  303. }
  304. void Platform::setIcon(const PixelData& pixelData)
  305. {
  306. SPtr<PixelData> resizedData = PixelData::create(32, 32, 1, PF_RGBA8);
  307. PixelUtil::scale(pixelData, *resizedData);
  308. if(!mData->mainXWindow)
  309. return;
  310. auto iterFind = mData->windowMap.find(mData->mainXWindow);
  311. if(iterFind == mData->windowMap.end())
  312. return;
  313. LinuxWindow* mainLinuxWindow = iterFind->second;
  314. Lock lock(mData->lock);
  315. mainLinuxWindow->setIcon(pixelData);
  316. }
  317. void Platform::setCaptionNonClientAreas(const ct::RenderWindow& window, const Vector<Rect2I>& nonClientAreas)
  318. {
  319. if(nonClientAreas.size() == 0)
  320. return;
  321. Lock lock(mData->lock);
  322. LinuxWindow* linuxWindow;
  323. window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
  324. // Note: Only supporting a single area
  325. linuxWindow->_setDragZone(nonClientAreas[0]);
  326. }
  327. void Platform::setResizeNonClientAreas(const ct::RenderWindow& window, const Vector<NonClientResizeArea>& nonClientAreas)
  328. {
  329. // Do nothing, resize areas not supported on Linux (but they are provided even on undecorated windows by the WM)
  330. }
  331. void Platform::resetNonClientAreas(const ct::RenderWindow& window)
  332. {
  333. // Do nothing, resize areas not supported on Linux (but they are provided even on undecorated windows by the WM)
  334. }
  335. void Platform::sleep(UINT32 duration)
  336. {
  337. usleep(duration * 1000);
  338. }
  339. void Platform::copyToClipboard(const WString& string)
  340. {
  341. Lock lock(mData->lock);
  342. mData->clipboardData = string;
  343. Atom clipboardAtom = XInternAtom(mData->xDisplay, "CLIPBOARD", 0);
  344. XSetSelectionOwner(mData->xDisplay, clipboardAtom, mData->mainXWindow, CurrentTime);
  345. }
  346. WString Platform::copyFromClipboard()
  347. {
  348. Lock lock(mData->lock);
  349. Atom clipboardAtom = XInternAtom(mData->xDisplay, "CLIPBOARD", 0);
  350. ::Window selOwner = XGetSelectionOwner(mData->xDisplay, clipboardAtom);
  351. if(selOwner == None)
  352. return L"";
  353. if(selOwner == mData->mainXWindow)
  354. return mData->clipboardData;
  355. XConvertSelection(mData->xDisplay, clipboardAtom, XA_STRING, clipboardAtom, mData->mainXWindow,
  356. CurrentTime);
  357. XFlush(mData->xDisplay);
  358. // Note: This might discard events if there are any in between the one we need. Ideally we let the
  359. // processEvents() handle them
  360. while(true)
  361. {
  362. XEvent event;
  363. XNextEvent(mData->xDisplay, &event);
  364. if(event.type == SelectionNotify && event.xselection.requestor == mData->mainXWindow)
  365. break;
  366. }
  367. Atom actualType;
  368. INT32 actualFormat;
  369. unsigned long length;
  370. unsigned long bytesRemaining;
  371. UINT8* data;
  372. XGetWindowProperty(mData->xDisplay, mData->mainXWindow, clipboardAtom,
  373. 0, 0, False, AnyPropertyType, &actualType, &actualFormat, &length, &bytesRemaining, &data);
  374. if(bytesRemaining > 0)
  375. {
  376. unsigned long unused;
  377. INT32 result = XGetWindowProperty(mData->xDisplay, mData->mainXWindow, clipboardAtom,
  378. 0, bytesRemaining, False, AnyPropertyType, &actualType, &actualFormat, &length,
  379. &unused, &data);
  380. if(result == Success)
  381. return UTF8::toWide(String((const char*)data));
  382. XFree(data);
  383. }
  384. return L"";
  385. }
  386. /** Maps Banshee button codes to X11 names for physical key locations. */
  387. const char* keyCodeToKeyName(ButtonCode code)
  388. {
  389. switch(code)
  390. {
  391. // Row #1
  392. case BC_F1: return "FK01";
  393. case BC_F2: return "FK02";
  394. case BC_F3: return "FK03";
  395. case BC_F4: return "FK04";
  396. case BC_F5: return "FK05";
  397. case BC_F6: return "FK06";
  398. case BC_F7: return "FK07";
  399. case BC_F8: return "FK08";
  400. case BC_F9: return "FK09";
  401. case BC_F10: return "FK10";
  402. case BC_F11: return "FK11";
  403. case BC_F12: return "FK12";
  404. // Row #2
  405. case BC_GRAVE: return "TLDE";
  406. case BC_1: return "AE01";
  407. case BC_2: return "AE02";
  408. case BC_3: return "AE03";
  409. case BC_4: return "AE04";
  410. case BC_5: return "AE05";
  411. case BC_6: return "AE06";
  412. case BC_7: return "AE07";
  413. case BC_8: return "AE08";
  414. case BC_9: return "AE09";
  415. case BC_0: return "AE10";
  416. case BC_MINUS: return "AE11";
  417. case BC_EQUALS: return "AE12";
  418. case BC_BACK: return "BKSP";
  419. // Row #3
  420. case BC_TAB: return "TAB";
  421. case BC_Q: return "AD01";
  422. case BC_W: return "AD02";
  423. case BC_E: return "AD03";
  424. case BC_R: return "AD04";
  425. case BC_T: return "AD05";
  426. case BC_Y: return "AD06";
  427. case BC_U: return "AD07";
  428. case BC_I: return "AD08";
  429. case BC_O: return "AD09";
  430. case BC_P: return "AD10";
  431. case BC_LBRACKET: return "AD11";
  432. case BC_RBRACKET: return "AD12";
  433. // Row #4
  434. case BC_A: return "AC01";
  435. case BC_S: return "AC02";
  436. case BC_D: return "AC03";
  437. case BC_F: return "AC04";
  438. case BC_G: return "AC05";
  439. case BC_H: return "AC06";
  440. case BC_J: return "AC07";
  441. case BC_K: return "AC08";
  442. case BC_L: return "AC09";
  443. case BC_SEMICOLON: return "AC10";
  444. case BC_APOSTROPHE: return "AC11";
  445. case BC_BACKSLASH: return "BKSL";
  446. // Row #5
  447. case BC_Z: return "AB01";
  448. case BC_X: return "AB02";
  449. case BC_C: return "AB03";
  450. case BC_V: return "AB04";
  451. case BC_B: return "AB05";
  452. case BC_N: return "AB06";
  453. case BC_M: return "AB07";
  454. case BC_COMMA: return "AB08";
  455. case BC_PERIOD: return "AB09";
  456. case BC_SLASH: return "AB10";
  457. // Keypad
  458. case BC_NUMPAD0: return "KP0";
  459. case BC_NUMPAD1: return "KP1";
  460. case BC_NUMPAD2: return "KP2";
  461. case BC_NUMPAD3: return "KP3";
  462. case BC_NUMPAD4: return "KP4";
  463. case BC_NUMPAD5: return "KP5";
  464. case BC_NUMPAD6: return "KP6";
  465. case BC_NUMPAD7: return "KP7";
  466. case BC_NUMPAD8: return "KP8";
  467. case BC_NUMPAD9: return "KP9";
  468. default:
  469. break;
  470. }
  471. return nullptr;
  472. }
  473. WString Platform::keyCodeToUnicode(UINT32 buttonCode)
  474. {
  475. Lock lock(mData->lock);
  476. static bool mapInitialized = false;
  477. static UnorderedMap<String, KeyCode> keyMap;
  478. if(!mapInitialized)
  479. {
  480. char name[XkbKeyNameLength + 1];
  481. XkbDescPtr desc = XkbGetMap(mData->xDisplay, 0, XkbUseCoreKbd);
  482. XkbGetNames(mData->xDisplay, XkbKeyNamesMask, desc);
  483. for(UINT32 keyCode = desc->min_key_code; keyCode <= desc->max_key_code; keyCode++)
  484. {
  485. memcpy(name, desc->names->keys[keyCode].name, XkbKeyNameLength);
  486. name[XkbKeyNameLength] = '\0';
  487. keyMap[String(name)] = keyCode;
  488. }
  489. XkbFreeNames(desc, XkbKeyNamesMask, True);
  490. XkbFreeKeyboard(desc, 0, True);
  491. mapInitialized = true;
  492. }
  493. const char* keyName = keyCodeToKeyName((ButtonCode)buttonCode);
  494. if(keyName == nullptr)
  495. {
  496. // Not a printable key
  497. return L"";
  498. }
  499. auto iterFind = keyMap.find(String(keyName));
  500. if(iterFind == keyMap.end())
  501. {
  502. // Cannot find mapping, although this shouldn't really happen
  503. return L"";
  504. }
  505. XKeyPressedEvent event;
  506. bs_zero_out(event);
  507. event.type = KeyPress;
  508. event.keycode = iterFind->second;
  509. event.display = mData->xDisplay;
  510. event.time = CurrentTime;
  511. event.window = mData->mainXWindow;
  512. event.root = RootWindow(mData->xDisplay, XDefaultScreen(mData->xDisplay));
  513. Status status;
  514. char buffer[16];
  515. INT32 length = Xutf8LookupString(mData->IC, &event, buffer, sizeof(buffer), nullptr, &status);
  516. if(length > 0)
  517. {
  518. buffer[length] = '\0';
  519. return UTF8::toWide(String(buffer));
  520. }
  521. return L"";
  522. }
  523. void Platform::openFolder(const Path& path)
  524. {
  525. String pathString = path.toString();
  526. const char* commandPattern = "xdg-open '%s'";
  527. char* commandStr = (char*)bs_stack_alloc((UINT32)pathString.size() + (UINT32)strlen(commandPattern) + 1);
  528. sprintf(commandStr, commandPattern, pathString.c_str());
  529. system(commandStr);
  530. bs_stack_free(commandStr);
  531. }
  532. /**
  533. * Converts an X11 KeySym code into an input command, if possible. Returns true if conversion was done.
  534. *
  535. * @param[in] keySym KeySym to try to translate to a command.
  536. * @param[in] shift True if the shift key was held down when the key was pressed.
  537. * @param[out] command Input command. Only valid if function returns true.
  538. * @return True if the KeySym is an input command.
  539. */
  540. bool parseInputCommand(KeySym keySym, bool shift, InputCommandType& command)
  541. {
  542. switch (keySym)
  543. {
  544. case XK_Left:
  545. command = shift ? InputCommandType::SelectLeft : InputCommandType::CursorMoveLeft;
  546. return true;
  547. case XK_Right:
  548. command = shift ? InputCommandType::SelectRight : InputCommandType::CursorMoveRight;
  549. return true;
  550. case XK_Up:
  551. command = shift ? InputCommandType::SelectUp : InputCommandType::CursorMoveUp;
  552. return true;
  553. case XK_Down:
  554. command = shift ? InputCommandType::SelectDown : InputCommandType::CursorMoveDown;
  555. return true;
  556. case XK_Escape:
  557. command = InputCommandType::Escape;
  558. return true;
  559. case XK_Return:
  560. command = shift ? InputCommandType::Return : InputCommandType::Confirm;
  561. return true;
  562. case XK_BackSpace:
  563. command = InputCommandType::Backspace;
  564. return true;
  565. case XK_Delete:
  566. command = InputCommandType::Delete;
  567. return true;
  568. }
  569. return false;
  570. }
  571. /** Returns a LinuxWindow from a native X11 window handle. */
  572. LinuxWindow* getLinuxWindow(LinuxPlatform::Pimpl* data, ::Window xWindow)
  573. {
  574. auto iterFind = data->windowMap.find(xWindow);
  575. if (iterFind != data->windowMap.end())
  576. {
  577. LinuxWindow* window = iterFind->second;
  578. return window;
  579. }
  580. return nullptr;
  581. }
  582. /** Returns a RenderWindow from a native X11 window handle. Returns null if the window isn't a RenderWindow */
  583. ct::RenderWindow* getRenderWindow(LinuxPlatform::Pimpl* data, ::Window xWindow)
  584. {
  585. LinuxWindow* linuxWindow = getLinuxWindow(data, xWindow);
  586. if(linuxWindow != nullptr)
  587. return (ct::RenderWindow*)linuxWindow->_getUserData();
  588. return nullptr;
  589. }
  590. void Platform::_messagePump()
  591. {
  592. while(true)
  593. {
  594. Lock lock(mData->lock);
  595. if(XPending(mData->xDisplay) <= 0)
  596. {
  597. // No more events, destroy any queued windows
  598. for(auto& entry : mData->toDestroy)
  599. XDestroyWindow(mData->xDisplay, entry);
  600. mData->toDestroy.clear();
  601. XSync(mData->xDisplay, false);
  602. break;
  603. }
  604. XEvent event;
  605. XNextEvent(mData->xDisplay, &event);
  606. switch (event.type)
  607. {
  608. case ClientMessage:
  609. {
  610. if(LinuxDragAndDrop::handleClientMessage(event.xclient))
  611. break;
  612. if((Atom)event.xclient.data.l[0] == mData->atomDeleteWindow)
  613. {
  614. // We queue the window for destruction as soon as we process all current events (since some of those
  615. // events could still refer to this window)
  616. mData->toDestroy.push_back(event.xclient.window);
  617. XUnmapWindow(mData->xDisplay, event.xclient.window);
  618. XSync(mData->xDisplay, false);
  619. }
  620. }
  621. break;
  622. case DestroyNotify:
  623. {
  624. LinuxWindow* window = getLinuxWindow(mData, event.xdestroywindow.window);
  625. if(window != nullptr)
  626. {
  627. CoreApplication::instance().quitRequested();
  628. window->_cleanUp();
  629. if (mData->mainXWindow == 0)
  630. return;
  631. }
  632. }
  633. break;
  634. case KeyPress:
  635. {
  636. // Process text input
  637. KeySym keySym = XkbKeycodeToKeysym(mData->xDisplay, (KeyCode)event.xkey.keycode, 0, 0);
  638. //// Check if input manager wants this event. If not, we process it.
  639. if(XFilterEvent(&event, None) == False)
  640. {
  641. // Don't consider Return key a character
  642. if(keySym != XK_Return)
  643. {
  644. Status status;
  645. char buffer[16];
  646. INT32 length = Xutf8LookupString(mData->IC, &event.xkey, buffer, sizeof(buffer), nullptr,
  647. &status);
  648. if (length > 0)
  649. {
  650. buffer[length] = '\0';
  651. U32String utfStr = UTF8::toUTF32(String(buffer));
  652. if (utfStr.length() > 0)
  653. onCharInput((UINT32) utfStr[0]);
  654. }
  655. }
  656. }
  657. // Handle input commands
  658. InputCommandType command = InputCommandType::Backspace;
  659. bool shift = (event.xkey.state & ShiftMask) != 0;
  660. if(parseInputCommand(keySym, shift, command))
  661. {
  662. if(!onInputCommand.empty())
  663. onInputCommand(command);
  664. }
  665. }
  666. break;
  667. case KeyRelease:
  668. // Do nothing
  669. break;
  670. case ButtonPress:
  671. {
  672. UINT32 button = event.xbutton.button;
  673. OSMouseButton mouseButton;
  674. bool validPress = false;
  675. switch(button)
  676. {
  677. case Button1:
  678. mouseButton = OSMouseButton::Left;
  679. validPress = true;
  680. break;
  681. case Button2:
  682. mouseButton = OSMouseButton::Middle;
  683. validPress = true;
  684. break;
  685. case Button3:
  686. mouseButton = OSMouseButton::Right;
  687. validPress = true;
  688. break;
  689. default:
  690. break;
  691. }
  692. if(validPress)
  693. {
  694. // Send event
  695. Vector2I pos;
  696. pos.x = event.xbutton.x_root;
  697. pos.y = event.xbutton.y_root;
  698. OSPointerButtonStates btnStates;
  699. btnStates.ctrl = (event.xbutton.state & ControlMask) != 0;
  700. btnStates.shift = (event.xbutton.state & ShiftMask) != 0;
  701. btnStates.mouseButtons[0] = (event.xbutton.state & Button1Mask) != 0;
  702. btnStates.mouseButtons[1] = (event.xbutton.state & Button2Mask) != 0;
  703. btnStates.mouseButtons[2] = (event.xbutton.state & Button3Mask) != 0;
  704. onCursorButtonPressed(pos, mouseButton, btnStates);
  705. // Handle double-click
  706. if(button == Button1)
  707. {
  708. if (event.xbutton.time < (mData->lastButtonPressTime + DOUBLE_CLICK_MS))
  709. {
  710. onCursorDoubleClick(pos, btnStates);
  711. mData->lastButtonPressTime = 0;
  712. }
  713. else
  714. mData->lastButtonPressTime = event.xbutton.time;
  715. }
  716. }
  717. // Handle window dragging for windows without a title bar
  718. if(button == Button1)
  719. {
  720. LinuxWindow* window = getLinuxWindow(mData, event.xbutton.window);
  721. if(window != nullptr)
  722. window->_dragStart(event.xbutton.x, event.xbutton.y);
  723. }
  724. break;
  725. }
  726. case ButtonRelease:
  727. {
  728. UINT32 button = event.xbutton.button;
  729. Vector2I pos;
  730. pos.x = event.xbutton.x_root;
  731. pos.y = event.xbutton.y_root;
  732. OSPointerButtonStates btnStates;
  733. btnStates.ctrl = (event.xbutton.state & ControlMask) != 0;
  734. btnStates.shift = (event.xbutton.state & ShiftMask) != 0;
  735. btnStates.mouseButtons[0] = (event.xbutton.state & Button1Mask) != 0;
  736. btnStates.mouseButtons[1] = (event.xbutton.state & Button2Mask) != 0;
  737. btnStates.mouseButtons[2] = (event.xbutton.state & Button3Mask) != 0;
  738. switch(button)
  739. {
  740. case Button1:
  741. onCursorButtonReleased(pos, OSMouseButton::Left, btnStates);
  742. break;
  743. case Button2:
  744. onCursorButtonReleased(pos, OSMouseButton::Middle, btnStates);
  745. break;
  746. case Button3:
  747. onCursorButtonReleased(pos, OSMouseButton::Right, btnStates);
  748. break;
  749. case Button4: // Vertical mouse wheel
  750. case Button5:
  751. {
  752. INT32 delta = button == Button4 ? 1 : -1;
  753. onMouseWheelScrolled((float)delta);
  754. }
  755. break;
  756. default:
  757. break;
  758. }
  759. // Handle window dragging for windows without a title bar
  760. if(button == Button1)
  761. {
  762. LinuxWindow* window = getLinuxWindow(mData, event.xbutton.window);
  763. if(window != nullptr)
  764. window->_dragEnd();
  765. }
  766. break;
  767. }
  768. case MotionNotify:
  769. {
  770. Vector2I pos;
  771. pos.x = event.xmotion.x_root;
  772. pos.y = event.xmotion.y_root;
  773. // Handle clipping if enabled
  774. if(clipCursor(mData, pos))
  775. _setCursorPosition(mData, pos);
  776. // Send event
  777. OSPointerButtonStates btnStates;
  778. btnStates.ctrl = (event.xmotion.state & ControlMask) != 0;
  779. btnStates.shift = (event.xmotion.state & ShiftMask) != 0;
  780. btnStates.mouseButtons[0] = (event.xmotion.state & Button1Mask) != 0;
  781. btnStates.mouseButtons[1] = (event.xmotion.state & Button2Mask) != 0;
  782. btnStates.mouseButtons[2] = (event.xmotion.state & Button3Mask) != 0;
  783. onCursorMoved(pos, btnStates);
  784. // Handle window dragging for windows without a title bar
  785. LinuxWindow* window = getLinuxWindow(mData, event.xmotion.window);
  786. if(window != nullptr)
  787. window->_dragUpdate(event.xmotion.x, event.xmotion.y);
  788. }
  789. break;
  790. case EnterNotify:
  791. // Do nothing
  792. break;
  793. case LeaveNotify:
  794. {
  795. if (event.xcrossing.mode == NotifyNormal)
  796. {
  797. Vector2I pos;
  798. pos.x = event.xcrossing.x_root;
  799. pos.y = event.xcrossing.y_root;
  800. if (clipCursor(mData, pos))
  801. _setCursorPosition(mData, pos);
  802. }
  803. ct::RenderWindow* renderWindow = getRenderWindow(mData, event.xcrossing.window);
  804. if(renderWindow != nullptr)
  805. renderWindow->_notifyMouseLeft();
  806. }
  807. break;
  808. case ConfigureNotify:
  809. {
  810. LinuxWindow* window = getLinuxWindow(mData, event.xconfigure.window);
  811. if(window != nullptr)
  812. {
  813. updateClipBounds(mData, window);
  814. ct::RenderWindow* renderWindow = (ct::RenderWindow*)window->_getUserData();
  815. if(renderWindow != nullptr)
  816. renderWindow->_windowMovedOrResized();
  817. }
  818. }
  819. break;
  820. case FocusIn:
  821. {
  822. // Update input context focus
  823. XSetICFocus(mData->IC);
  824. // Send event to render window
  825. ct::RenderWindow* renderWindow = getRenderWindow(mData, event.xfocus.window);
  826. // Not a render window, so it doesn't care about these events
  827. if (renderWindow != nullptr)
  828. {
  829. if (!renderWindow->getProperties().hasFocus)
  830. renderWindow->_windowFocusReceived();
  831. }
  832. }
  833. break;
  834. case FocusOut:
  835. {
  836. // Update input context focus
  837. XUnsetICFocus(mData->IC);
  838. // Send event to render window
  839. ct::RenderWindow* renderWindow = getRenderWindow(mData, event.xfocus.window);
  840. // Not a render window, so it doesn't care about these events
  841. if (renderWindow != nullptr)
  842. {
  843. if (renderWindow->getProperties().hasFocus)
  844. renderWindow->_windowFocusLost();
  845. }
  846. }
  847. break;
  848. case SelectionNotify:
  849. LinuxDragAndDrop::handleSelectionNotify(event.xselection);
  850. break;
  851. case SelectionRequest:
  852. {
  853. // Send the data saved by the last clipboard copy operation
  854. Atom compoundTextAtom = XInternAtom(mData->xDisplay, "COMPOUND_TEXT", 0);
  855. Atom utf8StringAtom = XInternAtom(mData->xDisplay, "UTF8_STRING", 0);
  856. Atom targetsAtom = XInternAtom(mData->xDisplay, "TARGETS", 0);
  857. XSelectionRequestEvent& selReq = event.xselectionrequest;
  858. XEvent response;
  859. if(selReq.target == XA_STRING || selReq.target == compoundTextAtom || selReq.target == utf8StringAtom)
  860. {
  861. String utf8data = UTF8::fromWide(mData->clipboardData);
  862. const UINT8* data = (const UINT8*)utf8data.c_str();
  863. INT32 dataLength = (INT32)utf8data.length();
  864. XChangeProperty(mData->xDisplay, selReq.requestor, selReq.property,
  865. selReq.target, 8, PropModeReplace, data, dataLength);
  866. response.xselection.property = selReq.property;
  867. }
  868. else if(selReq.target == targetsAtom)
  869. {
  870. Atom data[2];
  871. data[0] = utf8StringAtom;
  872. data[1] = XA_STRING;
  873. XChangeProperty (mData->xDisplay, selReq.requestor, selReq.property, selReq.target,
  874. 8, PropModeReplace, (unsigned char*)&data, sizeof (data));
  875. response.xselection.property = selReq.property;
  876. }
  877. else
  878. {
  879. response.xselection.property = None;
  880. }
  881. response.xselection.type = SelectionNotify;
  882. response.xselection.display = selReq.display;
  883. response.xselection.requestor = selReq.requestor;
  884. response.xselection.selection = selReq.selection;
  885. response.xselection.target = selReq.target;
  886. response.xselection.time = selReq.time;
  887. XSendEvent (mData->xDisplay, selReq.requestor, 0, 0, &response);
  888. XFlush (mData->xDisplay);
  889. }
  890. break;
  891. case PropertyNotify:
  892. // Report minimize, maximize and restore events
  893. if(event.xproperty.atom == mData->atomWmState)
  894. {
  895. Atom type;
  896. INT32 format;
  897. unsigned long count, bytesRemaining;
  898. UINT8* data = nullptr;
  899. INT32 result = XGetWindowProperty(mData->xDisplay, event.xproperty.window, mData->atomWmState,
  900. 0, 1024, False, AnyPropertyType, &type, &format,
  901. &count, &bytesRemaining, &data);
  902. if (result == Success)
  903. {
  904. ct::RenderWindow* renderWindow = getRenderWindow(mData, event.xproperty.window);
  905. // Not a render window, so it doesn't care about these events
  906. if(renderWindow == nullptr)
  907. continue;
  908. Atom* atoms = (Atom*)data;
  909. bool foundHorz = false;
  910. bool foundVert = false;
  911. for (unsigned long i = 0; i < count; i++)
  912. {
  913. if (atoms[i] == mData->atomWmStateMaxHorz) foundHorz = true;
  914. if (atoms[i] == mData->atomWmStateMaxVert) foundVert = true;
  915. if (foundVert && foundHorz)
  916. {
  917. if(event.xproperty.state == PropertyNewValue)
  918. renderWindow->_notifyMaximized();
  919. else
  920. renderWindow->_notifyRestored();
  921. }
  922. if(atoms[i] == mData->atomWmStateHidden)
  923. {
  924. if(event.xproperty.state == PropertyNewValue)
  925. renderWindow->_notifyMinimized();
  926. else
  927. renderWindow->_notifyRestored();
  928. }
  929. }
  930. XFree(atoms);
  931. }
  932. }
  933. break;
  934. default:
  935. break;
  936. }
  937. }
  938. }
  939. void Platform::_startUp()
  940. {
  941. Lock lock(mData->lock);
  942. mData->xDisplay = XOpenDisplay(nullptr);
  943. XSetErrorHandler(x11ErrorHandler);
  944. if(XSupportsLocale())
  945. {
  946. XSetLocaleModifiers("@im=none");
  947. mData->IM = XOpenIM(mData->xDisplay, nullptr, nullptr, nullptr);
  948. // Note: Currently our windows don't support pre-edit and status areas, which are used for more complex types
  949. // of character input. Later on it might be beneficial to support them.
  950. mData->IC = XCreateIC(mData->IM, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, nullptr);
  951. }
  952. mData->atomDeleteWindow = XInternAtom(mData->xDisplay, "WM_DELETE_WINDOW", False);
  953. mData->atomWmState = XInternAtom(mData->xDisplay, "_NET_WM_STATE", False);
  954. mData->atomWmStateHidden = XInternAtom(mData->xDisplay, "_NET_WM_STATE_HIDDEN", False);
  955. mData->atomWmStateMaxHorz = XInternAtom(mData->xDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
  956. mData->atomWmStateMaxVert = XInternAtom(mData->xDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", False);
  957. // Drag and drop
  958. LinuxDragAndDrop::startUp(mData->xDisplay);
  959. // Create empty cursor
  960. char data[1];
  961. memset(data, 0, sizeof(data));
  962. Pixmap pixmap = XCreateBitmapFromData(mData->xDisplay, DefaultRootWindow(mData->xDisplay), data, 1, 1);
  963. XColor color;
  964. color.red = color.green = color.blue = 0;
  965. mData->emptyCursor = XCreatePixmapCursor(mData->xDisplay, pixmap, pixmap, &color, &color, 0, 0);
  966. XFreePixmap(mData->xDisplay, pixmap);
  967. }
  968. void Platform::_update()
  969. {
  970. LinuxDragAndDrop::update();
  971. }
  972. void Platform::_coreUpdate()
  973. {
  974. _messagePump();
  975. }
  976. void Platform::_shutDown()
  977. {
  978. Lock lock(mData->lock);
  979. // Free empty cursor
  980. XFreeCursor(mData->xDisplay, mData->emptyCursor);
  981. mData->emptyCursor = None;
  982. // Shutdown drag and drop
  983. LinuxDragAndDrop::shutDown();
  984. if(mData->IC)
  985. {
  986. XDestroyIC(mData->IC);
  987. mData->IC = 0;
  988. }
  989. if(mData->IM)
  990. {
  991. XCloseIM(mData->IM);
  992. mData->IM = 0;
  993. }
  994. XCloseDisplay(mData->xDisplay);
  995. mData->xDisplay = nullptr;
  996. bs_delete(mData);
  997. mData = nullptr;
  998. }
  999. ::Display* LinuxPlatform::getXDisplay()
  1000. {
  1001. return mData->xDisplay;
  1002. }
  1003. ::Window LinuxPlatform::getMainXWindow()
  1004. {
  1005. return mData->mainXWindow;
  1006. }
  1007. void LinuxPlatform::lockX()
  1008. {
  1009. mData->lock.lock();
  1010. }
  1011. void LinuxPlatform::unlockX()
  1012. {
  1013. mData->lock.unlock();
  1014. }
  1015. void LinuxPlatform::_registerWindow(::Window xWindow, LinuxWindow* window)
  1016. {
  1017. // First window is assumed to be the main
  1018. if(mData->mainXWindow == 0)
  1019. {
  1020. mData->mainXWindow = xWindow;
  1021. // Input context client window must be set before use
  1022. XSetICValues(mData->IC,
  1023. XNClientWindow, xWindow,
  1024. XNFocusWindow, xWindow,
  1025. nullptr);
  1026. }
  1027. mData->windowMap[xWindow] = window;
  1028. applyCurrentCursor(mData, xWindow);
  1029. }
  1030. void LinuxPlatform::_unregisterWindow(::Window xWindow)
  1031. {
  1032. auto iterFind = mData->windowMap.find(xWindow);
  1033. if(iterFind != mData->windowMap.end())
  1034. {
  1035. if(mData->cursorClipEnabled && mData->cursorClipWindow == iterFind->second)
  1036. clipCursorDisable();
  1037. mData->windowMap.erase(iterFind);
  1038. }
  1039. if(mData->mainXWindow == xWindow)
  1040. mData->mainXWindow = 0;
  1041. }
  1042. Pixmap LinuxPlatform::createPixmap(const PixelData& data)
  1043. {
  1044. SPtr<PixelData> bgraData = PixelData::create(data.getWidth(), data.getHeight(), 1, PF_BGRA8);
  1045. PixelUtil::bulkPixelConversion(data, *bgraData);
  1046. UINT32 depth = (UINT32)XDefaultDepth(mData->xDisplay, 0);
  1047. XImage* image = XCreateImage(mData->xDisplay, XDefaultVisual(mData->xDisplay, 0), depth, ZPixmap, 0,
  1048. (char*)bgraData->getData(), data.getWidth(), data.getHeight(), 32, 0);
  1049. Pixmap pixmap = XCreatePixmap(mData->xDisplay, XDefaultRootWindow(mData->xDisplay),
  1050. data.getWidth(), data.getHeight(), depth);
  1051. XGCValues gcValues;
  1052. GC gc = XCreateGC(mData->xDisplay, pixmap, 0, &gcValues);
  1053. XPutImage(mData->xDisplay, pixmap, gc, image, 0, 0, 0, 0, data.getWidth(), data.getHeight());
  1054. XFreeGC(mData->xDisplay, gc);
  1055. // Make sure XDestroyImage doesn't free the data pointed to by 'data.bytes'
  1056. image->data = nullptr;
  1057. XDestroyImage(image);
  1058. return pixmap;
  1059. }
  1060. }