X11Window.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. // Avoid cluttering the global namespace by hiding these from the header
  2. #include <X11/Xlib.h>
  3. #include <X11/Xutil.h>
  4. #include <X11/Xos.h>
  5. #include <X11/Xatom.h>
  6. #include "../DFPSR/api/imageAPI.h"
  7. #include "../DFPSR/gui/BackendWindow.h"
  8. // According to this documentation, XInitThreads doesn't have to be used if a mutex is wrapped around all the calls to XLib.
  9. // https://tronche.com/gui/x/xlib/display/XInitThreads.html
  10. #include <mutex>
  11. #include <future>
  12. static std::mutex windowLock;
  13. // Enable this macro to disable multi-threading
  14. //#define DISABLE_MULTI_THREADING
  15. static const int bufferCount = 2;
  16. class X11Window : public dsr::BackendWindow {
  17. private:
  18. // The display is the connection to the X server
  19. // Each window has it's own connection, because you're only supposed to have one window
  20. // Let sub-windows be visual components for simpler input and deterministic custom decorations
  21. Display *display = nullptr;
  22. // The handle to the X11 window
  23. Window window;
  24. // Holds settings for drawing to the window
  25. GC graphicsContext;
  26. // Double buffering to allow drawing to a canvas while displaying the previous one
  27. // The image which can be drawn to, sharing memory with the X11 image
  28. dsr::AlignedImageRgbaU8 canvas[bufferCount];
  29. // An X11 image wrapped around the canvas pixel data
  30. XImage *canvasX[bufferCount] = {};
  31. int drawIndex = 0 % bufferCount;
  32. int showIndex = 1 % bufferCount;
  33. #ifndef DISABLE_MULTI_THREADING
  34. // The background worker for displaying the result using a separate thread protected by a mutex
  35. std::future<void> displayFuture;
  36. #endif
  37. // Remembers the dimensions of the window from creation and resize events
  38. // This allow requesting the size of the window at any time
  39. int windowWidth = 0, windowHeight = 0;
  40. // Called before the application fetches events from the input queue
  41. // Closing the window, moving the mouse, pressing a key, et cetera
  42. void prefetchEvents() override;
  43. // Color format
  44. dsr::PackOrderIndex packOrderIndex = dsr::PackOrderIndex::RGBA;
  45. dsr::PackOrderIndex getColorFormat_locked();
  46. private:
  47. // Helper methods specific to calling XLib
  48. void updateTitle_locked();
  49. private:
  50. // Canvas methods
  51. dsr::AlignedImageRgbaU8 getCanvas() override { return this->canvas[this->drawIndex]; }
  52. void resizeCanvas(int width, int height) override;
  53. // Window methods
  54. void setTitle(const dsr::String &newTitle) override {
  55. this->title = newTitle;
  56. this->updateTitle_locked();
  57. }
  58. void removeOldWindow_locked();
  59. void createGCWindow_locked(const dsr::String& title, int width, int height);
  60. void createWindowed_locked(const dsr::String& title, int width, int height);
  61. void createFullscreen_locked();
  62. void prepareWindow_locked();
  63. int windowState = 0; // 0=none, 1=windowed, 2=fullscreen
  64. public:
  65. // Constructors
  66. X11Window(const X11Window&) = delete; // Non-copyable because of pointer aliasing.
  67. X11Window(const dsr::String& title, int width, int height);
  68. int getWidth() const override { return this->windowWidth; };
  69. int getHeight() const override { return this->windowHeight; };
  70. // Destructor
  71. ~X11Window();
  72. // Interface
  73. void setFullScreen(bool enabled) override;
  74. bool isFullScreen() override { return this->windowState == 2; }
  75. void showCanvas() override;
  76. };
  77. void X11Window::updateTitle_locked() {
  78. windowLock.lock();
  79. XSetStandardProperties(this->display, this->window, this->title.toStdString().c_str(), "Icon", None, NULL, 0, NULL);
  80. windowLock.unlock();
  81. }
  82. dsr::PackOrderIndex X11Window::getColorFormat_locked() {
  83. windowLock.lock();
  84. XVisualInfo visualRequest;
  85. visualRequest.screen = 0;
  86. visualRequest.depth = 32;
  87. visualRequest.c_class = TrueColor;
  88. int visualCount;
  89. XVisualInfo *formatList = XGetVisualInfo(this->display, VisualScreenMask | VisualDepthMask | VisualClassMask, &visualRequest, &visualCount);
  90. dsr::PackOrderIndex result = dsr::PackOrderIndex::RGBA;
  91. if (formatList == nullptr) {
  92. dsr::throwError(U"Error! The display does not support truecolor formats.\n");
  93. } else {
  94. for (int i = 0; i < visualCount; i++) {
  95. if (formatList[i].bits_per_rgb == 8) {
  96. const uint32_t red = formatList[i].red_mask;
  97. const uint32_t green = formatList[i].green_mask;
  98. const uint32_t blue = formatList[i].blue_mask;
  99. const uint32_t first = 255u;
  100. const uint32_t second = 255u << 8u;
  101. const uint32_t third = 255u << 16u;
  102. const uint32_t fourth = 255u << 24u;
  103. if (red == first && green == second && blue == third) {
  104. result = dsr::PackOrderIndex::RGBA;
  105. } else if (red == second && green == third && blue == fourth) {
  106. result = dsr::PackOrderIndex::ARGB;
  107. } else if (blue == first && green == second && red == third) {
  108. result = dsr::PackOrderIndex::BGRA;
  109. } else if (blue == second && green == third && red == fourth) {
  110. result = dsr::PackOrderIndex::ABGR;
  111. } else {
  112. dsr::throwError(U"Error! Unhandled color format. Only RGBA, ARGB, BGRA and ABGR are currently supported.\n");
  113. }
  114. break;
  115. }
  116. }
  117. XFree(formatList);
  118. }
  119. windowLock.unlock();
  120. return result;
  121. }
  122. struct Hints {
  123. unsigned long flags;
  124. unsigned long functions;
  125. unsigned long decorations;
  126. long inputMode;
  127. unsigned long status;
  128. };
  129. void X11Window::setFullScreen(bool enabled) {
  130. if (this->windowState == 1 && enabled) {
  131. // Clean up any previous X11 window
  132. removeOldWindow_locked();
  133. // Create the new window and graphics context
  134. this->createFullscreen_locked();
  135. } else if (this->windowState == 2 && !enabled) {
  136. // Clean up any previous X11 window
  137. removeOldWindow_locked();
  138. // Create the new window and graphics context
  139. this->createWindowed_locked(this->title, 800, 600); // TODO: Remember the dimensions from last windowed mode
  140. }
  141. }
  142. void X11Window::removeOldWindow_locked() {
  143. windowLock.lock();
  144. if (this->windowState != 0) {
  145. XFreeGC(this->display, this->graphicsContext);
  146. XDestroyWindow(this->display, this->window);
  147. XUngrabPointer(this->display, CurrentTime);
  148. }
  149. this->windowState = 0;
  150. windowLock.unlock();
  151. }
  152. void X11Window::prepareWindow_locked() {
  153. windowLock.lock();
  154. // Set input masks
  155. XSelectInput(this->display, this->window,
  156. ExposureMask | StructureNotifyMask |
  157. PointerMotionMask |
  158. ButtonPressMask | ButtonReleaseMask |
  159. KeyPressMask | KeyReleaseMask
  160. );
  161. // Listen to the window close event.
  162. Atom WM_DELETE_WINDOW = XInternAtom(this->display, "WM_DELETE_WINDOW", False);
  163. XSetWMProtocols(this->display, this->window, &WM_DELETE_WINDOW, 1);
  164. windowLock.unlock();
  165. // Reallocate the canvas
  166. this->resizeCanvas(this->windowWidth, this->windowHeight);
  167. }
  168. void X11Window::createGCWindow_locked(const dsr::String& title, int width, int height) {
  169. windowLock.lock();
  170. // Request to resize the canvas and interface according to the new window
  171. this->windowWidth = width;
  172. this->windowHeight = height;
  173. this->receivedWindowResize(width, height);
  174. // Screen handle
  175. int defaultScreenIndex = DefaultScreen(this->display);
  176. unsigned long black = BlackPixel(this->display, defaultScreenIndex);
  177. unsigned long white = WhitePixel(this->display, defaultScreenIndex);
  178. // Create a new window
  179. this->window = XCreateSimpleWindow(this->display, DefaultRootWindow(this->display), 0, 0, width, height, 0, white, black);
  180. windowLock.unlock();
  181. this->updateTitle_locked();
  182. windowLock.lock();
  183. // Create a new graphics context
  184. this->graphicsContext = XCreateGC(this->display, this->window, 0, 0);
  185. XSetBackground(this->display, this->graphicsContext, black);
  186. XSetForeground(this->display, this->graphicsContext, white);
  187. XClearWindow(this->display, this->window);
  188. windowLock.unlock();
  189. }
  190. void X11Window::createWindowed_locked(const dsr::String& title, int width, int height) {
  191. // Create the window
  192. this->createGCWindow_locked(title, width, height);
  193. windowLock.lock();
  194. // Display the window when done placing it
  195. XMapRaised(this->display, this->window);
  196. this->windowState = 1;
  197. windowLock.unlock();
  198. this->prepareWindow_locked();
  199. }
  200. void X11Window::createFullscreen_locked() {
  201. windowLock.lock();
  202. // Get the screen resolution
  203. Screen* screenInfo = DefaultScreenOfDisplay(this->display);
  204. windowLock.unlock();
  205. // Create the window
  206. this->createGCWindow_locked(U"", screenInfo->width, screenInfo->height);
  207. windowLock.lock();
  208. // Override redirect
  209. unsigned long valuemask = CWOverrideRedirect;
  210. XSetWindowAttributes setwinattr;
  211. setwinattr.override_redirect = 1;
  212. XChangeWindowAttributes(this->display, this->window, valuemask, &setwinattr);
  213. // Remove decorations
  214. Hints hints;
  215. memset(&hints, 0, sizeof(hints));
  216. hints.flags = 2;
  217. hints.decorations = 0;
  218. Atom property;
  219. property = XInternAtom(this->display, "_MOTIF_WM_HINTS", True); // TODO: Check if this optional XLib feature is supported
  220. XChangeProperty(this->display, this->window, property, property, 32, PropModeReplace, (unsigned char*)&hints, 5);
  221. // Move to absolute origin
  222. XMoveResizeWindow(this->display, this->window, 0, 0, screenInfo->width, screenInfo->height);
  223. // Prevent accessing anything outside of the window until it closes (for multiple displays)
  224. XGrabPointer(this->display, this->window, 1, 0, GrabModeAsync, GrabModeAsync, this->window, 0L, CurrentTime);
  225. XGrabKeyboard(this->display, this->window, 1, GrabModeAsync, GrabModeAsync, CurrentTime);
  226. // Display the window when done placing it
  227. XMapRaised(this->display, this->window);
  228. // Now that the window is visible, it can be focused for keyboard input
  229. XSetInputFocus(this->display, this->window, RevertToNone, CurrentTime);
  230. this->windowState = 2;
  231. windowLock.unlock();
  232. this->prepareWindow_locked();
  233. }
  234. X11Window::X11Window(const dsr::String& title, int width, int height) {
  235. bool fullScreen = false;
  236. if (width < 1 || height < 1) {
  237. fullScreen = true;
  238. width = 400;
  239. height = 300;
  240. }
  241. windowLock.lock();
  242. this->display = XOpenDisplay(nullptr);
  243. windowLock.unlock();
  244. if (this->display == nullptr) {
  245. dsr::throwError(U"Error! Failed to open XLib display!\n");
  246. return;
  247. }
  248. // Get the color format
  249. this->packOrderIndex = this->getColorFormat_locked();
  250. // Remember the title
  251. this->title = title;
  252. // Create a window
  253. if (fullScreen) {
  254. this->createFullscreen_locked();
  255. } else {
  256. this->createWindowed_locked(title, width, height);
  257. }
  258. }
  259. // Convert keycodes from XLib to DSR
  260. static dsr::MouseKeyEnum getMouseKey(int keyCode) {
  261. dsr::MouseKeyEnum result = dsr::MouseKeyEnum::NoKey;
  262. if (keyCode == Button1) {
  263. result = dsr::MouseKeyEnum::Left;
  264. } else if (keyCode == Button2) {
  265. result = dsr::MouseKeyEnum::Middle;
  266. } else if (keyCode == Button3) {
  267. result = dsr::MouseKeyEnum::Right;
  268. } else if (keyCode == Button4) {
  269. result = dsr::MouseKeyEnum::ScrollUp;
  270. } else if (keyCode == Button5) {
  271. result = dsr::MouseKeyEnum::ScrollDown;
  272. }
  273. return result;
  274. }
  275. static bool isVerticalScrollKey(dsr::MouseKeyEnum key) {
  276. return key == dsr::MouseKeyEnum::ScrollDown || key == dsr::MouseKeyEnum::ScrollUp;
  277. }
  278. static dsr::DsrKey getDsrKey(KeySym keyCode) {
  279. dsr::DsrKey result = dsr::DsrKey_Unhandled;
  280. if (keyCode == XK_Escape) {
  281. result = dsr::DsrKey_Escape;
  282. } else if (keyCode == XK_F1) {
  283. result = dsr::DsrKey_F1;
  284. } else if (keyCode == XK_F2) {
  285. result = dsr::DsrKey_F2;
  286. } else if (keyCode == XK_F3) {
  287. result = dsr::DsrKey_F3;
  288. } else if (keyCode == XK_F4) {
  289. result = dsr::DsrKey_F4;
  290. } else if (keyCode == XK_F5) {
  291. result = dsr::DsrKey_F5;
  292. } else if (keyCode == XK_F6) {
  293. result = dsr::DsrKey_F6;
  294. } else if (keyCode == XK_F7) {
  295. result = dsr::DsrKey_F7;
  296. } else if (keyCode == XK_F8) {
  297. result = dsr::DsrKey_F8;
  298. } else if (keyCode == XK_F9) {
  299. result = dsr::DsrKey_F9;
  300. } else if (keyCode == XK_F10) {
  301. result = dsr::DsrKey_F10;
  302. } else if (keyCode == XK_F11) {
  303. result = dsr::DsrKey_F11;
  304. } else if (keyCode == XK_F12) {
  305. result = dsr::DsrKey_F12;
  306. } else if (keyCode == XK_Pause) {
  307. result = dsr::DsrKey_Pause;
  308. } else if (keyCode == XK_space) {
  309. result = dsr::DsrKey_Space;
  310. } else if (keyCode == XK_Tab) {
  311. result = dsr::DsrKey_Tab;
  312. } else if (keyCode == XK_Return) {
  313. result = dsr::DsrKey_Return;
  314. } else if (keyCode == XK_BackSpace) {
  315. result = dsr::DsrKey_BackSpace;
  316. } else if (keyCode == XK_Shift_L) {
  317. result = dsr::DsrKey_LeftShift;
  318. } else if (keyCode == XK_Shift_R) {
  319. result = dsr::DsrKey_RightShift;
  320. } else if (keyCode == XK_Control_L) {
  321. result = dsr::DsrKey_LeftControl;
  322. } else if (keyCode == XK_Control_R) {
  323. result = dsr::DsrKey_RightControl;
  324. } else if (keyCode == XK_Alt_L) {
  325. result = dsr::DsrKey_LeftAlt;
  326. } else if (keyCode == XK_Alt_R) {
  327. result = dsr::DsrKey_RightAlt;
  328. } else if (keyCode == XK_Delete) {
  329. result = dsr::DsrKey_Delete;
  330. } else if (keyCode == XK_Left) {
  331. result = dsr::DsrKey_LeftArrow;
  332. } else if (keyCode == XK_Right) {
  333. result = dsr::DsrKey_RightArrow;
  334. } else if (keyCode == XK_Up) {
  335. result = dsr::DsrKey_UpArrow;
  336. } else if (keyCode == XK_Down) {
  337. result = dsr::DsrKey_DownArrow;
  338. } else if (keyCode == XK_0) {
  339. result = dsr::DsrKey_0;
  340. } else if (keyCode == XK_1) {
  341. result = dsr::DsrKey_1;
  342. } else if (keyCode == XK_2) {
  343. result = dsr::DsrKey_2;
  344. } else if (keyCode == XK_3) {
  345. result = dsr::DsrKey_3;
  346. } else if (keyCode == XK_4) {
  347. result = dsr::DsrKey_4;
  348. } else if (keyCode == XK_5) {
  349. result = dsr::DsrKey_5;
  350. } else if (keyCode == XK_6) {
  351. result = dsr::DsrKey_6;
  352. } else if (keyCode == XK_7) {
  353. result = dsr::DsrKey_7;
  354. } else if (keyCode == XK_8) {
  355. result = dsr::DsrKey_8;
  356. } else if (keyCode == XK_9) {
  357. result = dsr::DsrKey_9;
  358. } else if (keyCode == XK_a || keyCode == XK_A) {
  359. result = dsr::DsrKey_A;
  360. } else if (keyCode == XK_b || keyCode == XK_B) {
  361. result = dsr::DsrKey_B;
  362. } else if (keyCode == XK_c || keyCode == XK_C) {
  363. result = dsr::DsrKey_C;
  364. } else if (keyCode == XK_d || keyCode == XK_D) {
  365. result = dsr::DsrKey_D;
  366. } else if (keyCode == XK_e || keyCode == XK_E) {
  367. result = dsr::DsrKey_E;
  368. } else if (keyCode == XK_f || keyCode == XK_F) {
  369. result = dsr::DsrKey_F;
  370. } else if (keyCode == XK_g || keyCode == XK_G) {
  371. result = dsr::DsrKey_G;
  372. } else if (keyCode == XK_h || keyCode == XK_H) {
  373. result = dsr::DsrKey_H;
  374. } else if (keyCode == XK_i || keyCode == XK_I) {
  375. result = dsr::DsrKey_I;
  376. } else if (keyCode == XK_j || keyCode == XK_J) {
  377. result = dsr::DsrKey_J;
  378. } else if (keyCode == XK_k || keyCode == XK_K) {
  379. result = dsr::DsrKey_K;
  380. } else if (keyCode == XK_l || keyCode == XK_L) {
  381. result = dsr::DsrKey_L;
  382. } else if (keyCode == XK_m || keyCode == XK_M) {
  383. result = dsr::DsrKey_M;
  384. } else if (keyCode == XK_n || keyCode == XK_N) {
  385. result = dsr::DsrKey_N;
  386. } else if (keyCode == XK_o || keyCode == XK_O) {
  387. result = dsr::DsrKey_O;
  388. } else if (keyCode == XK_p || keyCode == XK_P) {
  389. result = dsr::DsrKey_P;
  390. } else if (keyCode == XK_q || keyCode == XK_Q) {
  391. result = dsr::DsrKey_Q;
  392. } else if (keyCode == XK_r || keyCode == XK_R) {
  393. result = dsr::DsrKey_R;
  394. } else if (keyCode == XK_s || keyCode == XK_S) {
  395. result = dsr::DsrKey_S;
  396. } else if (keyCode == XK_t || keyCode == XK_T) {
  397. result = dsr::DsrKey_T;
  398. } else if (keyCode == XK_u || keyCode == XK_U) {
  399. result = dsr::DsrKey_U;
  400. } else if (keyCode == XK_v || keyCode == XK_V) {
  401. result = dsr::DsrKey_V;
  402. } else if (keyCode == XK_w || keyCode == XK_W) {
  403. result = dsr::DsrKey_W;
  404. } else if (keyCode == XK_x || keyCode == XK_X) {
  405. result = dsr::DsrKey_X;
  406. } else if (keyCode == XK_y || keyCode == XK_Y) {
  407. result = dsr::DsrKey_Y;
  408. } else if (keyCode == XK_z || keyCode == XK_Z) {
  409. result = dsr::DsrKey_Z;
  410. }
  411. return result;
  412. }
  413. // Also locked, but cannot change the name when overriding
  414. void X11Window::prefetchEvents() {
  415. // Only prefetch new events if nothing else is using the communication link
  416. if (windowLock.try_lock()) {
  417. if (this->display) {
  418. while (XPending(this->display)) {
  419. // Ensure that full-screen applications have keyboard focus if interacted with in any way
  420. if (this->windowState == 2) {
  421. XSetInputFocus(this->display, this->window, RevertToNone, CurrentTime);
  422. }
  423. // Get the current event
  424. XEvent currentEvent;
  425. XNextEvent(this->display, &currentEvent);
  426. // See if there's another event
  427. XEvent nextEvent;
  428. bool hasNextEvent = XPending(this->display);
  429. if (hasNextEvent) {
  430. XPeekEvent(this->display, &nextEvent);
  431. }
  432. if (currentEvent.type == Expose && currentEvent.xexpose.count == 0) {
  433. // Redraw
  434. this->queueInputEvent(new dsr::WindowEvent(dsr::WindowEventType::Redraw, this->windowWidth, this->windowHeight));
  435. } else if (currentEvent.type == KeyPress || currentEvent.type == KeyRelease) {
  436. // Key down/up
  437. // TODO: Are unicode characters handled with determinism?
  438. KeySym key; char text[255]; char character = '\0';
  439. if (XLookupString(&currentEvent.xkey, text, 255, &key, 0) == 1) { character = text[0]; }
  440. dsr::DsrKey dsrKey = getDsrKey(XLookupKeysym(&currentEvent.xkey, 0));
  441. // Distinguish between fake and physical repeats using time stamps
  442. if (hasNextEvent && currentEvent.type == KeyRelease && nextEvent.type == KeyPress && currentEvent.xkey.time == nextEvent.xkey.time) {
  443. // Repeated typing
  444. this->queueInputEvent(new dsr::KeyboardEvent(dsr::KeyboardEventType::KeyType, character, dsrKey));
  445. // Skip next event
  446. XNextEvent(this->display, &currentEvent);
  447. } else {
  448. if (currentEvent.type == KeyPress) {
  449. // Physical key down
  450. this->queueInputEvent(new dsr::KeyboardEvent(dsr::KeyboardEventType::KeyDown, character, dsrKey));
  451. // First press typing
  452. this->queueInputEvent(new dsr::KeyboardEvent(dsr::KeyboardEventType::KeyType, character, dsrKey));
  453. } else { // currentEvent.type == KeyRelease
  454. // Physical key up
  455. this->queueInputEvent(new dsr::KeyboardEvent(dsr::KeyboardEventType::KeyUp, character, dsrKey));
  456. }
  457. }
  458. } else if (currentEvent.type == ButtonPress) {
  459. // Mouse down
  460. dsr::MouseKeyEnum key = getMouseKey(currentEvent.xbutton.button);
  461. this->queueInputEvent(new dsr::MouseEvent(isVerticalScrollKey(key) ? dsr::MouseEventType::Scroll : dsr::MouseEventType::MouseDown, key, dsr::IVector2D(currentEvent.xbutton.x, currentEvent.xbutton.y)));
  462. } else if (currentEvent.type == ButtonRelease) {
  463. // Mouse up
  464. dsr::MouseKeyEnum key = getMouseKey(currentEvent.xbutton.button);
  465. // Scroll events are already captured from the mouse down signal
  466. if (!isVerticalScrollKey(key)) {
  467. this->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseUp, key, dsr::IVector2D(currentEvent.xbutton.x, currentEvent.xbutton.y)));
  468. }
  469. } else if (currentEvent.type == MotionNotify) {
  470. // Mouse move
  471. this->queueInputEvent(new dsr::MouseEvent(dsr::MouseEventType::MouseMove, dsr::MouseKeyEnum::NoKey, dsr::IVector2D(currentEvent.xmotion.x, currentEvent.xmotion.y)));
  472. } else if (currentEvent.type == ClientMessage) {
  473. // Close
  474. // Assume WM_DELETE_WINDOW since it is the only registered client message
  475. this->queueInputEvent(new dsr::WindowEvent(dsr::WindowEventType::Close, this->windowWidth, this->windowHeight));
  476. } else if (currentEvent.type == ConfigureNotify) {
  477. XConfigureEvent xce = currentEvent.xconfigure;
  478. if (this->windowWidth != xce.width || this->windowHeight != xce.height) {
  479. this->windowWidth = xce.width;
  480. this->windowHeight = xce.height;
  481. // Make a request to resize the canvas
  482. this->receivedWindowResize(xce.width, xce.height);
  483. }
  484. }
  485. }
  486. }
  487. windowLock.unlock();
  488. }
  489. }
  490. // Locked because it overrides
  491. void X11Window::resizeCanvas(int width, int height) {
  492. windowLock.lock();
  493. if (this->display) {
  494. unsigned int defaultDepth = DefaultDepth(this->display, XDefaultScreen(this->display));
  495. for (int b = 0; b < bufferCount; b++) {
  496. // Create a new canvas
  497. this->canvas[b] = dsr::image_create_RgbaU8_native(width, height, this->packOrderIndex);
  498. // Get a pointer to the pixels
  499. uint8_t* rawData = dsr::image_dangerous_getData(this->canvas[b]);
  500. // Create an image in XLib using the pointer
  501. // XLib takes ownership of the data
  502. this->canvasX[b] = XCreateImage(
  503. this->display, CopyFromParent, defaultDepth, ZPixmap, 0, (char*)rawData,
  504. dsr::image_getWidth(this->canvas[b]), dsr::image_getHeight(this->canvas[b]), 32, dsr::image_getStride(this->canvas[b])
  505. );
  506. // When the canvas image buffer is garbage collected, the destructor will call XLib to free the memory
  507. XImage *image = this->canvasX[b];
  508. dsr::image_dangerous_replaceDestructor(this->canvas[b], [image](uint8_t *data) { XDestroyImage(image); });
  509. }
  510. }
  511. windowLock.unlock();
  512. }
  513. X11Window::~X11Window() {
  514. #ifndef DISABLE_MULTI_THREADING
  515. // Wait for the last update of the window to finish so that it doesn't try to operate on freed resources
  516. if (this->displayFuture.valid()) {
  517. this->displayFuture.wait();
  518. }
  519. #endif
  520. windowLock.lock();
  521. if (this->display) {
  522. XFreeGC(this->display, this->graphicsContext);
  523. XDestroyWindow(this->display, this->window);
  524. XCloseDisplay(this->display);
  525. this->display = nullptr;
  526. }
  527. windowLock.unlock();
  528. }
  529. void X11Window::showCanvas() {
  530. if (this->display) {
  531. #ifndef DISABLE_MULTI_THREADING
  532. // Wait for the previous update to finish, to avoid flooding the system with new threads waiting for windowLock
  533. if (this->displayFuture.valid()) {
  534. this->displayFuture.wait();
  535. }
  536. #endif
  537. this->drawIndex = (this->drawIndex + 1) % bufferCount;
  538. this->showIndex = (this->showIndex + 1) % bufferCount;
  539. this->prefetchEvents();
  540. int displayIndex = this->showIndex;
  541. windowLock.lock();
  542. std::function<void()> task = [this, displayIndex]() {
  543. // Clamp canvas dimensions to the target window
  544. int width = std::min(dsr::image_getWidth(this->canvas[displayIndex]), this->windowWidth);
  545. int height = std::min(dsr::image_getHeight(this->canvas[displayIndex]), this->windowHeight);
  546. // Display the result
  547. XPutImage(this->display, this->window, this->graphicsContext, this->canvasX[displayIndex], 0, 0, 0, 0, width, height);
  548. windowLock.unlock();
  549. };
  550. #ifdef DISABLE_MULTI_THREADING
  551. // Perform instantly
  552. task();
  553. #else
  554. // Run in the background while doing other things
  555. this->displayFuture = std::async(std::launch::async, task);
  556. #endif
  557. }
  558. }
  559. std::shared_ptr<dsr::BackendWindow> createBackendWindow(const dsr::String& title, int width, int height) {
  560. // Check if a display is available for creating a window
  561. if (XOpenDisplay(nullptr) != nullptr) {
  562. auto backend = std::make_shared<X11Window>(title, width, height);
  563. return std::dynamic_pointer_cast<dsr::BackendWindow>(backend);
  564. } else {
  565. printf("No display detected. Aborting X11 window creation.\n");
  566. return std::shared_ptr<dsr::BackendWindow>();
  567. }
  568. }