BsLinuxPlatform.cpp 29 KB

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