BsUnixWindow.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. #include "BsUnixWindow.h"
  4. #include "BsUnixPlatform.h"
  5. #include "Math/BsRect2I.h"
  6. #include <X11/Xlib.h>
  7. #include <X11/Xutil.h>
  8. #include <X11/Xatom.h>
  9. #include <X11/extensions/Xrandr.h>
  10. #define _NET_WM_STATE_REMOVE 0
  11. #define _NET_WM_STATE_ADD 1
  12. #define _NET_WM_STATE_TOGGLE 2
  13. #define WM_NormalState 1
  14. #define WM_IconicState 3
  15. namespace bs
  16. {
  17. enum class WindowState
  18. {
  19. Minimized,
  20. Maximized,
  21. Normal
  22. };
  23. struct LinuxWindow::Pimpl
  24. {
  25. ::Window xWindow = 0;
  26. INT32 x, y;
  27. UINT32 width, height;
  28. bool hasTitleBar = true;
  29. bool dragInProgress = false;
  30. bool resizeDisabled = false;
  31. WindowState state = WindowState::Normal;
  32. Rect2I dragZone;
  33. int32_t dragStartX, dragStartY;
  34. };
  35. LinuxWindow::LinuxWindow(const WINDOW_DESC &desc)
  36. {
  37. ::Display* display = LinuxPlatform::getXDisplay();
  38. int screen;
  39. if(desc.screen == (UINT32)-1)
  40. screen = XDefaultScreen(display);
  41. else
  42. screen = std::min((int)desc.screen, XScreenCount(display));
  43. XSetWindowAttributes attributes;
  44. attributes.background_pixel = XWhitePixel(display, screen);
  45. attributes.border_pixel = XBlackPixel(display, screen);
  46. attributes.colormap = XCreateColormap(display,
  47. XRootWindow(display, desc.visualInfo.screen),
  48. desc.visualInfo.visual,
  49. AllocNone);
  50. uint32_t borderWidth = 0;
  51. m->x = desc.x;
  52. m->y = desc.y;
  53. m->width = desc.width;
  54. m->height = desc.height;
  55. m->xWindow = XCreateWindow(display,
  56. XRootWindow(display, desc.visualInfo.screen),
  57. desc.x, desc.y,
  58. desc.width, desc.height,
  59. borderWidth, desc.visualInfo.depth,
  60. InputOutput, desc.visualInfo.visual,
  61. CWBackPixel | CWBorderPixel | CWColormap, &attributes);
  62. XStoreName(display, m->xWindow, desc.title.c_str());
  63. XSizeHints hints;
  64. hints.flags = PPosition | PSize;
  65. hints.x = desc.x;
  66. hints.y = desc.y;
  67. hints.width = desc.width;
  68. hints.height = desc.height;
  69. if(!desc.allowResize)
  70. {
  71. hints.flags |= PMinSize | PMaxSize;
  72. hints.min_height = desc.height;
  73. hints.max_height = desc.height;
  74. hints.min_width = desc.width;
  75. hints.max_width = desc.width;
  76. }
  77. XSetNormalHints(display, m->xWindow, &hints);
  78. setShowDecorations(desc.showDecorations);
  79. setIsModal(desc.modal);
  80. // Ensures the child window is always on top of the parent window
  81. if(desc.parent)
  82. XSetTransientForHint(display, m->xWindow, desc.parent);
  83. XSelectInput(display, m->xWindow,
  84. ExposureMask | FocusChangeMask |
  85. KeyPressMask | KeyReleaseMask |
  86. ButtonPressMask | ButtonReleaseMask |
  87. EnterWindowMask | LeaveWindowMask |
  88. PointerMotionMask | ButtonMotionMask |
  89. StructureNotifyMask
  90. );
  91. XMapWindow(display, m->xWindow);
  92. // Make sure we get the window delete message from WM, so we can clean up ourselves
  93. Atom atomDeleteWindow = XInternAtom(display, "WM_DELETE_WINDOW", False);
  94. XSetWMProtocols(display, m->xWindow, &atomDeleteWindow, 1);
  95. // Set background image if assigned
  96. if(desc.background)
  97. {
  98. Pixmap pixmap = LinuxPlatform::createPixmap(desc.background);
  99. XSetWindowBackgroundPixmap(display, m->xWindow, pixmap);
  100. XFreePixmap(display, pixmap);
  101. }
  102. m->hasTitleBar = desc.showDecorations;
  103. m->resizeDisabled = !desc.allowResize;
  104. LinuxPlatform::_registerWindow(m->xWindow, this);
  105. }
  106. LinuxWindow::~LinuxWindow()
  107. {
  108. if(m->xWindow != 0)
  109. _cleanUp();
  110. }
  111. void LinuxWindow::close()
  112. {
  113. XDestroyWindow(LinuxPlatform::getXDisplay(), m->xWindow);
  114. XFlush(LinuxPlatform::getXDisplay());
  115. _cleanUp();
  116. }
  117. void LinuxWindow::move(INT32 x, INT32 y)
  118. {
  119. m->x = x;
  120. m->y = y;
  121. XMoveWindow(LinuxPlatform::getXDisplay(), m->xWindow, x, y);
  122. }
  123. void LinuxWindow::resize(UINT32 width, UINT32 height)
  124. {
  125. // If resize is disabled on WM level, we need to force it
  126. if(m->resizeDisabled)
  127. {
  128. XSizeHints hints;
  129. hints.flags = PMinSize | PMaxSize;
  130. hints.min_height = height;
  131. hints.max_height = height;
  132. hints.min_width = width;
  133. hints.max_width = width;
  134. XSetNormalHints(LinuxPlatform::getXDisplay(), m->xWindow, &hints);
  135. }
  136. m->width = width;
  137. m->height = height;
  138. XResizeWindow(LinuxPlatform::getXDisplay(), m->xWindow, width, height);
  139. }
  140. void LinuxWindow::hide()
  141. {
  142. // TODOPORT - Need to track all states so I can restore them on show()
  143. XUnmapWindow(LinuxPlatform::getXDisplay(), m->xWindow);
  144. }
  145. void LinuxWindow::show()
  146. {
  147. XMapWindow(LinuxPlatform::getXDisplay(), m->xWindow);
  148. XMoveResizeWindow(LinuxPlatform::getXDisplay(), m->xWindow, m->x, m->y, m->width, m->height);
  149. // TODOPORT - Restore all states (pos, size and style)
  150. }
  151. void LinuxWindow::maximize()
  152. {
  153. maximize(true);
  154. }
  155. void LinuxWindow::minimize()
  156. {
  157. minimize(true);
  158. }
  159. void LinuxWindow::restore()
  160. {
  161. if(isMaximized())
  162. maximize(false);
  163. else if(isMinimized())
  164. minimize(false);
  165. }
  166. INT32 LinuxWindow::getLeft() const
  167. {
  168. INT32 x, y;
  169. ::Window child;
  170. XTranslateCoordinates(LinuxPlatform::getXDisplay(), m->xWindow, DefaultRootWindow(LinuxPlatform::getXDisplay()),
  171. 0, 0, &x, &y, &child);
  172. return x;
  173. }
  174. INT32 LinuxWindow::getTop() const
  175. {
  176. INT32 x, y;
  177. ::Window child;
  178. XTranslateCoordinates(LinuxPlatform::getXDisplay(), m->xWindow, DefaultRootWindow(LinuxPlatform::getXDisplay()),
  179. 0, 0, &x, &y, &child);
  180. return y;
  181. }
  182. UINT32 LinuxWindow::getWidth() const
  183. {
  184. XWindowAttributes xwa;
  185. XGetWindowAttributes(LinuxPlatform::getXDisplay(), m->xWindow, &xwa);
  186. return xwa.width;
  187. }
  188. UINT32 LinuxWindow::getHeight() const
  189. {
  190. XWindowAttributes xwa;
  191. XGetWindowAttributes(LinuxPlatform::getXDisplay(), m->xWindow, &xwa);
  192. return xwa.height;
  193. }
  194. Vector2I LinuxWindow::windowToScreenPos(const Vector2I& windowPos) const
  195. {
  196. Vector2I screenPos;
  197. ::Window child;
  198. XTranslateCoordinates(LinuxPlatform::getXDisplay(), m->xWindow, DefaultRootWindow(LinuxPlatform::getXDisplay()),
  199. windowPos.x, windowPos.y, &screenPos.x, &screenPos.y, &child);
  200. return screenPos;
  201. }
  202. Vector2I LinuxWindow::screenToWindowPos(const Vector2I& screenPos) const
  203. {
  204. Vector2I windowPos;
  205. ::Window child;
  206. XTranslateCoordinates(LinuxPlatform::getXDisplay(), DefaultRootWindow(LinuxPlatform::getXDisplay()), m->xWindow,
  207. screenPos.x, screenPos.y, &windowPos.x, &windowPos.y, &child);
  208. return windowPos;
  209. }
  210. void LinuxWindow::setIcon(const SPtr<PixelData>& data)
  211. {
  212. Pixmap iconPixmap = LinuxPlatform::createPixmap(data);
  213. XWMHints* hints = XAllocWMHints();
  214. hints->flags = IconPixmapHint;
  215. hints->icon_pixmap = iconPixmap;
  216. XSetWMHints(LinuxPlatform::getXDisplay(), m->xWindow, hints);
  217. XFlush(LinuxPlatform::getXDisplay());
  218. XFree(hints);
  219. XFreePixmap(LinuxPlatform::getXDisplay(), iconPixmap);
  220. }
  221. void LinuxWindow::_cleanUp()
  222. {
  223. LinuxPlatform::_unregisterWindow(m->xWindow);
  224. m->xWindow = 0;
  225. }
  226. bool LinuxWindow::_dragStart(int32_t x, int32_t y)
  227. {
  228. if(m->hasTitleBar)
  229. return false;
  230. if(m->dragZone.width == 0 || m->dragZone.height == 0)
  231. return false;
  232. if(x >= m->dragZone.x && x < (m->dragZone.x + m->dragZone.width) &&
  233. y >= m->dragZone.y && y < (m->dragZone.y + m->dragZone.height))
  234. {
  235. m->dragStartX = x;
  236. m->dragStartY = y;
  237. m->dragInProgress = true;
  238. return true;
  239. }
  240. return false;
  241. }
  242. void LinuxWindow::_dragUpdate(int32_t x, int32_t y)
  243. {
  244. if(!m->dragInProgress)
  245. return;
  246. int32_t offsetX = x - m->dragStartX;
  247. int32_t offsetY = y - m->dragStartY;
  248. move(getLeft() + offsetX, getTop() + offsetY);
  249. }
  250. void LinuxWindow::_dragEnd()
  251. {
  252. m->dragInProgress = false;
  253. }
  254. ::Window LinuxWindow::_getXWindow() const
  255. {
  256. return m->xWindow;
  257. }
  258. bool LinuxWindow::isMaximized() const
  259. {
  260. Atom wmState = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE", False);
  261. Atom type;
  262. int32_t format;
  263. uint64_t length;
  264. uint64_t remaining;
  265. uint8_t* data = nullptr;
  266. int32_t result = XGetWindowProperty(LinuxPlatform::getXDisplay(), m->xWindow, wmState,
  267. 0, 1024, False, XA_ATOM, &type, &format,
  268. &length, &remaining, &data);
  269. if (result == Success)
  270. {
  271. Atom* atoms = (Atom*)data;
  272. Atom wmMaxHorz = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE_MAXIMIZED_HORZ", False);
  273. Atom wmMaxVert = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE_MAXIMIZED_VERT", False);
  274. bool foundHorz = false;
  275. bool foundVert = false;
  276. for (uint64_t i = 0; i < length; i++)
  277. {
  278. if (atoms[i] == wmMaxHorz)
  279. foundHorz = true;
  280. if (atoms[i] == wmMaxVert)
  281. foundVert = true;
  282. if (foundVert && foundHorz)
  283. return true;
  284. }
  285. XFree(atoms);
  286. }
  287. return false;
  288. }
  289. bool LinuxWindow::isMinimized()
  290. {
  291. Atom wmState = XInternAtom(LinuxPlatform::getXDisplay(), "WM_STATE", True);
  292. Atom type;
  293. int32_t format;
  294. uint64_t length;
  295. uint64_t remaining;
  296. uint8_t* data = nullptr;
  297. int32_t result = XGetWindowProperty(LinuxPlatform::getXDisplay(), m->xWindow, wmState,
  298. 0, 1024, False, AnyPropertyType, &type, &format,
  299. &length, &remaining, &data);
  300. if(result == Success)
  301. {
  302. long* state = (long*) data;
  303. if(state[0] == WM_IconicState)
  304. return true;
  305. }
  306. return false;
  307. }
  308. void LinuxWindow::maximize(bool enable)
  309. {
  310. Atom wmState = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE", False);
  311. Atom wmMaxHorz = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE_MAXIMIZED_HORZ", False);
  312. Atom wmMaxVert = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE_MAXIMIZED_VERT", False);
  313. XEvent xev;
  314. memset(&xev, 0, sizeof(xev));
  315. xev.type = ClientMessage;
  316. xev.xclient.window = m->xWindow;
  317. xev.xclient.message_type = wmState;
  318. xev.xclient.format = 32;
  319. xev.xclient.data.l[0] = enable ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
  320. xev.xclient.data.l[1] = wmMaxHorz;
  321. xev.xclient.data.l[2] = wmMaxVert;
  322. XSendEvent(LinuxPlatform::getXDisplay(), DefaultRootWindow(LinuxPlatform::getXDisplay()), False,
  323. SubstructureRedirectMask | SubstructureNotifyMask, &xev);
  324. }
  325. void LinuxWindow::minimize(bool enable)
  326. {
  327. XEvent xev;
  328. Atom wmChange = XInternAtom(LinuxPlatform::getXDisplay(), "WM_CHANGE_STATE", False);
  329. memset(&xev, 0, sizeof(xev));
  330. xev.type = ClientMessage;
  331. xev.xclient.window = m->xWindow;
  332. xev.xclient.message_type = wmChange;
  333. xev.xclient.format = 32;
  334. xev.xclient.data.l[0] = enable ? WM_IconicState : WM_NormalState;
  335. XSendEvent(LinuxPlatform::getXDisplay(), DefaultRootWindow(LinuxPlatform::getXDisplay()), False,
  336. SubstructureRedirectMask | SubstructureNotifyMask, &xev);
  337. }
  338. void LinuxWindow::_setFullscreen(bool fullscreen)
  339. {
  340. // Attempt to bypass compositor if switching to fullscreen
  341. if(fullscreen)
  342. {
  343. Atom wmBypassCompositor = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_BYPASS_COMPOSITOR", False);
  344. if (wmBypassCompositor)
  345. {
  346. static constexpr uint32_t enabled = 1;
  347. XChangeProperty(LinuxPlatform::getXDisplay(), m->xWindow, wmBypassCompositor,
  348. XA_CARDINAL, 32, PropModeReplace, (unsigned char*) &enabled, 1);
  349. }
  350. }
  351. // Make the switch to fullscreen
  352. XEvent xev;
  353. Atom wmState = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE", False);
  354. Atom wmFullscreen = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE_FULLSCREEN", False);
  355. memset(&xev, 0, sizeof(xev));
  356. xev.type = ClientMessage;
  357. xev.xclient.window = m->xWindow;
  358. xev.xclient.message_type = wmState;
  359. xev.xclient.format = 32;
  360. xev.xclient.data.l[0] = fullscreen ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
  361. xev.xclient.data.l[1] = wmFullscreen;
  362. xev.xclient.data.l[2] = 0;
  363. XSendEvent(LinuxPlatform::getXDisplay(), DefaultRootWindow(LinuxPlatform::getXDisplay()), False,
  364. SubstructureRedirectMask | SubstructureNotifyMask, &xev);
  365. }
  366. void LinuxWindow::setShowDecorations(bool show)
  367. {
  368. static constexpr uint32_t MWM_HINTS_FUNCTIONS = (1 << 0);
  369. static constexpr uint32_t MWM_HINTS_DECORATIONS = (1 << 1);
  370. static constexpr uint32_t MWM_DECOR_BORDER = (1 << 1);
  371. static constexpr uint32_t MWM_DECOR_RESIZEH = (1 << 2);
  372. static constexpr uint32_t MWM_DECOR_TITLE = (1 << 3);
  373. static constexpr uint32_t MWM_DECOR_MENU = (1 << 4);
  374. static constexpr uint32_t MWM_DECOR_MINIMIZE = (1 << 5);
  375. static constexpr uint32_t MWM_DECOR_MAXIMIZE = (1 << 6);
  376. static constexpr uint32_t MWM_FUNC_RESIZE = (1 << 1);
  377. static constexpr uint32_t MWM_FUNC_MOVE = (1 << 2);
  378. static constexpr uint32_t MWM_FUNC_MINIMIZE = (1 << 3);
  379. static constexpr uint32_t MWM_FUNC_MAXIMIZE = (1 << 4);
  380. static constexpr uint32_t MWM_FUNC_CLOSE = (1 << 5);
  381. struct MotifHints
  382. {
  383. uint32_t flags;
  384. uint32_t functions;
  385. uint32_t decorations;
  386. int32_t inputMode;
  387. uint32_t status;
  388. };
  389. if(show)
  390. return;
  391. MotifHints motifHints;
  392. motifHints.flags = MWM_HINTS_DECORATIONS;
  393. motifHints.decorations = 0;
  394. motifHints.functions = 0;
  395. motifHints.inputMode = 0;
  396. motifHints.status = 0;
  397. Atom wmHintsAtom = XInternAtom(LinuxPlatform::getXDisplay(), "_MOTIF_WM_HINTS", False);
  398. XChangeProperty(LinuxPlatform::getXDisplay(), m->xWindow,
  399. wmHintsAtom, wmHintsAtom,
  400. 32,
  401. PropModeReplace,
  402. (unsigned char *)&motifHints,
  403. 5);
  404. }
  405. void LinuxWindow::setIsModal(bool modal)
  406. {
  407. if(modal)
  408. {
  409. Atom wmState = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE", False);
  410. Atom wmValue = XInternAtom(LinuxPlatform::getXDisplay(), "_NET_WM_STATE_MODAL", False);
  411. XEvent xev;
  412. memset(&xev, 0, sizeof(xev));
  413. xev.type = ClientMessage;
  414. xev.xclient.window = m->xWindow;
  415. xev.xclient.message_type = wmState;
  416. xev.xclient.format = 32;
  417. xev.xclient.data.l[0] = _NET_WM_STATE_ADD;
  418. xev.xclient.data.l[1] = wmValue;
  419. xev.xclient.data.l[2] = 0;
  420. xev.xclient.data.l[3] = 1;
  421. XSendEvent(LinuxPlatform::getXDisplay(), DefaultRootWindow(LinuxPlatform::getXDisplay()), False,
  422. SubstructureRedirectMask | SubstructureNotifyMask, &xev);
  423. }
  424. }
  425. }