BsLinuxWindow.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. #include "BsLinuxWindow.h"
  4. #include "BsLinuxPlatform.h"
  5. #include "BsLinuxDropTarget.h"
  6. #include <X11/Xatom.h>
  7. #include <X11/extensions/Xrandr.h>
  8. #define _NET_WM_STATE_REMOVE 0
  9. #define _NET_WM_STATE_ADD 1
  10. #define _NET_WM_STATE_TOGGLE 2
  11. #define WM_NormalState 1
  12. #define WM_IconicState 3
  13. namespace bs
  14. {
  15. enum class WindowState
  16. {
  17. Minimized,
  18. Maximized,
  19. Normal
  20. };
  21. struct LinuxWindow::Pimpl
  22. {
  23. ::Window xWindow = 0;
  24. INT32 x, y;
  25. UINT32 width, height;
  26. bool hasTitleBar = true;
  27. bool dragInProgress = false;
  28. bool resizeDisabled = false;
  29. WindowState state = WindowState::Normal;
  30. Rect2I dragZone;
  31. INT32 dragStartX, dragStartY;
  32. void* userData = nullptr;
  33. };
  34. LinuxWindow::LinuxWindow(const WINDOW_DESC &desc)
  35. {
  36. m = bs_new<Pimpl>();
  37. ::Display* display = LinuxPlatform::getXDisplay();
  38. // Find the screen of the chosen monitor, as well as its current dimensions
  39. INT32 screen = XDefaultScreen(display);
  40. UINT32 outputIdx = 0;
  41. RROutput primaryOutput = XRRGetOutputPrimary(display, RootWindow(display, screen));
  42. INT32 monitorX = 0;
  43. INT32 monitorY = 0;
  44. UINT32 monitorWidth = 0;
  45. UINT32 monitorHeight = 0;
  46. INT32 screenCount = XScreenCount(display);
  47. for(INT32 i = 0; i < screenCount; i++)
  48. {
  49. XRRScreenResources* screenRes = XRRGetScreenResources(display, RootWindow(display, i));
  50. bool foundMonitor = false;
  51. for (INT32 j = 0; j < screenRes->noutput; j++)
  52. {
  53. XRROutputInfo* outputInfo = XRRGetOutputInfo(display, screenRes, screenRes->outputs[j]);
  54. if (outputInfo == nullptr || outputInfo->crtc == 0 || outputInfo->connection == RR_Disconnected)
  55. {
  56. XRRFreeOutputInfo(outputInfo);
  57. continue;
  58. }
  59. XRRCrtcInfo* crtcInfo = XRRGetCrtcInfo(display, screenRes, outputInfo->crtc);
  60. if (crtcInfo == nullptr)
  61. {
  62. XRRFreeCrtcInfo(crtcInfo);
  63. XRRFreeOutputInfo(outputInfo);
  64. continue;
  65. }
  66. if(desc.screen == (UINT32)-1)
  67. {
  68. if(screenRes->outputs[j] == primaryOutput)
  69. foundMonitor = true;
  70. }
  71. else
  72. foundMonitor = outputIdx == desc.screen;
  73. if(foundMonitor)
  74. {
  75. screen = i;
  76. monitorX = crtcInfo->x;
  77. monitorY = crtcInfo->y;
  78. monitorWidth = crtcInfo->width;
  79. monitorHeight = crtcInfo->height;
  80. foundMonitor = true;
  81. break;
  82. }
  83. }
  84. if(foundMonitor)
  85. break;
  86. }
  87. XSetWindowAttributes attributes;
  88. attributes.background_pixel = XWhitePixel(display, screen);
  89. attributes.border_pixel = XBlackPixel(display, screen);
  90. attributes.background_pixmap = 0;
  91. attributes.colormap = XCreateColormap(display,
  92. XRootWindow(display, screen),
  93. desc.visualInfo.visual,
  94. AllocNone);
  95. // If no position specified, center on the requested monitor
  96. if (desc.x == -1)
  97. m->x = monitorX + (monitorWidth - desc.width) / 2;
  98. else if (desc.screen != (UINT32)-1)
  99. m->x = monitorX + desc.x;
  100. else
  101. m->x = desc.x;
  102. if (desc.y == -1)
  103. m->y = monitorY + (monitorHeight - desc.height) / 2;
  104. else if (desc.screen != (UINT32)-1)
  105. m->y = monitorY + desc.y;
  106. else
  107. m->y = desc.y;
  108. m->width = desc.width;
  109. m->height = desc.height;
  110. m->xWindow = XCreateWindow(display,
  111. XRootWindow(display, screen),
  112. m->x, m->y,
  113. m->width, m->height,
  114. 0, desc.visualInfo.depth,
  115. InputOutput, desc.visualInfo.visual,
  116. CWBackPixel | CWBorderPixel | CWColormap | CWBackPixmap, &attributes);
  117. XStoreName(display, m->xWindow, desc.title.c_str());
  118. // Position/size might have (and usually will) get overridden by the WM, so re-apply them
  119. XSizeHints hints;
  120. hints.flags = PPosition | PSize;
  121. hints.x = m->x;
  122. hints.y = m->y;
  123. hints.width = m->width;
  124. hints.height = m->height;
  125. if(!desc.allowResize)
  126. {
  127. hints.flags |= PMinSize | PMaxSize;
  128. hints.min_height = desc.height;
  129. hints.max_height = desc.height;
  130. hints.min_width = desc.width;
  131. hints.max_width = desc.width;
  132. }
  133. XSetNormalHints(display, m->xWindow, &hints);
  134. setShowDecorations(desc.showDecorations);
  135. setIsModal(desc.modal);
  136. // Ensures the child window is always on top of the parent window
  137. if(desc.parent)
  138. XSetTransientForHint(display, m->xWindow, desc.parent);
  139. XSelectInput(display, m->xWindow,
  140. ExposureMask | FocusChangeMask |
  141. KeyPressMask | KeyReleaseMask |
  142. ButtonPressMask | ButtonReleaseMask |
  143. EnterWindowMask | LeaveWindowMask |
  144. PointerMotionMask | ButtonMotionMask |
  145. StructureNotifyMask | PropertyChangeMask
  146. );
  147. XMapWindow(display, m->xWindow);
  148. // Make sure we get the window delete message from WM, so we can clean up ourselves
  149. Atom atomDeleteWindow = XInternAtom(display, "WM_DELETE_WINDOW", False);
  150. XSetWMProtocols(display, m->xWindow, &atomDeleteWindow, 1);
  151. // Enable drag and drop
  152. LinuxDragAndDrop::makeDNDAware(m->xWindow);
  153. // Set background image if assigned
  154. if(desc.background)
  155. {
  156. Pixmap pixmap = LinuxPlatform::createPixmap(*desc.background, (UINT32)desc.visualInfo.depth);
  157. XSetWindowBackgroundPixmap(display, m->xWindow, pixmap);
  158. XFreePixmap(display, pixmap);
  159. XSync(display, 0);
  160. }
  161. if(!desc.showOnTaskBar)
  162. showOnTaskbar(false);
  163. m->hasTitleBar = desc.showDecorations;
  164. m->resizeDisabled = !desc.allowResize;
  165. LinuxPlatform::_registerWindow(m->xWindow, this);
  166. }
  167. LinuxWindow::~LinuxWindow()
  168. {
  169. if(m->xWindow != 0)
  170. _cleanUp();
  171. bs_delete(m);
  172. }
  173. void LinuxWindow::close()
  174. {
  175. XDestroyWindow(LinuxPlatform::getXDisplay(), m->xWindow);
  176. XFlush(LinuxPlatform::getXDisplay());
  177. _cleanUp();
  178. }
  179. void LinuxWindow::move(INT32 x, INT32 y)
  180. {
  181. m->x = x;
  182. m->y = y;
  183. XMoveWindow(LinuxPlatform::getXDisplay(), m->xWindow, x, y);
  184. }
  185. void LinuxWindow::resize(UINT32 width, UINT32 height)
  186. {
  187. // If resize is disabled on WM level, we need to force it
  188. if(m->resizeDisabled)
  189. {
  190. XSizeHints hints;
  191. hints.flags = PMinSize | PMaxSize;
  192. hints.min_height = height;
  193. hints.max_height = height;
  194. hints.min_width = width;
  195. hints.max_width = width;
  196. XSetNormalHints(LinuxPlatform::getXDisplay(), m->xWindow, &hints);
  197. }
  198. m->width = width;
  199. m->height = height;
  200. XResizeWindow(LinuxPlatform::getXDisplay(), m->xWindow, width, height);
  201. }
  202. void LinuxWindow::hide()
  203. {
  204. XUnmapWindow(LinuxPlatform::getXDisplay(), m->xWindow);
  205. }
  206. void LinuxWindow::show()
  207. {
  208. XMapWindow(LinuxPlatform::getXDisplay(), m->xWindow);
  209. XMoveResizeWindow(LinuxPlatform::getXDisplay(), m->xWindow, m->x, m->y, m->width, m->height);
  210. }
  211. void LinuxWindow::maximize()
  212. {
  213. maximize(true);
  214. }
  215. void LinuxWindow::minimize()
  216. {
  217. minimize(true);
  218. }
  219. void LinuxWindow::restore()
  220. {
  221. if(isMaximized())
  222. maximize(false);
  223. else if(isMinimized())
  224. minimize(false);
  225. }
  226. INT32 LinuxWindow::getLeft() const
  227. {
  228. INT32 x, y;
  229. ::Window child;
  230. XTranslateCoordinates(LinuxPlatform::getXDisplay(), m->xWindow, DefaultRootWindow(LinuxPlatform::getXDisplay()),
  231. 0, 0, &x, &y, &child);
  232. return x;
  233. }
  234. INT32 LinuxWindow::getTop() const
  235. {
  236. INT32 x, y;
  237. ::Window child;
  238. XTranslateCoordinates(LinuxPlatform::getXDisplay(), m->xWindow, DefaultRootWindow(LinuxPlatform::getXDisplay()),
  239. 0, 0, &x, &y, &child);
  240. return y;
  241. }
  242. UINT32 LinuxWindow::getWidth() const
  243. {
  244. XWindowAttributes xwa;
  245. XGetWindowAttributes(LinuxPlatform::getXDisplay(), m->xWindow, &xwa);
  246. return (UINT32)xwa.width;
  247. }
  248. UINT32 LinuxWindow::getHeight() const
  249. {
  250. XWindowAttributes xwa;
  251. XGetWindowAttributes(LinuxPlatform::getXDisplay(), m->xWindow, &xwa);
  252. return (UINT32)xwa.height;
  253. }
  254. Vector2I LinuxWindow::windowToScreenPos(const Vector2I& windowPos) const
  255. {
  256. Vector2I screenPos;
  257. ::Window child;
  258. XTranslateCoordinates(LinuxPlatform::getXDisplay(), m->xWindow, DefaultRootWindow(LinuxPlatform::getXDisplay()),
  259. windowPos.x, windowPos.y, &screenPos.x, &screenPos.y, &child);
  260. return screenPos;
  261. }
  262. Vector2I LinuxWindow::screenToWindowPos(const Vector2I& screenPos) const
  263. {
  264. Vector2I windowPos;
  265. ::Window child;
  266. XTranslateCoordinates(LinuxPlatform::getXDisplay(), DefaultRootWindow(LinuxPlatform::getXDisplay()), m->xWindow,
  267. screenPos.x, screenPos.y, &windowPos.x, &windowPos.y, &child);
  268. return windowPos;
  269. }
  270. void LinuxWindow::setIcon(const PixelData& data)
  271. {
  272. ::Display* display = LinuxPlatform::getXDisplay();
  273. Pixmap iconPixmap = LinuxPlatform::createPixmap(data, (UINT32)XDefaultDepth(display, XDefaultScreen(display)));
  274. XWMHints* hints = XAllocWMHints();
  275. hints->flags = IconPixmapHint;
  276. hints->icon_pixmap = iconPixmap;
  277. XSetWMHints(display, m->xWindow, hints);
  278. XFlush(display);
  279. XFree(hints);
  280. XFreePixmap(display, iconPixmap);
  281. }
  282. void LinuxWindow::_cleanUp()
  283. {
  284. LinuxPlatform::_unregisterWindow(m->xWindow);
  285. m->xWindow = 0;
  286. }
  287. void LinuxWindow::_setDragZone(const Rect2I& rect)
  288. {
  289. m->dragZone = rect;
  290. }
  291. bool LinuxWindow::_dragStart(INT32 x, INT32 y)
  292. {
  293. if(m->hasTitleBar)
  294. return false;
  295. if(m->dragZone.width == 0 || m->dragZone.height == 0)
  296. return false;
  297. if(x >= m->dragZone.x && x < (INT32)(m->dragZone.x + m->dragZone.width) &&
  298. y >= m->dragZone.y && y < (INT32)(m->dragZone.y + m->dragZone.height))
  299. {
  300. m->dragStartX = x;
  301. m->dragStartY = y;
  302. m->dragInProgress = true;
  303. return true;
  304. }
  305. return false;
  306. }
  307. void LinuxWindow::_dragUpdate(INT32 x, INT32 y)
  308. {
  309. if(!m->dragInProgress)
  310. return;
  311. INT32 offsetX = x - m->dragStartX;
  312. INT32 offsetY = y - m->dragStartY;
  313. move(getLeft() + offsetX, getTop() + offsetY);
  314. }
  315. void LinuxWindow::_dragEnd()
  316. {
  317. m->dragInProgress = false;
  318. }
  319. ::Window LinuxWindow::_getXWindow() const
  320. {
  321. return m->xWindow;
  322. }
  323. void LinuxWindow::_setUserData(void* data)
  324. {
  325. m->userData = data;
  326. }
  327. void* LinuxWindow::_getUserData() const
  328. {
  329. return m->userData;
  330. }
  331. bool LinuxWindow::isMaximized() const
  332. {
  333. Atom wmState = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE", False);
  334. Atom type;
  335. INT32 format;
  336. uint64_t length;
  337. uint64_t remaining;
  338. uint8_t* data = nullptr;
  339. INT32 result = XGetWindowProperty(LinuxPlatform::getXDisplay(), m->xWindow, wmState,
  340. 0, 1024, False, XA_ATOM, &type, &format,
  341. &length, &remaining, &data);
  342. if (result == Success)
  343. {
  344. Atom* atoms = (Atom*)data;
  345. Atom wmMaxHorz = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE_MAXIMIZED_HORZ", False);
  346. Atom wmMaxVert = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE_MAXIMIZED_VERT", False);
  347. bool foundHorz = false;
  348. bool foundVert = false;
  349. for (UINT32 i = 0; i < length; i++)
  350. {
  351. if (atoms[i] == wmMaxHorz)
  352. foundHorz = true;
  353. if (atoms[i] == wmMaxVert)
  354. foundVert = true;
  355. if (foundVert && foundHorz)
  356. return true;
  357. }
  358. XFree(atoms);
  359. }
  360. return false;
  361. }
  362. bool LinuxWindow::isMinimized()
  363. {
  364. Atom wmState = XInternAtom(LinuxPlatform::getXDisplay(), "WM_STATE", True);
  365. Atom type;
  366. INT32 format;
  367. uint64_t length;
  368. uint64_t remaining;
  369. uint8_t* data = nullptr;
  370. INT32 result = XGetWindowProperty(LinuxPlatform::getXDisplay(), m->xWindow, wmState,
  371. 0, 1024, False, AnyPropertyType, &type, &format,
  372. &length, &remaining, &data);
  373. if(result == Success)
  374. {
  375. long* state = (long*) data;
  376. if(state[0] == WM_IconicState)
  377. return true;
  378. }
  379. return false;
  380. }
  381. void LinuxWindow::maximize(bool enable)
  382. {
  383. Atom wmState = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE", False);
  384. Atom wmMaxHorz = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE_MAXIMIZED_HORZ", False);
  385. Atom wmMaxVert = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE_MAXIMIZED_VERT", False);
  386. XEvent xev;
  387. memset(&xev, 0, sizeof(xev));
  388. xev.type = ClientMessage;
  389. xev.xclient.window = m->xWindow;
  390. xev.xclient.message_type = wmState;
  391. xev.xclient.format = 32;
  392. xev.xclient.data.l[0] = enable ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
  393. xev.xclient.data.l[1] = wmMaxHorz;
  394. xev.xclient.data.l[2] = wmMaxVert;
  395. XSendEvent(LinuxPlatform::getXDisplay(), DefaultRootWindow(LinuxPlatform::getXDisplay()), False,
  396. SubstructureRedirectMask | SubstructureNotifyMask, &xev);
  397. }
  398. void LinuxWindow::minimize(bool enable)
  399. {
  400. XEvent xev;
  401. Atom wmChange = XInternAtom(LinuxPlatform::getXDisplay(), "WM_CHANGE_STATE", False);
  402. memset(&xev, 0, sizeof(xev));
  403. xev.type = ClientMessage;
  404. xev.xclient.window = m->xWindow;
  405. xev.xclient.message_type = wmChange;
  406. xev.xclient.format = 32;
  407. xev.xclient.data.l[0] = enable ? WM_IconicState : WM_NormalState;
  408. XSendEvent(LinuxPlatform::getXDisplay(), DefaultRootWindow(LinuxPlatform::getXDisplay()), False,
  409. SubstructureRedirectMask | SubstructureNotifyMask, &xev);
  410. }
  411. void LinuxWindow::showOnTaskbar(bool enable)
  412. {
  413. Atom wmState = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE", False);
  414. Atom wmSkipTaskbar = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE_SKIP_TASKBAR", False);
  415. XEvent xev;
  416. memset(&xev, 0, sizeof(xev));
  417. xev.type = ClientMessage;
  418. xev.xclient.window = m->xWindow;
  419. xev.xclient.message_type = wmState;
  420. xev.xclient.format = 32;
  421. xev.xclient.data.l[0] = enable ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
  422. xev.xclient.data.l[1] = wmSkipTaskbar;
  423. XSendEvent(LinuxPlatform::getXDisplay(), DefaultRootWindow(LinuxPlatform::getXDisplay()), False,
  424. SubstructureRedirectMask | SubstructureNotifyMask, &xev);
  425. }
  426. void LinuxWindow::_setFullscreen(bool fullscreen)
  427. {
  428. // Attempt to bypass compositor if switching to fullscreen
  429. if(fullscreen)
  430. {
  431. Atom wmBypassCompositor = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_BYPASS_COMPOSITOR", False);
  432. if (wmBypassCompositor)
  433. {
  434. static constexpr UINT32 enabled = 1;
  435. XChangeProperty(LinuxPlatform::getXDisplay(), m->xWindow, wmBypassCompositor,
  436. XA_CARDINAL, 32, PropModeReplace, (unsigned char*) &enabled, 1);
  437. }
  438. }
  439. // Make the switch to fullscreen
  440. XEvent xev;
  441. Atom wmState = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE", False);
  442. Atom wmFullscreen = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE_FULLSCREEN", False);
  443. memset(&xev, 0, sizeof(xev));
  444. xev.type = ClientMessage;
  445. xev.xclient.window = m->xWindow;
  446. xev.xclient.message_type = wmState;
  447. xev.xclient.format = 32;
  448. xev.xclient.data.l[0] = fullscreen ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
  449. xev.xclient.data.l[1] = wmFullscreen;
  450. xev.xclient.data.l[2] = 0;
  451. XSendEvent(LinuxPlatform::getXDisplay(), DefaultRootWindow(LinuxPlatform::getXDisplay()), False,
  452. SubstructureRedirectMask | SubstructureNotifyMask, &xev);
  453. }
  454. void LinuxWindow::setShowDecorations(bool show)
  455. {
  456. static constexpr UINT32 MWM_HINTS_DECORATIONS = (1 << 1);
  457. struct MotifHints
  458. {
  459. UINT32 flags;
  460. UINT32 functions;
  461. UINT32 decorations;
  462. INT32 inputMode;
  463. UINT32 status;
  464. };
  465. if(show)
  466. return;
  467. MotifHints motifHints;
  468. motifHints.flags = MWM_HINTS_DECORATIONS;
  469. motifHints.decorations = 0;
  470. motifHints.functions = 0;
  471. motifHints.inputMode = 0;
  472. motifHints.status = 0;
  473. Atom wmHintsAtom = XInternAtom(LinuxPlatform::getXDisplay(), "_MOTIF_WM_HINTS", False);
  474. XChangeProperty(LinuxPlatform::getXDisplay(), m->xWindow,
  475. wmHintsAtom, wmHintsAtom,
  476. 32,
  477. PropModeReplace,
  478. (unsigned char *)&motifHints,
  479. 5);
  480. }
  481. void LinuxWindow::setIsModal(bool modal)
  482. {
  483. if(modal)
  484. {
  485. Atom wmState = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE", False);
  486. Atom wmValue = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE_MODAL", False);
  487. XEvent xev;
  488. memset(&xev, 0, sizeof(xev));
  489. xev.type = ClientMessage;
  490. xev.xclient.window = m->xWindow;
  491. xev.xclient.message_type = wmState;
  492. xev.xclient.format = 32;
  493. xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
  494. xev.xclient.data.l[1] = wmValue;
  495. xev.xclient.data.l[2] = 0;
  496. xev.xclient.data.l[3] = 1;
  497. XSendEvent(LinuxPlatform::getXDisplay(), DefaultRootWindow(LinuxPlatform::getXDisplay()), False,
  498. SubstructureRedirectMask | SubstructureNotifyMask, &xev);
  499. }
  500. }
  501. }