BsLinuxPlatform.cpp 34 KB

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