BsLinuxWindow.cpp 14 KB

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