BsLinuxPlatform.cpp 41 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487
  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/BsLinuxInput.h"
  7. #include "Linux/BsLinuxPlatform.h"
  8. #include "Linux/BsLinuxWindow.h"
  9. #include "RenderAPI/BsRenderWindow.h"
  10. #include "BsLinuxDropTarget.h"
  11. #include "BsCoreApplication.h"
  12. #include <X11/X.h>
  13. #include <X11/Xatom.h>
  14. #include <X11/Xcursor/Xcursor.h>
  15. #include <X11/Xlib.h>
  16. #include <X11/XKBlib.h>
  17. #include <X11/extensions/XInput2.h>
  18. #include <pwd.h>
  19. namespace bs
  20. {
  21. Event<void(const Vector2I&, const OSPointerButtonStates&)> Platform::onCursorMoved;
  22. Event<void(const Vector2I&, OSMouseButton button, const OSPointerButtonStates&)> Platform::onCursorButtonPressed;
  23. Event<void(const Vector2I&, OSMouseButton button, const OSPointerButtonStates&)> Platform::onCursorButtonReleased;
  24. Event<void(const Vector2I&, const OSPointerButtonStates&)> Platform::onCursorDoubleClick;
  25. Event<void(InputCommandType)> Platform::onInputCommand;
  26. Event<void(float)> Platform::onMouseWheelScrolled;
  27. Event<void(UINT32)> Platform::onCharInput;
  28. Event<void()> Platform::onMouseCaptureChanged;
  29. Mutex LinuxPlatform::eventLock;
  30. Queue<LinuxButtonEvent> LinuxPlatform::buttonEvents;
  31. LinuxMouseMotionEvent LinuxPlatform::mouseMotionEvent;
  32. enum class X11CursorType
  33. {
  34. Arrow,
  35. ArrowDrag,
  36. ArrowLeftRight,
  37. Wait,
  38. IBeam,
  39. SizeTopLeft,
  40. SizeTopRight,
  41. SizeBotLeft,
  42. SizeBotRight,
  43. SizeLeft,
  44. SizeRight,
  45. SizeTop,
  46. SizeBottom,
  47. Deny,
  48. Count
  49. };
  50. struct Platform::Pimpl
  51. {
  52. ::Display* xDisplay = nullptr;
  53. ::Window mainXWindow = 0;
  54. ::Window fullscreenXWindow = 0;
  55. UnorderedMap<::Window, LinuxWindow*> windowMap;
  56. Mutex lock;
  57. XIM IM;
  58. XIC IC;
  59. Time lastButtonPressTime;
  60. Atom atomDeleteWindow;
  61. Atom atomWmState;
  62. Atom atomWmStateHidden;
  63. Atom atomWmStateMaxVert;
  64. Atom atomWmStateMaxHorz;
  65. // X11 Event handling
  66. int xInput2Opcode;
  67. UnorderedMap<String, KeyCode> keyNameMap; /**< Maps X11 key name (e.g. "TAB") to system-specific X11 KeyCode. */
  68. Vector<ButtonCode> keyCodeMap; /**< Maps system-specific X11 KeyCode to Banshee ButtonCode. */
  69. // Clipboard
  70. WString clipboardData;
  71. // Cursor
  72. ::Cursor currentCursor = None;
  73. ::Cursor emptyCursor = None;
  74. bool isCursorHidden = false;
  75. Rect2I cursorClipRect;
  76. LinuxWindow* cursorClipWindow = nullptr;
  77. bool cursorClipEnabled = false;
  78. };
  79. static const UINT32 DOUBLE_CLICK_MS = 500;
  80. Vector2I _getCursorPosition(Platform::Pimpl* data)
  81. {
  82. Vector2I pos;
  83. UINT32 screenCount = (UINT32)XScreenCount(data->xDisplay);
  84. for (UINT32 i = 0; i < screenCount; ++i)
  85. {
  86. ::Window outRoot, outChild;
  87. INT32 childX, childY;
  88. UINT32 mask;
  89. if(XQueryPointer(data->xDisplay, XRootWindow(data->xDisplay, i), &outRoot, &outChild, &pos.x,
  90. &pos.y, &childX, &childY, &mask))
  91. break;
  92. }
  93. return pos;
  94. }
  95. void _setCursorPosition(Platform::Pimpl* data, const Vector2I& screenPos)
  96. {
  97. UINT32 screenCount = (UINT32)XScreenCount(data->xDisplay);
  98. // Note assuming screens are laid out horizontally left to right
  99. INT32 screenX = 0;
  100. for(UINT32 i = 0; i < screenCount; ++i)
  101. {
  102. ::Window root = XRootWindow(data->xDisplay, i);
  103. INT32 screenXEnd = screenX + XDisplayWidth(data->xDisplay, i);
  104. if(screenPos.x >= screenX && screenPos.x < screenXEnd)
  105. {
  106. XWarpPointer(data->xDisplay, None, root, 0, 0, 0, 0, screenPos.x, screenPos.y);
  107. XFlush(data->xDisplay);
  108. return;
  109. }
  110. screenX = screenXEnd;
  111. }
  112. }
  113. void applyCurrentCursor(Platform::Pimpl* data, ::Window window)
  114. {
  115. if(data->isCursorHidden)
  116. XDefineCursor(data->xDisplay, window, data->emptyCursor);
  117. else
  118. {
  119. if (data->currentCursor != None)
  120. XDefineCursor(data->xDisplay, window, data->currentCursor);
  121. else
  122. XUndefineCursor(data->xDisplay, window);
  123. }
  124. }
  125. void updateClipBounds(Platform::Pimpl* data, LinuxWindow* window)
  126. {
  127. if(!data->cursorClipEnabled || data->cursorClipWindow != window)
  128. return;
  129. data->cursorClipRect.x = window->getLeft();
  130. data->cursorClipRect.y = window->getTop();
  131. data->cursorClipRect.width = window->getWidth();
  132. data->cursorClipRect.height = window->getHeight();
  133. }
  134. bool clipCursor(Platform::Pimpl* data, Vector2I& pos)
  135. {
  136. if(!data->cursorClipEnabled)
  137. return false;
  138. INT32 clippedX = pos.x - data->cursorClipRect.x;
  139. INT32 clippedY = pos.y - data->cursorClipRect.y;
  140. if(clippedX < 0)
  141. clippedX = 0;
  142. else if(clippedX >= (INT32)data->cursorClipRect.width)
  143. clippedX = data->cursorClipRect.width > 0 ? data->cursorClipRect.width - 1 : 0;
  144. if(clippedY < 0)
  145. clippedY = 0;
  146. else if(clippedY >= (INT32)data->cursorClipRect.height)
  147. clippedY = data->cursorClipRect.height > 0 ? data->cursorClipRect.height - 1 : 0;
  148. clippedX += data->cursorClipRect.x;
  149. clippedY += data->cursorClipRect.y;
  150. if(clippedX != pos.x || clippedY != pos.y)
  151. {
  152. pos.x = clippedX;
  153. pos.y = clippedY;
  154. return true;
  155. }
  156. return false;
  157. }
  158. void setCurrentCursor(Platform::Pimpl* data, ::Cursor cursor)
  159. {
  160. if(data->currentCursor)
  161. XFreeCursor(data->xDisplay, data->currentCursor);
  162. data->currentCursor = cursor;
  163. for(auto& entry : data->windowMap)
  164. applyCurrentCursor(data, entry.first);
  165. }
  166. /**
  167. * Searches the window hierarchy, from top to bottom, looking for the top-most window that contains the specified
  168. * point. Returns 0 if one is not found.
  169. */
  170. ::Window getWindowUnderPoint(::Display* display, ::Window rootWindow, ::Window window, const Vector2I& screenPos)
  171. {
  172. ::Window outRoot, outParent;
  173. ::Window* children;
  174. UINT32 numChildren;
  175. XQueryTree(display, window, &outRoot, &outParent, &children, &numChildren);
  176. if(children == nullptr || numChildren == 0)
  177. return window;
  178. for(UINT32 j = 0; j < numChildren; j++)
  179. {
  180. ::Window curWindow = children[numChildren - j - 1];
  181. XWindowAttributes xwa;
  182. XGetWindowAttributes(display, curWindow, &xwa);
  183. if(xwa.map_state != IsViewable || xwa.c_class != InputOutput)
  184. continue;
  185. // Get position in root window coordinates
  186. ::Window outChild;
  187. Vector2I pos;
  188. if(!XTranslateCoordinates(display, curWindow, rootWindow, 0, 0, &pos.x, &pos.y, &outChild))
  189. continue;
  190. Rect2I area(pos.x, pos.y, (UINT32)xwa.width, (UINT32)xwa.height);
  191. if(area.contains(screenPos))
  192. {
  193. XFree(children);
  194. return getWindowUnderPoint(display, rootWindow, curWindow, screenPos);
  195. }
  196. }
  197. XFree(children);
  198. return 0;
  199. }
  200. int x11ErrorHandler(::Display* display, XErrorEvent* event)
  201. {
  202. // X11 by default crashes the app on error, even though some errors can be just fine. So we provide our own handler.
  203. char buffer[256];
  204. XGetErrorText(display, event->error_code, buffer, sizeof(buffer));
  205. LOGWRN("X11 error: " + String(buffer));
  206. return 0;
  207. }
  208. Platform::Pimpl* Platform::mData = bs_new<Platform::Pimpl>();
  209. Platform::~Platform()
  210. { }
  211. Vector2I Platform::getCursorPosition()
  212. {
  213. Lock lock(mData->lock);
  214. return _getCursorPosition(mData);
  215. }
  216. void Platform::setCursorPosition(const Vector2I& screenPos)
  217. {
  218. Lock lock(mData->lock);
  219. _setCursorPosition(mData, screenPos);
  220. }
  221. void Platform::captureMouse(const RenderWindow& window)
  222. {
  223. Lock lock(mData->lock);
  224. LinuxWindow* linuxWindow;
  225. window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
  226. UINT32 mask = ButtonPressMask | ButtonReleaseMask | PointerMotionMask | FocusChangeMask;
  227. XGrabPointer(mData->xDisplay, linuxWindow->_getXWindow(), False, mask, GrabModeAsync,
  228. GrabModeAsync, None, None, CurrentTime);
  229. XSync(mData->xDisplay, False);
  230. }
  231. void Platform::releaseMouseCapture()
  232. {
  233. Lock lock(mData->lock);
  234. XUngrabPointer(mData->xDisplay, CurrentTime);
  235. XSync(mData->xDisplay, False);
  236. }
  237. bool Platform::isPointOverWindow(const RenderWindow& window, const Vector2I& screenPos)
  238. {
  239. Lock lock(mData->lock);
  240. LinuxWindow* linuxWindow;
  241. window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
  242. ::Window xWindow = linuxWindow->_getXWindow();
  243. UINT32 screenCount = (UINT32)XScreenCount(mData->xDisplay);
  244. for (UINT32 i = 0; i < screenCount; ++i)
  245. {
  246. ::Window rootWindow = XRootWindow(mData->xDisplay, i);
  247. ::Window curWindow = getWindowUnderPoint(mData->xDisplay, rootWindow, rootWindow, screenPos);
  248. return curWindow == xWindow;
  249. }
  250. return false;
  251. }
  252. void Platform::hideCursor()
  253. {
  254. Lock lock(mData->lock);
  255. mData->isCursorHidden = true;
  256. for(auto& entry : mData->windowMap)
  257. applyCurrentCursor(mData, entry.first);
  258. }
  259. void Platform::showCursor()
  260. {
  261. Lock lock(mData->lock);
  262. mData->isCursorHidden = false;
  263. for(auto& entry : mData->windowMap)
  264. applyCurrentCursor(mData, entry.first);
  265. }
  266. bool Platform::isCursorHidden()
  267. {
  268. Lock lock(mData->lock);
  269. return mData->isCursorHidden;
  270. }
  271. void Platform::clipCursorToWindow(const RenderWindow& window)
  272. {
  273. Lock lock(mData->lock);
  274. LinuxWindow* linuxWindow;
  275. window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
  276. mData->cursorClipEnabled = true;
  277. mData->cursorClipWindow = linuxWindow;
  278. updateClipBounds(mData, linuxWindow);
  279. Vector2I pos = _getCursorPosition(mData);
  280. if(clipCursor(mData, pos))
  281. _setCursorPosition(mData, pos);
  282. }
  283. void Platform::clipCursorToRect(const Rect2I& screenRect)
  284. {
  285. Lock lock(mData->lock);
  286. mData->cursorClipEnabled = true;
  287. mData->cursorClipRect = screenRect;
  288. mData->cursorClipWindow = nullptr;
  289. Vector2I pos = _getCursorPosition(mData);
  290. if(clipCursor(mData, pos))
  291. _setCursorPosition(mData, pos);
  292. }
  293. void Platform::clipCursorDisable()
  294. {
  295. Lock lock(mData->lock);
  296. mData->cursorClipEnabled = false;
  297. mData->cursorClipWindow = None;
  298. }
  299. void Platform::setCursor(PixelData& pixelData, const Vector2I& hotSpot)
  300. {
  301. SPtr<PixelData> bgraData = PixelData::create(pixelData.getWidth(), pixelData.getHeight(), 1, PF_BGRA8);
  302. PixelUtil::bulkPixelConversion(pixelData, *bgraData);
  303. Lock lock(mData->lock);
  304. XcursorImage* image = XcursorImageCreate((int)bgraData->getWidth(), (int)bgraData->getHeight());
  305. image->xhot = (XcursorDim)hotSpot.x;
  306. image->yhot = (XcursorDim)hotSpot.y;
  307. image->delay = 0;
  308. memcpy(image->pixels, bgraData->getData(), bgraData->getSize());
  309. ::Cursor cursor = XcursorImageLoadCursor(mData->xDisplay, image);
  310. XcursorImageDestroy(image);
  311. setCurrentCursor(mData, cursor);
  312. }
  313. void Platform::setIcon(const PixelData& pixelData)
  314. {
  315. SPtr<PixelData> resizedData = PixelData::create(32, 32, 1, PF_RGBA8);
  316. PixelUtil::scale(pixelData, *resizedData);
  317. if(!mData->mainXWindow)
  318. return;
  319. auto iterFind = mData->windowMap.find(mData->mainXWindow);
  320. if(iterFind == mData->windowMap.end())
  321. return;
  322. LinuxWindow* mainLinuxWindow = iterFind->second;
  323. Lock lock(mData->lock);
  324. mainLinuxWindow->setIcon(pixelData);
  325. }
  326. void Platform::setCaptionNonClientAreas(const ct::RenderWindow& window, const Vector<Rect2I>& nonClientAreas)
  327. {
  328. if(nonClientAreas.size() == 0)
  329. return;
  330. Lock lock(mData->lock);
  331. LinuxWindow* linuxWindow;
  332. window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
  333. linuxWindow->_setDragZones(nonClientAreas);
  334. }
  335. void Platform::setResizeNonClientAreas(const ct::RenderWindow& window, const Vector<NonClientResizeArea>& nonClientAreas)
  336. {
  337. // Do nothing, resize areas not supported on Linux (but they are provided even on undecorated windows by the WM)
  338. }
  339. void Platform::resetNonClientAreas(const ct::RenderWindow& window)
  340. {
  341. Lock lock(mData->lock);
  342. LinuxWindow* linuxWindow;
  343. window.getCustomAttribute("LINUX_WINDOW", &linuxWindow);
  344. linuxWindow->_setDragZones({});
  345. }
  346. void Platform::sleep(UINT32 duration)
  347. {
  348. usleep(duration * 1000);
  349. }
  350. void Platform::copyToClipboard(const WString& string)
  351. {
  352. Lock lock(mData->lock);
  353. mData->clipboardData = string;
  354. Atom clipboardAtom = XInternAtom(mData->xDisplay, "CLIPBOARD", 0);
  355. XSetSelectionOwner(mData->xDisplay, clipboardAtom, mData->mainXWindow, CurrentTime);
  356. }
  357. WString Platform::copyFromClipboard()
  358. {
  359. Lock lock(mData->lock);
  360. Atom clipboardAtom = XInternAtom(mData->xDisplay, "CLIPBOARD", 0);
  361. ::Window selOwner = XGetSelectionOwner(mData->xDisplay, clipboardAtom);
  362. if(selOwner == None)
  363. return L"";
  364. if(selOwner == mData->mainXWindow)
  365. return mData->clipboardData;
  366. XConvertSelection(mData->xDisplay, clipboardAtom, XA_STRING, clipboardAtom, mData->mainXWindow,
  367. CurrentTime);
  368. XFlush(mData->xDisplay);
  369. // Note: This might discard events if there are any in between the one we need. Ideally we let the
  370. // processEvents() handle them
  371. while(true)
  372. {
  373. XEvent event;
  374. XNextEvent(mData->xDisplay, &event);
  375. if(event.type == SelectionNotify && event.xselection.requestor == mData->mainXWindow)
  376. break;
  377. }
  378. Atom actualType;
  379. INT32 actualFormat;
  380. unsigned long length;
  381. unsigned long bytesRemaining;
  382. UINT8* data;
  383. XGetWindowProperty(mData->xDisplay, mData->mainXWindow, clipboardAtom,
  384. 0, 0, False, AnyPropertyType, &actualType, &actualFormat, &length, &bytesRemaining, &data);
  385. if(bytesRemaining > 0)
  386. {
  387. unsigned long unused;
  388. INT32 result = XGetWindowProperty(mData->xDisplay, mData->mainXWindow, clipboardAtom,
  389. 0, bytesRemaining, False, AnyPropertyType, &actualType, &actualFormat, &length,
  390. &unused, &data);
  391. if(result == Success)
  392. return UTF8::toWide(String((const char*)data));
  393. XFree(data);
  394. }
  395. return L"";
  396. }
  397. /** Maps X11 mouse button codes to Banshee button codes. */
  398. ButtonCode xButtonToButtonCode(int button)
  399. {
  400. switch (button)
  401. {
  402. case Button1:
  403. return BC_MOUSE_LEFT;
  404. case Button2:
  405. return BC_MOUSE_MIDDLE;
  406. case Button3:
  407. return BC_MOUSE_RIGHT;
  408. default:
  409. return (ButtonCode)(BC_MOUSE_LEFT + button - 1);
  410. }
  411. }
  412. /** Maps Banshee button codes to X11 names for physical key locations. */
  413. const char* buttonCodeToKeyName(ButtonCode code)
  414. {
  415. switch(code)
  416. {
  417. // Row #1
  418. case BC_ESCAPE: return "ESC";
  419. case BC_F1: return "FK01";
  420. case BC_F2: return "FK02";
  421. case BC_F3: return "FK03";
  422. case BC_F4: return "FK04";
  423. case BC_F5: return "FK05";
  424. case BC_F6: return "FK06";
  425. case BC_F7: return "FK07";
  426. case BC_F8: return "FK08";
  427. case BC_F9: return "FK09";
  428. case BC_F10: return "FK10";
  429. case BC_F11: return "FK11";
  430. case BC_F12: return "FK12";
  431. case BC_F13: return "FK13";
  432. case BC_F14: return "FK14";
  433. case BC_F15: return "FK15";
  434. // Row #2
  435. case BC_GRAVE: return "TLDE";
  436. case BC_1: return "AE01";
  437. case BC_2: return "AE02";
  438. case BC_3: return "AE03";
  439. case BC_4: return "AE04";
  440. case BC_5: return "AE05";
  441. case BC_6: return "AE06";
  442. case BC_7: return "AE07";
  443. case BC_8: return "AE08";
  444. case BC_9: return "AE09";
  445. case BC_0: return "AE10";
  446. case BC_MINUS: return "AE11";
  447. case BC_EQUALS: return "AE12";
  448. case BC_BACK: return "BKSP";
  449. // Row #3
  450. case BC_TAB: return "TAB";
  451. case BC_Q: return "AD01";
  452. case BC_W: return "AD02";
  453. case BC_E: return "AD03";
  454. case BC_R: return "AD04";
  455. case BC_T: return "AD05";
  456. case BC_Y: return "AD06";
  457. case BC_U: return "AD07";
  458. case BC_I: return "AD08";
  459. case BC_O: return "AD09";
  460. case BC_P: return "AD10";
  461. case BC_LBRACKET: return "AD11";
  462. case BC_RBRACKET: return "AD12";
  463. case BC_RETURN: return "RTRN";
  464. // Row #4
  465. case BC_CAPITAL: return "CAPS";
  466. case BC_A: return "AC01";
  467. case BC_S: return "AC02";
  468. case BC_D: return "AC03";
  469. case BC_F: return "AC04";
  470. case BC_G: return "AC05";
  471. case BC_H: return "AC06";
  472. case BC_J: return "AC07";
  473. case BC_K: return "AC08";
  474. case BC_L: return "AC09";
  475. case BC_SEMICOLON: return "AC10";
  476. case BC_APOSTROPHE: return "AC11";
  477. case BC_BACKSLASH: return "BKSL";
  478. // Row #5
  479. case BC_LSHIFT: return "LFSH";
  480. case BC_Z: return "AB01";
  481. case BC_X: return "AB02";
  482. case BC_C: return "AB03";
  483. case BC_V: return "AB04";
  484. case BC_B: return "AB05";
  485. case BC_N: return "AB06";
  486. case BC_M: return "AB07";
  487. case BC_COMMA: return "AB08";
  488. case BC_PERIOD: return "AB09";
  489. case BC_SLASH: return "AB10";
  490. case BC_RSHIFT: return "RTSH";
  491. // Row #6
  492. case BC_LCONTROL: return "LCTL";
  493. case BC_LWIN: return "LWIN";
  494. case BC_LMENU: return "LALT";
  495. case BC_SPACE: return "SPCE";
  496. case BC_RMENU: return "RALT";
  497. case BC_RWIN: return "RWIN";
  498. case BC_RCONTROL: return "RCTL";
  499. // Keypad
  500. case BC_NUMPAD0: return "KP0";
  501. case BC_NUMPAD1: return "KP1";
  502. case BC_NUMPAD2: return "KP2";
  503. case BC_NUMPAD3: return "KP3";
  504. case BC_NUMPAD4: return "KP4";
  505. case BC_NUMPAD5: return "KP5";
  506. case BC_NUMPAD6: return "KP6";
  507. case BC_NUMPAD7: return "KP7";
  508. case BC_NUMPAD8: return "KP8";
  509. case BC_NUMPAD9: return "KP9";
  510. case BC_NUMLOCK: return "NMLK";
  511. case BC_DIVIDE: return "KPDV";
  512. case BC_MULTIPLY: return "KPMU";
  513. case BC_SUBTRACT: return "KPSU";
  514. case BC_ADD: return "KPAD";
  515. case BC_DECIMAL: return "KPDL";
  516. case BC_NUMPADENTER: return "KPEN";
  517. case BC_NUMPADEQUALS: return "KPEQ";
  518. // Special keys
  519. case BC_SCROLL: return "SCLK";
  520. case BC_PAUSE: return "PAUS";
  521. case BC_INSERT: return "INS";
  522. case BC_HOME: return "HOME";
  523. case BC_PGUP: return "PGUP";
  524. case BC_DELETE: return "DELE";
  525. case BC_END: return "END";
  526. case BC_PGDOWN: return "PGDN";
  527. case BC_UP: return "UP";
  528. case BC_LEFT: return "LEFT";
  529. case BC_DOWN: return "DOWN";
  530. case BC_RIGHT: return "RGHT";
  531. case BC_MUTE: return "MUTE";
  532. case BC_VOLUMEDOWN: return "VOL-";
  533. case BC_VOLUMEUP: return "VOL+";
  534. case BC_POWER: return "POWR";
  535. // International keys
  536. case BC_OEM_102: return "LSGT"; // German keyboard: < > |
  537. case BC_KANA: return "AB11"; // Taking a guess here, many layouts map <AB11> to "kana_RO"
  538. case BC_YEN: return "AE13"; // Taking a guess, often mapped to yen
  539. default:
  540. // Missing Japanese (?): KATA, HIRA, HENK, MUHE, JPCM
  541. // Missing Korean (?): HNGL, HJCV
  542. // Missing because it's not clear which BC_ is correct: PRSC (print screen), LVL3 (AltGr), MENU
  543. // Misc: LNFD (line feed), I120, I126, I128, I129, COMP, STOP, AGAI (redo), PROP, UNDO, FRNT, COPY, OPEN, PAST
  544. // FIND, CUT, HELP, I147-I190, FK16-FK24, MDSW (mode switch), ALT, META, SUPR, HYPR, I208-I253
  545. break;
  546. }
  547. return nullptr;
  548. }
  549. WString Platform::keyCodeToUnicode(UINT32 buttonCode)
  550. {
  551. Lock lock(mData->lock);
  552. const char* keyName = buttonCodeToKeyName((ButtonCode)buttonCode);
  553. if(keyName == nullptr)
  554. {
  555. // Not a printable key
  556. return L"";
  557. }
  558. auto iterFind = mData->keyNameMap.find(String(keyName));
  559. if(iterFind == mData->keyNameMap.end())
  560. {
  561. // Cannot find mapping, although this shouldn't really happen
  562. return L"";
  563. }
  564. XKeyPressedEvent event;
  565. bs_zero_out(event);
  566. event.type = KeyPress;
  567. event.keycode = iterFind->second;
  568. event.display = mData->xDisplay;
  569. event.time = CurrentTime;
  570. event.window = mData->mainXWindow;
  571. event.root = RootWindow(mData->xDisplay, XDefaultScreen(mData->xDisplay));
  572. Status status;
  573. char buffer[16];
  574. INT32 length = Xutf8LookupString(mData->IC, &event, buffer, sizeof(buffer), nullptr, &status);
  575. if(length > 0)
  576. {
  577. buffer[length] = '\0';
  578. return UTF8::toWide(String(buffer));
  579. }
  580. return L"";
  581. }
  582. void Platform::openFolder(const Path& path)
  583. {
  584. String pathString = path.toString();
  585. const char* commandPattern = "xdg-open '%s'";
  586. char* commandStr = (char*)bs_stack_alloc((UINT32)pathString.size() + (UINT32)strlen(commandPattern) + 1);
  587. sprintf(commandStr, commandPattern, pathString.c_str());
  588. (void)system(commandStr);
  589. bs_stack_free(commandStr);
  590. }
  591. /**
  592. * Converts an X11 KeySym code into an input command, if possible. Returns true if conversion was done.
  593. *
  594. * @param[in] keySym KeySym to try to translate to a command.
  595. * @param[in] shift True if the shift key was held down when the key was pressed.
  596. * @param[out] command Input command. Only valid if function returns true.
  597. * @return True if the KeySym is an input command.
  598. */
  599. bool parseInputCommand(KeySym keySym, bool shift, InputCommandType& command)
  600. {
  601. switch (keySym)
  602. {
  603. case XK_Left:
  604. command = shift ? InputCommandType::SelectLeft : InputCommandType::CursorMoveLeft;
  605. return true;
  606. case XK_Right:
  607. command = shift ? InputCommandType::SelectRight : InputCommandType::CursorMoveRight;
  608. return true;
  609. case XK_Up:
  610. command = shift ? InputCommandType::SelectUp : InputCommandType::CursorMoveUp;
  611. return true;
  612. case XK_Down:
  613. command = shift ? InputCommandType::SelectDown : InputCommandType::CursorMoveDown;
  614. return true;
  615. case XK_Escape:
  616. command = InputCommandType::Escape;
  617. return true;
  618. case XK_Return:
  619. command = shift ? InputCommandType::Return : InputCommandType::Confirm;
  620. return true;
  621. case XK_BackSpace:
  622. command = InputCommandType::Backspace;
  623. return true;
  624. case XK_Delete:
  625. command = InputCommandType::Delete;
  626. return true;
  627. }
  628. return false;
  629. }
  630. /** Returns a LinuxWindow from a native X11 window handle. */
  631. LinuxWindow* getLinuxWindow(LinuxPlatform::Pimpl* data, ::Window xWindow)
  632. {
  633. auto iterFind = data->windowMap.find(xWindow);
  634. if (iterFind != data->windowMap.end())
  635. {
  636. LinuxWindow* window = iterFind->second;
  637. return window;
  638. }
  639. return nullptr;
  640. }
  641. /** Returns a RenderWindow from a native X11 window handle. Returns null if the window isn't a RenderWindow */
  642. ct::RenderWindow* getRenderWindow(LinuxPlatform::Pimpl* data, ::Window xWindow)
  643. {
  644. LinuxWindow* linuxWindow = getLinuxWindow(data, xWindow);
  645. if(linuxWindow != nullptr)
  646. return (ct::RenderWindow*)linuxWindow->_getUserData();
  647. return nullptr;
  648. }
  649. /**
  650. * Enqueue a button press/release event to be handled by the main thread
  651. *
  652. * @param bc ButtonCode for the button that was pressed or released
  653. * @param pressed true if the button was pressed, false if it was released
  654. * @param timestamp Time when the event happened
  655. */
  656. void enqueueButtonEvent(ButtonCode bc, bool pressed, UINT64 timestamp)
  657. {
  658. if (bc == BC_UNASSIGNED)
  659. return;
  660. Lock eventLock(LinuxPlatform::eventLock);
  661. LinuxButtonEvent event;
  662. event.button = bc;
  663. event.pressed = pressed;
  664. event.timestamp = timestamp;
  665. LinuxPlatform::buttonEvents.push(event);
  666. }
  667. void Platform::_messagePump()
  668. {
  669. while(true)
  670. {
  671. Lock lock(mData->lock);
  672. if(XPending(mData->xDisplay) <= 0)
  673. break;
  674. XEvent event;
  675. XNextEvent(mData->xDisplay, &event);
  676. XGenericEventCookie* cookie = &event.xcookie;
  677. if (cookie->type == GenericEvent && cookie->extension == mData->xInput2Opcode)
  678. {
  679. XGetEventData(mData->xDisplay, cookie);
  680. XIRawEvent* xInput2Event = (XIRawEvent*) cookie->data;
  681. switch (xInput2Event->evtype)
  682. {
  683. case XI_RawMotion:
  684. if (xInput2Event->valuators.mask_len > 0)
  685. {
  686. // Assume X/Y delta is stored in valuators 0/1 and vertical scroll in valuator 3.
  687. // While there is an API that reliably tells us the valuator index for vertical scroll, there's
  688. // nothing "more reliable" for X/Y axes, as the only way to possibly identify them from device
  689. // info is by axis name, so we can use the axis index directly just as well. GDK seems to assume
  690. // 0 for x and 1 for y too, so that's hopefully safe, and 3 appears to be common for the scroll
  691. // wheel.
  692. float deltas[4] = {0};
  693. int currentValuesIndex = 0;
  694. for (unsigned int valuator = 0; valuator < 4; valuator++)
  695. if (XIMaskIsSet(xInput2Event->valuators.mask, valuator))
  696. deltas[valuator] = xInput2Event->raw_values[currentValuesIndex++];
  697. Lock eventLock(LinuxPlatform::eventLock);
  698. LinuxPlatform::mouseMotionEvent.deltaX += deltas[0];
  699. LinuxPlatform::mouseMotionEvent.deltaY += deltas[1];
  700. LinuxPlatform::mouseMotionEvent.deltaZ += deltas[3]; // Not a typo - 2 is for horizontal scroll.
  701. }
  702. break;
  703. }
  704. XFreeEventData(mData->xDisplay, cookie);
  705. }
  706. switch (event.type)
  707. {
  708. case ClientMessage:
  709. {
  710. if(LinuxDragAndDrop::handleClientMessage(event.xclient))
  711. break;
  712. // User requested the window to close
  713. if((Atom)event.xclient.data.l[0] == mData->atomDeleteWindow)
  714. {
  715. LinuxWindow* window = getLinuxWindow(mData, event.xclient.window);
  716. if(window != nullptr)
  717. {
  718. // If it's a render window we allow the client code to handle the message
  719. ct::RenderWindow* renderWindow = (ct::RenderWindow*)window->_getUserData();
  720. if(renderWindow != nullptr)
  721. renderWindow->_notifyCloseRequested();
  722. else // If not, we just destroy the window
  723. window->_destroy();
  724. }
  725. }
  726. }
  727. break;
  728. case KeyPress:
  729. {
  730. XKeyPressedEvent* keyEvent = (XKeyPressedEvent*) &event;
  731. enqueueButtonEvent(mData->keyCodeMap[keyEvent->keycode], true, (UINT64) keyEvent->time);
  732. // Process text input
  733. KeySym keySym = XkbKeycodeToKeysym(mData->xDisplay, (KeyCode)event.xkey.keycode, 0, 0);
  734. //// Check if input manager wants this event. If not, we process it.
  735. if(XFilterEvent(&event, None) == False)
  736. {
  737. // Don't consider Return key a character
  738. if(keySym != XK_Return)
  739. {
  740. Status status;
  741. char buffer[16];
  742. INT32 length = Xutf8LookupString(mData->IC, &event.xkey, buffer, sizeof(buffer), nullptr,
  743. &status);
  744. if (length > 0)
  745. {
  746. buffer[length] = '\0';
  747. U32String utfStr = UTF8::toUTF32(String(buffer));
  748. if (utfStr.length() > 0)
  749. onCharInput((UINT32) utfStr[0]);
  750. }
  751. }
  752. }
  753. // Handle input commands
  754. InputCommandType command = InputCommandType::Backspace;
  755. bool shift = (event.xkey.state & ShiftMask) != 0;
  756. if(parseInputCommand(keySym, shift, command))
  757. {
  758. if(!onInputCommand.empty())
  759. onInputCommand(command);
  760. }
  761. }
  762. break;
  763. case KeyRelease:
  764. {
  765. XKeyReleasedEvent* keyEvent = (XKeyReleasedEvent*) &event;
  766. enqueueButtonEvent(mData->keyCodeMap[keyEvent->keycode], false, (UINT64) keyEvent->time);
  767. }
  768. break;
  769. case ButtonPress:
  770. {
  771. XButtonPressedEvent* buttonEvent = (XButtonPressedEvent*) &event;
  772. UINT32 button = event.xbutton.button;
  773. enqueueButtonEvent(xButtonToButtonCode(button), true, (UINT64) buttonEvent->time);
  774. OSPointerButtonStates btnStates;
  775. btnStates.mouseButtons[0] = (event.xbutton.state & Button1Mask) != 0;
  776. btnStates.mouseButtons[1] = (event.xbutton.state & Button2Mask) != 0;
  777. btnStates.mouseButtons[2] = (event.xbutton.state & Button3Mask) != 0;
  778. OSMouseButton mouseButton;
  779. bool validPress = false;
  780. switch(button)
  781. {
  782. case Button1:
  783. mouseButton = OSMouseButton::Left;
  784. btnStates.mouseButtons[0] = true;
  785. validPress = true;
  786. break;
  787. case Button2:
  788. mouseButton = OSMouseButton::Middle;
  789. btnStates.mouseButtons[1] = true;
  790. validPress = true;
  791. break;
  792. case Button3:
  793. mouseButton = OSMouseButton::Right;
  794. btnStates.mouseButtons[2] = true;
  795. validPress = true;
  796. break;
  797. default:
  798. break;
  799. }
  800. if(validPress)
  801. {
  802. // Send event
  803. Vector2I pos;
  804. pos.x = event.xbutton.x_root;
  805. pos.y = event.xbutton.y_root;
  806. btnStates.ctrl = (event.xbutton.state & ControlMask) != 0;
  807. btnStates.shift = (event.xbutton.state & ShiftMask) != 0;
  808. onCursorButtonPressed(pos, mouseButton, btnStates);
  809. // Handle double-click
  810. if(button == Button1)
  811. {
  812. if (event.xbutton.time < (mData->lastButtonPressTime + DOUBLE_CLICK_MS))
  813. {
  814. onCursorDoubleClick(pos, btnStates);
  815. mData->lastButtonPressTime = 0;
  816. }
  817. else
  818. mData->lastButtonPressTime = event.xbutton.time;
  819. }
  820. }
  821. // Handle window dragging for windows without a title bar
  822. if(button == Button1)
  823. {
  824. LinuxWindow* window = getLinuxWindow(mData, event.xbutton.window);
  825. if(window != nullptr)
  826. window->_dragStart(event.xbutton);
  827. }
  828. break;
  829. }
  830. case ButtonRelease:
  831. {
  832. XButtonReleasedEvent* buttonEvent = (XButtonReleasedEvent*) &event;
  833. UINT32 button = event.xbutton.button;
  834. enqueueButtonEvent(xButtonToButtonCode(button), false, (UINT64) buttonEvent->time);
  835. Vector2I pos;
  836. pos.x = event.xbutton.x_root;
  837. pos.y = event.xbutton.y_root;
  838. OSPointerButtonStates btnStates;
  839. btnStates.ctrl = (event.xbutton.state & ControlMask) != 0;
  840. btnStates.shift = (event.xbutton.state & ShiftMask) != 0;
  841. btnStates.mouseButtons[0] = (event.xbutton.state & Button1Mask) != 0;
  842. btnStates.mouseButtons[1] = (event.xbutton.state & Button2Mask) != 0;
  843. btnStates.mouseButtons[2] = (event.xbutton.state & Button3Mask) != 0;
  844. switch(button)
  845. {
  846. case Button1:
  847. btnStates.mouseButtons[0] = false;
  848. onCursorButtonReleased(pos, OSMouseButton::Left, btnStates);
  849. break;
  850. case Button2:
  851. btnStates.mouseButtons[1] = false;
  852. onCursorButtonReleased(pos, OSMouseButton::Middle, btnStates);
  853. break;
  854. case Button3:
  855. btnStates.mouseButtons[2] = false;
  856. onCursorButtonReleased(pos, OSMouseButton::Right, btnStates);
  857. break;
  858. case Button4: // Vertical mouse wheel
  859. case Button5:
  860. {
  861. INT32 delta = button == Button4 ? 1 : -1;
  862. onMouseWheelScrolled((float)delta);
  863. }
  864. break;
  865. default:
  866. break;
  867. }
  868. // Handle window dragging for windows without a title bar
  869. if(button == Button1)
  870. {
  871. LinuxWindow* window = getLinuxWindow(mData, event.xbutton.window);
  872. if(window != nullptr)
  873. window->_dragEnd();
  874. }
  875. break;
  876. }
  877. case MotionNotify:
  878. {
  879. Vector2I pos;
  880. pos.x = event.xmotion.x_root;
  881. pos.y = event.xmotion.y_root;
  882. // Handle clipping if enabled
  883. if(clipCursor(mData, pos))
  884. _setCursorPosition(mData, pos);
  885. // Send event
  886. OSPointerButtonStates btnStates;
  887. btnStates.ctrl = (event.xmotion.state & ControlMask) != 0;
  888. btnStates.shift = (event.xmotion.state & ShiftMask) != 0;
  889. btnStates.mouseButtons[0] = (event.xmotion.state & Button1Mask) != 0;
  890. btnStates.mouseButtons[1] = (event.xmotion.state & Button2Mask) != 0;
  891. btnStates.mouseButtons[2] = (event.xmotion.state & Button3Mask) != 0;
  892. onCursorMoved(pos, btnStates);
  893. }
  894. break;
  895. case EnterNotify:
  896. // Do nothing
  897. break;
  898. case LeaveNotify:
  899. {
  900. if (event.xcrossing.mode == NotifyNormal)
  901. {
  902. Vector2I pos;
  903. pos.x = event.xcrossing.x_root;
  904. pos.y = event.xcrossing.y_root;
  905. if (clipCursor(mData, pos))
  906. _setCursorPosition(mData, pos);
  907. }
  908. ct::RenderWindow* renderWindow = getRenderWindow(mData, event.xcrossing.window);
  909. if(renderWindow != nullptr)
  910. renderWindow->_notifyMouseLeft();
  911. }
  912. break;
  913. case ConfigureNotify:
  914. {
  915. LinuxWindow* window = getLinuxWindow(mData, event.xconfigure.window);
  916. if(window != nullptr)
  917. {
  918. updateClipBounds(mData, window);
  919. ct::RenderWindow* renderWindow = (ct::RenderWindow*)window->_getUserData();
  920. if(renderWindow != nullptr)
  921. renderWindow->_windowMovedOrResized();
  922. }
  923. }
  924. break;
  925. case FocusIn:
  926. {
  927. // Update input context focus
  928. XSetICFocus(mData->IC);
  929. // Send event to render window
  930. ct::RenderWindow* renderWindow = getRenderWindow(mData, event.xfocus.window);
  931. // Not a render window, so it doesn't care about these events
  932. if (renderWindow != nullptr)
  933. {
  934. if (!renderWindow->getProperties().hasFocus)
  935. renderWindow->_windowFocusReceived();
  936. }
  937. }
  938. break;
  939. case FocusOut:
  940. {
  941. // Update input context focus
  942. XUnsetICFocus(mData->IC);
  943. // Send event to render window
  944. ct::RenderWindow* renderWindow = getRenderWindow(mData, event.xfocus.window);
  945. // Not a render window, so it doesn't care about these events
  946. if (renderWindow != nullptr)
  947. {
  948. if (renderWindow->getProperties().hasFocus)
  949. renderWindow->_windowFocusLost();
  950. }
  951. }
  952. break;
  953. case SelectionNotify:
  954. LinuxDragAndDrop::handleSelectionNotify(event.xselection);
  955. break;
  956. case SelectionRequest:
  957. {
  958. // Send the data saved by the last clipboard copy operation
  959. Atom compoundTextAtom = XInternAtom(mData->xDisplay, "COMPOUND_TEXT", 0);
  960. Atom utf8StringAtom = XInternAtom(mData->xDisplay, "UTF8_STRING", 0);
  961. Atom targetsAtom = XInternAtom(mData->xDisplay, "TARGETS", 0);
  962. XSelectionRequestEvent& selReq = event.xselectionrequest;
  963. XEvent response;
  964. if(selReq.target == XA_STRING || selReq.target == compoundTextAtom || selReq.target == utf8StringAtom)
  965. {
  966. String utf8data = UTF8::fromWide(mData->clipboardData);
  967. const UINT8* data = (const UINT8*)utf8data.c_str();
  968. INT32 dataLength = (INT32)utf8data.length();
  969. XChangeProperty(mData->xDisplay, selReq.requestor, selReq.property,
  970. selReq.target, 8, PropModeReplace, data, dataLength);
  971. response.xselection.property = selReq.property;
  972. }
  973. else if(selReq.target == targetsAtom)
  974. {
  975. Atom data[2];
  976. data[0] = utf8StringAtom;
  977. data[1] = XA_STRING;
  978. XChangeProperty (mData->xDisplay, selReq.requestor, selReq.property, selReq.target,
  979. 8, PropModeReplace, (unsigned char*)&data, sizeof (data));
  980. response.xselection.property = selReq.property;
  981. }
  982. else
  983. {
  984. response.xselection.property = None;
  985. }
  986. response.xselection.type = SelectionNotify;
  987. response.xselection.display = selReq.display;
  988. response.xselection.requestor = selReq.requestor;
  989. response.xselection.selection = selReq.selection;
  990. response.xselection.target = selReq.target;
  991. response.xselection.time = selReq.time;
  992. XSendEvent (mData->xDisplay, selReq.requestor, 0, 0, &response);
  993. XFlush (mData->xDisplay);
  994. }
  995. break;
  996. case PropertyNotify:
  997. // Report minimize, maximize and restore events
  998. if(event.xproperty.atom == mData->atomWmState)
  999. {
  1000. // Check that the window hasn't been destroyed
  1001. if(getLinuxWindow(mData, event.xproperty.window) == nullptr)
  1002. break;
  1003. Atom type;
  1004. INT32 format;
  1005. unsigned long count, bytesRemaining;
  1006. UINT8* data = nullptr;
  1007. INT32 result = XGetWindowProperty(mData->xDisplay, event.xproperty.window, mData->atomWmState,
  1008. 0, 1024, False, AnyPropertyType, &type, &format,
  1009. &count, &bytesRemaining, &data);
  1010. if (result == Success)
  1011. {
  1012. ct::RenderWindow* renderWindow = getRenderWindow(mData, event.xproperty.window);
  1013. // Not a render window, so it doesn't care about these events
  1014. if(renderWindow == nullptr)
  1015. continue;
  1016. Atom* atoms = (Atom*)data;
  1017. bool foundHorz = false;
  1018. bool foundVert = false;
  1019. for (unsigned long i = 0; i < count; i++)
  1020. {
  1021. if (atoms[i] == mData->atomWmStateMaxHorz) foundHorz = true;
  1022. if (atoms[i] == mData->atomWmStateMaxVert) foundVert = true;
  1023. if (foundVert && foundHorz)
  1024. {
  1025. if(event.xproperty.state == PropertyNewValue)
  1026. renderWindow->_notifyMaximized();
  1027. else
  1028. renderWindow->_notifyRestored();
  1029. }
  1030. if(atoms[i] == mData->atomWmStateHidden)
  1031. {
  1032. if(event.xproperty.state == PropertyNewValue)
  1033. renderWindow->_notifyMinimized();
  1034. else
  1035. renderWindow->_notifyRestored();
  1036. }
  1037. }
  1038. XFree(atoms);
  1039. }
  1040. }
  1041. break;
  1042. default:
  1043. break;
  1044. }
  1045. }
  1046. }
  1047. void Platform::_startUp()
  1048. {
  1049. Lock lock(mData->lock);
  1050. mData->xDisplay = XOpenDisplay(nullptr);
  1051. XSetErrorHandler(x11ErrorHandler);
  1052. // For raw, relative mouse motion events, XInput2 extension is required
  1053. int firstEvent;
  1054. int firstError;
  1055. if (!XQueryExtension(mData->xDisplay, "XInputExtension", &mData->xInput2Opcode, &firstEvent, &firstError)) {
  1056. BS_EXCEPT(InternalErrorException, "X Server doesn't support the XInput extension");
  1057. }
  1058. int majorVersion = 2;
  1059. int minorVersion = 0;
  1060. if (XIQueryVersion(mData->xDisplay, &majorVersion, &minorVersion) != Success) {
  1061. BS_EXCEPT(InternalErrorException, "X Server doesn't support at least the XInput 2.0 extension");
  1062. }
  1063. // Let XInput know we are interested in raw mouse movement events
  1064. XIEventMask mask;
  1065. mask.deviceid = XIAllDevices;
  1066. mask.mask_len = XIMaskLen(XI_LASTEVENT);
  1067. unsigned char maskBuffer[mask.mask_len] = {0};
  1068. mask.mask = maskBuffer;
  1069. XISetMask(mask.mask, XI_RawMotion);
  1070. // "RawEvents are sent exclusively to all root windows", so this should receive all events, even though we only
  1071. // select on one display's root window (untested for lack of second screen).
  1072. XISelectEvents(mData->xDisplay, XRootWindow(mData->xDisplay, DefaultScreen(mData->xDisplay)), &mask, 1);
  1073. XFlush(mData->xDisplay);
  1074. if(XSupportsLocale())
  1075. {
  1076. XSetLocaleModifiers("@im=none");
  1077. mData->IM = XOpenIM(mData->xDisplay, nullptr, nullptr, nullptr);
  1078. // Note: Currently our windows don't support pre-edit and status areas, which are used for more complex types
  1079. // of character input. Later on it might be beneficial to support them.
  1080. mData->IC = XCreateIC(mData->IM, XNInputStyle, XIMPreeditNothing | XIMStatusNothing, nullptr);
  1081. }
  1082. mData->atomDeleteWindow = XInternAtom(mData->xDisplay, "WM_DELETE_WINDOW", False);
  1083. mData->atomWmState = XInternAtom(mData->xDisplay, "_NET_WM_STATE", False);
  1084. mData->atomWmStateHidden = XInternAtom(mData->xDisplay, "_NET_WM_STATE_HIDDEN", False);
  1085. mData->atomWmStateMaxHorz = XInternAtom(mData->xDisplay, "_NET_WM_STATE_MAXIMIZED_HORZ", False);
  1086. mData->atomWmStateMaxVert = XInternAtom(mData->xDisplay, "_NET_WM_STATE_MAXIMIZED_VERT", False);
  1087. // Drag and drop
  1088. LinuxDragAndDrop::startUp(mData->xDisplay);
  1089. // Create empty cursor
  1090. char data[1];
  1091. memset(data, 0, sizeof(data));
  1092. Pixmap pixmap = XCreateBitmapFromData(mData->xDisplay, DefaultRootWindow(mData->xDisplay), data, 1, 1);
  1093. XColor color;
  1094. color.red = color.green = color.blue = 0;
  1095. mData->emptyCursor = XCreatePixmapCursor(mData->xDisplay, pixmap, pixmap, &color, &color, 0, 0);
  1096. XFreePixmap(mData->xDisplay, pixmap);
  1097. // Initialize "unique X11 keyname" -> "X11 keycode" map
  1098. char name[XkbKeyNameLength + 1];
  1099. XkbDescPtr desc = XkbGetMap(mData->xDisplay, 0, XkbUseCoreKbd);
  1100. XkbGetNames(mData->xDisplay, XkbKeyNamesMask, desc);
  1101. for (UINT32 keyCode = desc->min_key_code; keyCode <= desc->max_key_code; keyCode++)
  1102. {
  1103. memcpy(name, desc->names->keys[keyCode].name, XkbKeyNameLength);
  1104. name[XkbKeyNameLength] = '\0';
  1105. mData->keyNameMap[String(name)] = keyCode;
  1106. }
  1107. XkbFreeNames(desc, XkbKeyNamesMask, True);
  1108. XkbFreeKeyboard(desc, 0, True);
  1109. // Initialize "X11 keycode" -> "Banshee ButtonCode" map, based on the keyNameMap and keyCodeToKeyName()
  1110. mData->keyCodeMap.resize(desc->max_key_code + 1, BC_UNASSIGNED);
  1111. for (UINT32 buttonCodeNum = BC_UNASSIGNED; buttonCodeNum <= BC_NumKeys; buttonCodeNum++)
  1112. {
  1113. ButtonCode buttonCode = (ButtonCode) buttonCodeNum;
  1114. const char* keyNameCStr = buttonCodeToKeyName(buttonCode);
  1115. if (keyNameCStr != nullptr)
  1116. {
  1117. String keyName = String(keyNameCStr);
  1118. auto iterFind = mData->keyNameMap.find(keyName);
  1119. if (iterFind != mData->keyNameMap.end())
  1120. {
  1121. KeyCode keyCode = iterFind->second;
  1122. mData->keyCodeMap[keyCode] = buttonCode;
  1123. }
  1124. }
  1125. }
  1126. }
  1127. void Platform::_update()
  1128. {
  1129. LinuxDragAndDrop::update();
  1130. }
  1131. void Platform::_coreUpdate()
  1132. {
  1133. _messagePump();
  1134. }
  1135. void Platform::_shutDown()
  1136. {
  1137. Lock lock(mData->lock);
  1138. // Free empty cursor
  1139. XFreeCursor(mData->xDisplay, mData->emptyCursor);
  1140. mData->emptyCursor = None;
  1141. // Shutdown drag and drop
  1142. LinuxDragAndDrop::shutDown();
  1143. if(mData->IC)
  1144. {
  1145. XDestroyIC(mData->IC);
  1146. mData->IC = 0;
  1147. }
  1148. if(mData->IM)
  1149. {
  1150. XCloseIM(mData->IM);
  1151. mData->IM = 0;
  1152. }
  1153. XCloseDisplay(mData->xDisplay);
  1154. mData->xDisplay = nullptr;
  1155. bs_delete(mData);
  1156. mData = nullptr;
  1157. }
  1158. ::Display* LinuxPlatform::getXDisplay()
  1159. {
  1160. return mData->xDisplay;
  1161. }
  1162. ::Window LinuxPlatform::getMainXWindow()
  1163. {
  1164. return mData->mainXWindow;
  1165. }
  1166. Path LinuxPlatform::getHomeDir()
  1167. {
  1168. const char* homeDir = getenv("HOME");
  1169. if(!homeDir)
  1170. homeDir = getpwuid(getuid())->pw_dir;
  1171. return Path(homeDir);
  1172. }
  1173. void LinuxPlatform::lockX()
  1174. {
  1175. mData->lock.lock();
  1176. }
  1177. void LinuxPlatform::unlockX()
  1178. {
  1179. mData->lock.unlock();
  1180. }
  1181. void LinuxPlatform::_registerWindow(::Window xWindow, LinuxWindow* window)
  1182. {
  1183. // First window is assumed to be the main
  1184. if(mData->mainXWindow == 0)
  1185. {
  1186. mData->mainXWindow = xWindow;
  1187. // Input context client window must be set before use
  1188. XSetICValues(mData->IC,
  1189. XNClientWindow, xWindow,
  1190. XNFocusWindow, xWindow,
  1191. nullptr);
  1192. }
  1193. mData->windowMap[xWindow] = window;
  1194. applyCurrentCursor(mData, xWindow);
  1195. }
  1196. void LinuxPlatform::_unregisterWindow(::Window xWindow)
  1197. {
  1198. auto iterFind = mData->windowMap.find(xWindow);
  1199. if(iterFind != mData->windowMap.end())
  1200. {
  1201. if(mData->cursorClipEnabled && mData->cursorClipWindow == iterFind->second)
  1202. clipCursorDisable();
  1203. mData->windowMap.erase(iterFind);
  1204. }
  1205. if(mData->mainXWindow == xWindow)
  1206. mData->mainXWindow = 0;
  1207. }
  1208. Pixmap LinuxPlatform::createPixmap(const PixelData& data, UINT32 depth)
  1209. {
  1210. // Premultiply alpha
  1211. Vector<Color> colors = data.getColors();
  1212. for(auto& color : colors)
  1213. {
  1214. color.r *= color.a;
  1215. color.g *= color.a;
  1216. color.b *= color.a;
  1217. }
  1218. // Convert to BGRA
  1219. SPtr<PixelData> bgraData = PixelData::create(data.getWidth(), data.getHeight(), 1, PF_BGRA8);
  1220. bgraData->setColors(colors);
  1221. XImage* image = XCreateImage(mData->xDisplay, CopyFromParent, depth, ZPixmap, 0,
  1222. (char*)bgraData->getData(), data.getWidth(), data.getHeight(), 32, 0);
  1223. Pixmap pixmap = XCreatePixmap(mData->xDisplay, XDefaultRootWindow(mData->xDisplay),
  1224. data.getWidth(), data.getHeight(), depth);
  1225. XGCValues gcValues;
  1226. GC gc = XCreateGC(mData->xDisplay, pixmap, 0, &gcValues);
  1227. XPutImage(mData->xDisplay, pixmap, gc, image, 0, 0, 0, 0, data.getWidth(), data.getHeight());
  1228. XFreeGC(mData->xDisplay, gc);
  1229. // Make sure XDestroyImage doesn't free the data pointed to by 'data.bytes'
  1230. image->data = nullptr;
  1231. XDestroyImage(image);
  1232. return pixmap;
  1233. }
  1234. }