Window.cpp 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282
  1. /**
  2. * Copyright (c) 2006-2019 LOVE Development Team
  3. *
  4. * This software is provided 'as-is', without any express or implied
  5. * warranty. In no event will the authors be held liable for any damages
  6. * arising from the use of this software.
  7. *
  8. * Permission is granted to anyone to use this software for any purpose,
  9. * including commercial applications, and to alter it and redistribute it
  10. * freely, subject to the following restrictions:
  11. *
  12. * 1. The origin of this software must not be misrepresented; you must not
  13. * claim that you wrote the original software. If you use this software
  14. * in a product, an acknowledgment in the product documentation would be
  15. * appreciated but is not required.
  16. * 2. Altered source versions must be plainly marked as such, and must not be
  17. * misrepresented as being the original software.
  18. * 3. This notice may not be removed or altered from any source distribution.
  19. **/
  20. // LOVE
  21. #include "common/config.h"
  22. #include "graphics/Graphics.h"
  23. #include "Window.h"
  24. #ifdef LOVE_ANDROID
  25. #include "common/android.h"
  26. #endif
  27. #ifdef LOVE_IOS
  28. #include "common/ios.h"
  29. #endif
  30. // C++
  31. #include <iostream>
  32. #include <vector>
  33. #include <algorithm>
  34. // C
  35. #include <cstdio>
  36. // SDL
  37. #include <SDL_syswm.h>
  38. #if defined(LOVE_WINDOWS)
  39. #include <windows.h>
  40. #elif defined(LOVE_MACOSX)
  41. #include "common/macosx.h"
  42. #endif
  43. #ifndef APIENTRY
  44. #define APIENTRY
  45. #endif
  46. namespace love
  47. {
  48. namespace window
  49. {
  50. namespace sdl
  51. {
  52. Window::Window()
  53. : open(false)
  54. , mouseGrabbed(false)
  55. , window(nullptr)
  56. , context(nullptr)
  57. , displayedWindowError(false)
  58. , hasSDL203orEarlier(false)
  59. , contextAttribs()
  60. {
  61. if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0)
  62. throw love::Exception("Could not initialize SDL video subsystem (%s)", SDL_GetError());
  63. // Make sure the screensaver doesn't activate by default.
  64. setDisplaySleepEnabled(false);
  65. SDL_version version = {};
  66. SDL_GetVersion(&version);
  67. hasSDL203orEarlier = (version.major == 2 && version.minor == 0 && version.patch <= 3);
  68. }
  69. Window::~Window()
  70. {
  71. close(false);
  72. graphics.set(nullptr);
  73. SDL_QuitSubSystem(SDL_INIT_VIDEO);
  74. }
  75. void Window::setGraphics(graphics::Graphics *graphics)
  76. {
  77. this->graphics.set(graphics);
  78. }
  79. void Window::setGLFramebufferAttributes(int msaa, bool sRGB, bool stencil, int depth)
  80. {
  81. // Set GL window / framebuffer attributes.
  82. SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
  83. SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
  84. SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
  85. SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
  86. SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
  87. SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, stencil ? 8 : 0);
  88. SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, depth);
  89. SDL_GL_SetAttribute(SDL_GL_RETAINED_BACKING, 0);
  90. SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, (msaa > 0) ? 1 : 0);
  91. SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, (msaa > 0) ? msaa : 0);
  92. SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, sRGB ? 1 : 0);
  93. const char *driver = SDL_GetCurrentVideoDriver();
  94. if (driver && strstr(driver, "x11") == driver)
  95. {
  96. // Always disable the sRGB flag when GLX is used with older SDL versions,
  97. // because of this bug: https://bugzilla.libsdl.org/show_bug.cgi?id=2897
  98. // In practice GLX will always give an sRGB-capable framebuffer anyway.
  99. if (hasSDL203orEarlier)
  100. SDL_GL_SetAttribute(SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, 0);
  101. }
  102. #if defined(LOVE_WINDOWS)
  103. // Avoid the Microsoft OpenGL 1.1 software renderer on Windows. Apparently
  104. // older Intel drivers like to use it as a fallback when requesting some
  105. // unsupported framebuffer attribute values, rather than properly failing.
  106. SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
  107. #endif
  108. }
  109. void Window::setGLContextAttributes(const ContextAttribs &attribs)
  110. {
  111. int profilemask = 0;
  112. int contextflags = 0;
  113. if (attribs.gles)
  114. profilemask = SDL_GL_CONTEXT_PROFILE_ES;
  115. else if (attribs.versionMajor * 10 + attribs.versionMinor >= 32)
  116. profilemask |= SDL_GL_CONTEXT_PROFILE_CORE;
  117. else if (attribs.debug)
  118. profilemask = SDL_GL_CONTEXT_PROFILE_COMPATIBILITY;
  119. if (attribs.debug)
  120. contextflags |= SDL_GL_CONTEXT_DEBUG_FLAG;
  121. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, attribs.versionMajor);
  122. SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, attribs.versionMinor);
  123. SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, profilemask);
  124. SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, contextflags);
  125. }
  126. bool Window::checkGLVersion(const ContextAttribs &attribs, std::string &outversion)
  127. {
  128. typedef unsigned char GLubyte;
  129. typedef unsigned int GLenum;
  130. typedef const GLubyte *(APIENTRY *glGetStringPtr)(GLenum name);
  131. const GLenum GL_VENDOR_ENUM = 0x1F00;
  132. const GLenum GL_RENDERER_ENUM = 0x1F01;
  133. const GLenum GL_VERSION_ENUM = 0x1F02;
  134. // We don't have OpenGL headers or an automatic OpenGL function loader in
  135. // this module, so we have to get the glGetString function pointer ourselves.
  136. glGetStringPtr glGetStringFunc = (glGetStringPtr) SDL_GL_GetProcAddress("glGetString");
  137. if (!glGetStringFunc)
  138. return false;
  139. const char *glversion = (const char *) glGetStringFunc(GL_VERSION_ENUM);
  140. if (!glversion)
  141. return false;
  142. outversion = glversion;
  143. const char *glrenderer = (const char *) glGetStringFunc(GL_RENDERER_ENUM);
  144. if (glrenderer)
  145. outversion += " - " + std::string(glrenderer);
  146. const char *glvendor = (const char *) glGetStringFunc(GL_VENDOR_ENUM);
  147. if (glvendor)
  148. outversion += " (" + std::string(glvendor) + ")";
  149. int glmajor = 0;
  150. int glminor = 0;
  151. // glGetString(GL_VERSION) returns a string with the format "major.minor",
  152. // or "OpenGL ES major.minor" in GLES contexts.
  153. const char *format = "%d.%d";
  154. if (attribs.gles)
  155. format = "OpenGL ES %d.%d";
  156. if (sscanf(glversion, format, &glmajor, &glminor) != 2)
  157. return false;
  158. if (glmajor < attribs.versionMajor
  159. || (glmajor == attribs.versionMajor && glminor < attribs.versionMinor))
  160. return false;
  161. return true;
  162. }
  163. std::vector<Window::ContextAttribs> Window::getContextAttribsList() const
  164. {
  165. // If we already have a set of context attributes that we know work, just
  166. // return that. love.graphics doesn't really support switching GL versions
  167. // after the first initialization.
  168. if (contextAttribs.versionMajor > 0)
  169. return std::vector<ContextAttribs>{contextAttribs};
  170. bool preferGLES = false;
  171. #ifdef LOVE_GRAPHICS_USE_OPENGLES
  172. preferGLES = true;
  173. #endif
  174. const char *curdriver = SDL_GetCurrentVideoDriver();
  175. const char *glesdrivers[] = {"RPI", "Android", "uikit", "winrt", "emscripten"};
  176. // We always want to try OpenGL ES first on certain video backends.
  177. for (const char *glesdriver : glesdrivers)
  178. {
  179. if (curdriver && strstr(curdriver, glesdriver) == curdriver)
  180. {
  181. preferGLES = true;
  182. // Prior to SDL 2.0.4, backends that use OpenGL ES didn't properly
  183. // ask for a sRGB framebuffer when requested by SDL_GL_SetAttribute.
  184. // This doesn't account for windowing backends that sometimes use
  185. // EGL, e.g. the X11 and windows SDL backends.
  186. if (hasSDL203orEarlier)
  187. graphics::setGammaCorrect(false);
  188. break;
  189. }
  190. }
  191. if (!preferGLES)
  192. {
  193. const char *gleshint = SDL_GetHint("LOVE_GRAPHICS_USE_OPENGLES");
  194. preferGLES = (gleshint != nullptr && gleshint[0] != '0');
  195. }
  196. // Do we want a debug context?
  197. bool debug = love::graphics::isDebugEnabled();
  198. const char *preferGL2hint = SDL_GetHint("LOVE_GRAPHICS_USE_GL2");
  199. bool preferGL2 = (preferGL2hint != nullptr && preferGL2hint[0] != '0');
  200. const char *preferGL3hint = SDL_GetHint("LOVE_GRAPHICS_USE_GL3");
  201. bool preferGL3 = (preferGL3hint != nullptr && preferGL3hint[0] != '0');
  202. std::vector<ContextAttribs> glcontexts =
  203. {
  204. {4, 3, false, debug},
  205. {3, 3, false, debug},
  206. {2, 1, false, debug},
  207. };
  208. std::vector<ContextAttribs> glescontexts =
  209. {
  210. {3, 1, true, debug},
  211. {3, 0, true, debug},
  212. {2, 0, true, debug}
  213. };
  214. if (preferGL2)
  215. {
  216. std::swap(glcontexts[0], glcontexts[2]);
  217. std::swap(glescontexts[0], glescontexts[2]);
  218. }
  219. else if (preferGL3)
  220. {
  221. std::swap(glcontexts[0], glcontexts[1]);
  222. std::swap(glescontexts[0], glescontexts[1]);
  223. }
  224. std::vector<ContextAttribs> attribslist;
  225. if (preferGLES)
  226. {
  227. attribslist.insert(attribslist.end(), glescontexts.begin(), glescontexts.end());
  228. attribslist.insert(attribslist.end(), glcontexts.begin(), glcontexts.end());
  229. }
  230. else
  231. {
  232. attribslist.insert(attribslist.end(), glcontexts.begin(), glcontexts.end());
  233. attribslist.insert(attribslist.end(), glescontexts.begin(), glescontexts.end());
  234. }
  235. return attribslist;
  236. }
  237. bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowflags, int msaa, bool stencil, int depth)
  238. {
  239. std::vector<ContextAttribs> attribslist = getContextAttribsList();
  240. std::string windowerror;
  241. std::string contexterror;
  242. std::string glversion;
  243. // Unfortunately some OpenGL context settings are part of the internal
  244. // window state in the Windows and Linux SDL backends, so we have to
  245. // recreate the window when we want to change those settings...
  246. // Also, apparently some Intel drivers on Windows give back a Microsoft
  247. // OpenGL 1.1 software renderer context when high MSAA values are requested!
  248. const auto create = [&](ContextAttribs attribs) -> bool
  249. {
  250. if (context)
  251. {
  252. SDL_GL_DeleteContext(context);
  253. context = nullptr;
  254. }
  255. if (window)
  256. {
  257. SDL_DestroyWindow(window);
  258. SDL_FlushEvent(SDL_WINDOWEVENT);
  259. window = nullptr;
  260. }
  261. window = SDL_CreateWindow(title.c_str(), x, y, w, h, windowflags);
  262. if (!window)
  263. {
  264. windowerror = std::string(SDL_GetError());
  265. return false;
  266. }
  267. context = SDL_GL_CreateContext(window);
  268. if (!context)
  269. contexterror = std::string(SDL_GetError());
  270. // Make sure the context's version is at least what we requested.
  271. if (context && !checkGLVersion(attribs, glversion))
  272. {
  273. SDL_GL_DeleteContext(context);
  274. context = nullptr;
  275. }
  276. if (!context)
  277. {
  278. SDL_DestroyWindow(window);
  279. window = nullptr;
  280. return false;
  281. }
  282. return true;
  283. };
  284. // Try each context profile in order.
  285. for (ContextAttribs attribs : attribslist)
  286. {
  287. int curMSAA = msaa;
  288. bool curSRGB = love::graphics::isGammaCorrect();
  289. setGLFramebufferAttributes(curMSAA, curSRGB, stencil, depth);
  290. setGLContextAttributes(attribs);
  291. windowerror.clear();
  292. contexterror.clear();
  293. create(attribs);
  294. if (!window && curMSAA > 0)
  295. {
  296. // The MSAA setting could have caused the failure.
  297. setGLFramebufferAttributes(0, curSRGB, stencil, depth);
  298. if (create(attribs))
  299. curMSAA = 0;
  300. }
  301. if (!window && curSRGB)
  302. {
  303. // same with sRGB.
  304. setGLFramebufferAttributes(curMSAA, false, stencil, depth);
  305. if (create(attribs))
  306. curSRGB = false;
  307. }
  308. if (!window && curMSAA > 0 && curSRGB)
  309. {
  310. // Or both!
  311. setGLFramebufferAttributes(0, false, stencil, depth);
  312. if (create(attribs))
  313. {
  314. curMSAA = 0;
  315. curSRGB = false;
  316. }
  317. }
  318. if (window && context)
  319. {
  320. // Store the successful context attributes so we can re-use them in
  321. // subsequent calls to createWindowAndContext.
  322. contextAttribs = attribs;
  323. love::graphics::setGammaCorrect(curSRGB);
  324. break;
  325. }
  326. }
  327. if (!context || !window)
  328. {
  329. std::string title = "Unable to create OpenGL window";
  330. std::string message = "This program requires a graphics card and video drivers which support OpenGL 2.1 or OpenGL ES 2.";
  331. if (!glversion.empty())
  332. message += "\n\nDetected OpenGL version:\n" + glversion;
  333. else if (!contexterror.empty())
  334. message += "\n\nOpenGL context creation error: " + contexterror;
  335. else if (!windowerror.empty())
  336. message += "\n\nSDL window creation error: " + windowerror;
  337. std::cerr << title << std::endl << message << std::endl;
  338. // Display a message box with the error, but only once.
  339. if (!displayedWindowError)
  340. {
  341. showMessageBox(title, message, MESSAGEBOX_ERROR, false);
  342. displayedWindowError = true;
  343. }
  344. close();
  345. return false;
  346. }
  347. open = true;
  348. return true;
  349. }
  350. bool Window::setWindow(int width, int height, WindowSettings *settings)
  351. {
  352. if (!graphics.get())
  353. graphics.set(Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS));
  354. if (graphics.get() && graphics->isCanvasActive())
  355. throw love::Exception("love.window.setMode cannot be called while a Canvas is active in love.graphics.");
  356. WindowSettings f;
  357. if (settings)
  358. f = *settings;
  359. f.minwidth = std::max(f.minwidth, 1);
  360. f.minheight = std::max(f.minheight, 1);
  361. f.display = std::min(std::max(f.display, 0), getDisplayCount() - 1);
  362. // Use the desktop resolution if a width or height of 0 is specified.
  363. if (width == 0 || height == 0)
  364. {
  365. SDL_DisplayMode mode = {};
  366. SDL_GetDesktopDisplayMode(f.display, &mode);
  367. width = mode.w;
  368. height = mode.h;
  369. }
  370. Uint32 sdlflags = SDL_WINDOW_OPENGL;
  371. // On Android we always must have fullscreen type FULLSCREEN_TYPE_DESKTOP
  372. #ifdef LOVE_ANDROID
  373. f.fstype = FULLSCREEN_DESKTOP;
  374. #endif
  375. if (f.fullscreen)
  376. {
  377. if (f.fstype == FULLSCREEN_DESKTOP)
  378. sdlflags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
  379. else
  380. {
  381. sdlflags |= SDL_WINDOW_FULLSCREEN;
  382. SDL_DisplayMode mode = {0, width, height, 0, nullptr};
  383. // Fullscreen window creation will bug out if no mode can be used.
  384. if (SDL_GetClosestDisplayMode(f.display, &mode, &mode) == nullptr)
  385. {
  386. // GetClosestDisplayMode will fail if we request a size larger
  387. // than the largest available display mode, so we'll try to use
  388. // the largest (first) mode in that case.
  389. if (SDL_GetDisplayMode(f.display, 0, &mode) < 0)
  390. return false;
  391. }
  392. width = mode.w;
  393. height = mode.h;
  394. }
  395. }
  396. if (f.resizable)
  397. sdlflags |= SDL_WINDOW_RESIZABLE;
  398. if (f.borderless)
  399. sdlflags |= SDL_WINDOW_BORDERLESS;
  400. if (f.highdpi)
  401. sdlflags |= SDL_WINDOW_ALLOW_HIGHDPI;
  402. int x = f.x;
  403. int y = f.y;
  404. if (f.useposition && !f.fullscreen)
  405. {
  406. // The position needs to be in the global coordinate space.
  407. SDL_Rect displaybounds = {};
  408. SDL_GetDisplayBounds(f.display, &displaybounds);
  409. x += displaybounds.x;
  410. y += displaybounds.y;
  411. }
  412. else
  413. {
  414. if (f.centered)
  415. x = y = SDL_WINDOWPOS_CENTERED_DISPLAY(f.display);
  416. else
  417. x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(f.display);
  418. }
  419. close();
  420. if (!createWindowAndContext(x, y, width, height, sdlflags, f.msaa, f.stencil, f.depth))
  421. return false;
  422. // Make sure the window keeps any previously set icon.
  423. setIcon(icon.get());
  424. // Make sure the mouse keeps its previous grab setting.
  425. setMouseGrab(mouseGrabbed);
  426. // Enforce minimum window dimensions.
  427. SDL_SetWindowMinimumSize(window, f.minwidth, f.minheight);
  428. if ((f.useposition || f.centered) && !f.fullscreen)
  429. SDL_SetWindowPosition(window, x, y);
  430. SDL_RaiseWindow(window);
  431. setVSync(f.vsync);
  432. updateSettings(f, false);
  433. if (graphics.get())
  434. {
  435. double scaledw, scaledh;
  436. fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
  437. graphics->setMode((int) scaledw, (int) scaledh, pixelWidth, pixelHeight, f.stencil);
  438. }
  439. #ifdef LOVE_ANDROID
  440. love::android::setImmersive(f.fullscreen);
  441. #endif
  442. return true;
  443. }
  444. bool Window::onSizeChanged(int width, int height)
  445. {
  446. if (!window)
  447. return false;
  448. windowWidth = width;
  449. windowHeight = height;
  450. SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight);
  451. if (graphics.get())
  452. {
  453. double scaledw, scaledh;
  454. fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
  455. graphics->setViewportSize((int) scaledw, (int) scaledh, pixelWidth, pixelHeight);
  456. }
  457. return true;
  458. }
  459. void Window::updateSettings(const WindowSettings &newsettings, bool updateGraphicsViewport)
  460. {
  461. Uint32 wflags = SDL_GetWindowFlags(window);
  462. // Set the new display mode as the current display mode.
  463. SDL_GetWindowSize(window, &windowWidth, &windowHeight);
  464. SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight);
  465. if ((wflags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP)
  466. {
  467. settings.fullscreen = true;
  468. settings.fstype = FULLSCREEN_DESKTOP;
  469. }
  470. else if ((wflags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN)
  471. {
  472. settings.fullscreen = true;
  473. settings.fstype = FULLSCREEN_EXCLUSIVE;
  474. }
  475. else
  476. {
  477. settings.fullscreen = false;
  478. settings.fstype = newsettings.fstype;
  479. }
  480. #ifdef LOVE_ANDROID
  481. settings.fullscreen = love::android::getImmersive();
  482. #endif
  483. // SDL_GetWindowMinimumSize gives back 0,0 sometimes...
  484. settings.minwidth = newsettings.minwidth;
  485. settings.minheight = newsettings.minheight;
  486. settings.resizable = (wflags & SDL_WINDOW_RESIZABLE) != 0;
  487. settings.borderless = (wflags & SDL_WINDOW_BORDERLESS) != 0;
  488. settings.centered = newsettings.centered;
  489. getPosition(settings.x, settings.y, settings.display);
  490. settings.highdpi = (wflags & SDL_WINDOW_ALLOW_HIGHDPI) != 0;
  491. settings.usedpiscale = newsettings.usedpiscale;
  492. // Only minimize on focus loss if the window is in exclusive-fullscreen mode
  493. if (settings.fullscreen && settings.fstype == FULLSCREEN_EXCLUSIVE)
  494. SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "1");
  495. else
  496. SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
  497. // Verify MSAA setting.
  498. int buffers = 0;
  499. int samples = 0;
  500. SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &buffers);
  501. SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &samples);
  502. settings.msaa = (buffers > 0 ? samples : 0);
  503. settings.vsync = getVSync();
  504. settings.stencil = newsettings.stencil;
  505. settings.depth = newsettings.depth;
  506. SDL_DisplayMode dmode = {};
  507. SDL_GetCurrentDisplayMode(settings.display, &dmode);
  508. // May be 0 if the refresh rate can't be determined.
  509. settings.refreshrate = (double) dmode.refresh_rate;
  510. // Update the viewport size now instead of waiting for event polling.
  511. if (updateGraphicsViewport && graphics.get())
  512. {
  513. double scaledw, scaledh;
  514. fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
  515. graphics->setViewportSize((int) scaledw, (int) scaledh, pixelWidth, pixelHeight);
  516. }
  517. }
  518. void Window::getWindow(int &width, int &height, WindowSettings &newsettings)
  519. {
  520. // The window might have been modified (moved, resized, etc.) by the user.
  521. if (window)
  522. updateSettings(settings, true);
  523. width = windowWidth;
  524. height = windowHeight;
  525. newsettings = settings;
  526. }
  527. void Window::close()
  528. {
  529. close(true);
  530. }
  531. void Window::close(bool allowExceptions)
  532. {
  533. if (graphics.get())
  534. {
  535. if (allowExceptions && graphics->isCanvasActive())
  536. throw love::Exception("love.window.close cannot be called while a Canvas is active in love.graphics.");
  537. graphics->unSetMode();
  538. }
  539. if (context)
  540. {
  541. SDL_GL_DeleteContext(context);
  542. context = nullptr;
  543. }
  544. if (window)
  545. {
  546. SDL_DestroyWindow(window);
  547. window = nullptr;
  548. // The old window may have generated pending events which are no longer
  549. // relevant. Destroy them all!
  550. SDL_FlushEvent(SDL_WINDOWEVENT);
  551. }
  552. open = false;
  553. }
  554. bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype)
  555. {
  556. if (!window)
  557. return false;
  558. if (graphics.get() && graphics->isCanvasActive())
  559. throw love::Exception("love.window.setFullscreen cannot be called while a Canvas is active in love.graphics.");
  560. WindowSettings newsettings = settings;
  561. newsettings.fullscreen = fullscreen;
  562. newsettings.fstype = fstype;
  563. Uint32 sdlflags = 0;
  564. if (fullscreen)
  565. {
  566. if (fstype == FULLSCREEN_DESKTOP)
  567. sdlflags = SDL_WINDOW_FULLSCREEN_DESKTOP;
  568. else
  569. {
  570. sdlflags = SDL_WINDOW_FULLSCREEN;
  571. SDL_DisplayMode mode = {};
  572. mode.w = windowWidth;
  573. mode.h = windowHeight;
  574. SDL_GetClosestDisplayMode(SDL_GetWindowDisplayIndex(window), &mode, &mode);
  575. SDL_SetWindowDisplayMode(window, &mode);
  576. }
  577. }
  578. #ifdef LOVE_ANDROID
  579. love::android::setImmersive(fullscreen);
  580. #endif
  581. if (SDL_SetWindowFullscreen(window, sdlflags) == 0)
  582. {
  583. SDL_GL_MakeCurrent(window, context);
  584. updateSettings(newsettings, true);
  585. // Apparently this gets un-set when we exit fullscreen (at least in OS X).
  586. if (!fullscreen)
  587. SDL_SetWindowMinimumSize(window, settings.minwidth, settings.minheight);
  588. return true;
  589. }
  590. return false;
  591. }
  592. bool Window::setFullscreen(bool fullscreen)
  593. {
  594. return setFullscreen(fullscreen, settings.fstype);
  595. }
  596. int Window::getDisplayCount() const
  597. {
  598. return SDL_GetNumVideoDisplays();
  599. }
  600. const char *Window::getDisplayName(int displayindex) const
  601. {
  602. const char *name = SDL_GetDisplayName(displayindex);
  603. if (name == nullptr)
  604. throw love::Exception("Invalid display index: %d", displayindex + 1);
  605. return name;
  606. }
  607. Window::DisplayOrientation Window::getDisplayOrientation(int displayindex) const
  608. {
  609. // TODO: We can expose this everywhere, we just need to watch out for the
  610. // SDL binary being older than the headers on Linux.
  611. #if SDL_VERSION_ATLEAST(2, 0, 9) && (defined(LOVE_ANDROID) || !defined(LOVE_LINUX))
  612. switch (SDL_GetDisplayOrientation(displayindex))
  613. {
  614. case SDL_ORIENTATION_UNKNOWN: return ORIENTATION_UNKNOWN;
  615. case SDL_ORIENTATION_LANDSCAPE: return ORIENTATION_LANDSCAPE;
  616. case SDL_ORIENTATION_LANDSCAPE_FLIPPED: return ORIENTATION_LANDSCAPE_FLIPPED;
  617. case SDL_ORIENTATION_PORTRAIT: return ORIENTATION_PORTRAIT;
  618. case SDL_ORIENTATION_PORTRAIT_FLIPPED: return ORIENTATION_PORTRAIT_FLIPPED;
  619. }
  620. #else
  621. LOVE_UNUSED(displayindex);
  622. #endif
  623. return ORIENTATION_UNKNOWN;
  624. }
  625. std::vector<Window::WindowSize> Window::getFullscreenSizes(int displayindex) const
  626. {
  627. std::vector<WindowSize> sizes;
  628. for (int i = 0; i < SDL_GetNumDisplayModes(displayindex); i++)
  629. {
  630. SDL_DisplayMode mode = {};
  631. SDL_GetDisplayMode(displayindex, i, &mode);
  632. WindowSize w = {mode.w, mode.h};
  633. // SDL2's display mode list has multiple entries for modes of the same
  634. // size with different bits per pixel, so we need to filter those out.
  635. if (std::find(sizes.begin(), sizes.end(), w) == sizes.end())
  636. sizes.push_back(w);
  637. }
  638. return sizes;
  639. }
  640. void Window::getDesktopDimensions(int displayindex, int &width, int &height) const
  641. {
  642. if (displayindex >= 0 && displayindex < getDisplayCount())
  643. {
  644. SDL_DisplayMode mode = {};
  645. SDL_GetDesktopDisplayMode(displayindex, &mode);
  646. width = mode.w;
  647. height = mode.h;
  648. }
  649. else
  650. {
  651. width = 0;
  652. height = 0;
  653. }
  654. }
  655. void Window::setPosition(int x, int y, int displayindex)
  656. {
  657. if (!window)
  658. return;
  659. displayindex = std::min(std::max(displayindex, 0), getDisplayCount() - 1);
  660. SDL_Rect displaybounds = {};
  661. SDL_GetDisplayBounds(displayindex, &displaybounds);
  662. // The position needs to be in the global coordinate space.
  663. x += displaybounds.x;
  664. y += displaybounds.y;
  665. SDL_SetWindowPosition(window, x, y);
  666. settings.useposition = true;
  667. }
  668. void Window::getPosition(int &x, int &y, int &displayindex)
  669. {
  670. if (!window)
  671. {
  672. x = y = 0;
  673. displayindex = 0;
  674. return;
  675. }
  676. displayindex = std::max(SDL_GetWindowDisplayIndex(window), 0);
  677. SDL_GetWindowPosition(window, &x, &y);
  678. // In SDL <= 2.0.3, fullscreen windows are always reported as 0,0. In every
  679. // other case we need to convert the position from global coordinates to the
  680. // monitor's coordinate space.
  681. if (x != 0 || y != 0)
  682. {
  683. SDL_Rect displaybounds = {};
  684. SDL_GetDisplayBounds(displayindex, &displaybounds);
  685. x -= displaybounds.x;
  686. y -= displaybounds.y;
  687. }
  688. }
  689. Rect Window::getSafeArea() const
  690. {
  691. #if defined(LOVE_IOS)
  692. if (window != nullptr)
  693. return love::ios::getSafeArea(window);
  694. #elif defined(LOVE_ANDROID)
  695. if (window != nullptr)
  696. {
  697. int top, left, bottom, right;
  698. if (love::android::getSafeArea(top, left, bottom, right))
  699. {
  700. // DisplayCutout API returns safe area in pixels
  701. // and is affected by display orientation.
  702. double safeLeft, safeTop, safeWidth, safeHeight;
  703. fromPixels(left, top, safeLeft, safeTop);
  704. fromPixels(pixelWidth - left - right, pixelHeight - top - bottom, safeWidth, safeHeight);
  705. return {(int) safeLeft, (int) safeTop, (int) safeWidth, (int) safeHeight};
  706. }
  707. }
  708. #endif
  709. double dw, dh;
  710. fromPixels(pixelWidth, pixelHeight, dw, dh);
  711. return {0, 0, (int) dw, (int) dh};
  712. }
  713. bool Window::isOpen() const
  714. {
  715. return open;
  716. }
  717. void Window::setWindowTitle(const std::string &title)
  718. {
  719. this->title = title;
  720. if (window)
  721. SDL_SetWindowTitle(window, title.c_str());
  722. }
  723. const std::string &Window::getWindowTitle() const
  724. {
  725. return title;
  726. }
  727. bool Window::setIcon(love::image::ImageData *imgd)
  728. {
  729. if (!imgd)
  730. return false;
  731. if (imgd->getFormat() != PIXELFORMAT_RGBA8)
  732. throw love::Exception("setIcon only accepts 32-bit RGBA images.");
  733. icon.set(imgd);
  734. if (!window)
  735. return false;
  736. Uint32 rmask, gmask, bmask, amask;
  737. #ifdef LOVE_BIG_ENDIAN
  738. rmask = 0xFF000000;
  739. gmask = 0x00FF0000;
  740. bmask = 0x0000FF00;
  741. amask = 0x000000FF;
  742. #else
  743. rmask = 0x000000FF;
  744. gmask = 0x0000FF00;
  745. bmask = 0x00FF0000;
  746. amask = 0xFF000000;
  747. #endif
  748. int w = imgd->getWidth();
  749. int h = imgd->getHeight();
  750. int bytesperpixel = (int) getPixelFormatSize(imgd->getFormat());
  751. int pitch = w * bytesperpixel;
  752. SDL_Surface *sdlicon = nullptr;
  753. {
  754. // We don't want another thread modifying the ImageData mid-copy.
  755. love::thread::Lock lock(imgd->getMutex());
  756. sdlicon = SDL_CreateRGBSurfaceFrom(imgd->getData(), w, h, bytesperpixel * 8, pitch, rmask, gmask, bmask, amask);
  757. }
  758. if (!sdlicon)
  759. return false;
  760. SDL_SetWindowIcon(window, sdlicon);
  761. SDL_FreeSurface(sdlicon);
  762. return true;
  763. }
  764. love::image::ImageData *Window::getIcon()
  765. {
  766. return icon.get();
  767. }
  768. void Window::setVSync(int vsync)
  769. {
  770. if (context == nullptr)
  771. return;
  772. SDL_GL_SetSwapInterval(vsync);
  773. // Check if adaptive vsync was requested but not supported, and fall back
  774. // to regular vsync if so.
  775. if (vsync == -1 && SDL_GL_GetSwapInterval() != -1)
  776. SDL_GL_SetSwapInterval(1);
  777. }
  778. int Window::getVSync() const
  779. {
  780. return context != nullptr ? SDL_GL_GetSwapInterval() : 0;
  781. }
  782. void Window::setDisplaySleepEnabled(bool enable)
  783. {
  784. if (enable)
  785. SDL_EnableScreenSaver();
  786. else
  787. SDL_DisableScreenSaver();
  788. }
  789. bool Window::isDisplaySleepEnabled() const
  790. {
  791. return SDL_IsScreenSaverEnabled() != SDL_FALSE;
  792. }
  793. void Window::minimize()
  794. {
  795. if (window != nullptr)
  796. SDL_MinimizeWindow(window);
  797. }
  798. void Window::maximize()
  799. {
  800. if (window != nullptr)
  801. {
  802. SDL_MaximizeWindow(window);
  803. updateSettings(settings, true);
  804. }
  805. }
  806. void Window::restore()
  807. {
  808. if (window != nullptr)
  809. {
  810. SDL_RestoreWindow(window);
  811. updateSettings(settings, true);
  812. }
  813. }
  814. bool Window::isMaximized() const
  815. {
  816. return window != nullptr && (SDL_GetWindowFlags(window) & SDL_WINDOW_MAXIMIZED);
  817. }
  818. bool Window::isMinimized() const
  819. {
  820. return window != nullptr && (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED);
  821. }
  822. void Window::swapBuffers()
  823. {
  824. SDL_GL_SwapWindow(window);
  825. }
  826. bool Window::hasFocus() const
  827. {
  828. return (window && SDL_GetKeyboardFocus() == window);
  829. }
  830. bool Window::hasMouseFocus() const
  831. {
  832. return (window && SDL_GetMouseFocus() == window);
  833. }
  834. bool Window::isVisible() const
  835. {
  836. return window && (SDL_GetWindowFlags(window) & SDL_WINDOW_SHOWN) != 0;
  837. }
  838. void Window::setMouseGrab(bool grab)
  839. {
  840. mouseGrabbed = grab;
  841. if (window)
  842. SDL_SetWindowGrab(window, (SDL_bool) grab);
  843. }
  844. bool Window::isMouseGrabbed() const
  845. {
  846. if (window)
  847. return SDL_GetWindowGrab(window) != SDL_FALSE;
  848. else
  849. return mouseGrabbed;
  850. }
  851. int Window::getWidth() const
  852. {
  853. return windowWidth;
  854. }
  855. int Window::getHeight() const
  856. {
  857. return windowHeight;
  858. }
  859. int Window::getPixelWidth() const
  860. {
  861. return pixelWidth;
  862. }
  863. int Window::getPixelHeight() const
  864. {
  865. return pixelHeight;
  866. }
  867. void Window::windowToPixelCoords(double *x, double *y) const
  868. {
  869. if (x != nullptr)
  870. *x = (*x) * ((double) pixelWidth / (double) windowWidth);
  871. if (y != nullptr)
  872. *y = (*y) * ((double) pixelHeight / (double) windowHeight);
  873. }
  874. void Window::pixelToWindowCoords(double *x, double *y) const
  875. {
  876. if (x != nullptr)
  877. *x = (*x) * ((double) windowWidth / (double) pixelWidth);
  878. if (y != nullptr)
  879. *y = (*y) * ((double) windowHeight / (double) pixelHeight);
  880. }
  881. void Window::windowToDPICoords(double *x, double *y) const
  882. {
  883. double px = x != nullptr ? *x : 0.0;
  884. double py = y != nullptr ? *y : 0.0;
  885. windowToPixelCoords(&px, &py);
  886. double dpix = 0.0;
  887. double dpiy = 0.0;
  888. fromPixels(px, py, dpix, dpiy);
  889. if (x != nullptr)
  890. *x = dpix;
  891. if (y != nullptr)
  892. *y = dpiy;
  893. }
  894. void Window::DPIToWindowCoords(double *x, double *y) const
  895. {
  896. double dpix = x != nullptr ? *x : 0.0;
  897. double dpiy = y != nullptr ? *y : 0.0;
  898. double px = 0.0;
  899. double py = 0.0;
  900. toPixels(dpix, dpiy, px, py);
  901. pixelToWindowCoords(&px, &py);
  902. if (x != nullptr)
  903. *x = px;
  904. if (y != nullptr)
  905. *y = py;
  906. }
  907. double Window::getDPIScale() const
  908. {
  909. return settings.usedpiscale ? getNativeDPIScale() : 1.0;
  910. }
  911. double Window::getNativeDPIScale() const
  912. {
  913. #ifdef LOVE_ANDROID
  914. return love::android::getScreenScale();
  915. #else
  916. return (double) pixelHeight / (double) windowHeight;
  917. #endif
  918. }
  919. double Window::toPixels(double x) const
  920. {
  921. return x * getDPIScale();
  922. }
  923. void Window::toPixels(double wx, double wy, double &px, double &py) const
  924. {
  925. double scale = getDPIScale();
  926. px = wx * scale;
  927. py = wy * scale;
  928. }
  929. double Window::fromPixels(double x) const
  930. {
  931. return x / getDPIScale();
  932. }
  933. void Window::fromPixels(double px, double py, double &wx, double &wy) const
  934. {
  935. double scale = getDPIScale();
  936. wx = px / scale;
  937. wy = py / scale;
  938. }
  939. const void *Window::getHandle() const
  940. {
  941. return window;
  942. }
  943. SDL_MessageBoxFlags Window::convertMessageBoxType(MessageBoxType type) const
  944. {
  945. switch (type)
  946. {
  947. case MESSAGEBOX_ERROR:
  948. return SDL_MESSAGEBOX_ERROR;
  949. case MESSAGEBOX_WARNING:
  950. return SDL_MESSAGEBOX_WARNING;
  951. case MESSAGEBOX_INFO:
  952. default:
  953. return SDL_MESSAGEBOX_INFORMATION;
  954. }
  955. }
  956. bool Window::showMessageBox(const std::string &title, const std::string &message, MessageBoxType type, bool attachtowindow)
  957. {
  958. SDL_MessageBoxFlags flags = convertMessageBoxType(type);
  959. SDL_Window *sdlwindow = attachtowindow ? window : nullptr;
  960. return SDL_ShowSimpleMessageBox(flags, title.c_str(), message.c_str(), sdlwindow) >= 0;
  961. }
  962. int Window::showMessageBox(const MessageBoxData &data)
  963. {
  964. SDL_MessageBoxData sdldata = {};
  965. sdldata.flags = convertMessageBoxType(data.type);
  966. sdldata.title = data.title.c_str();
  967. sdldata.message = data.message.c_str();
  968. sdldata.window = data.attachToWindow ? window : nullptr;
  969. sdldata.numbuttons = (int) data.buttons.size();
  970. std::vector<SDL_MessageBoxButtonData> sdlbuttons;
  971. for (int i = 0; i < (int) data.buttons.size(); i++)
  972. {
  973. SDL_MessageBoxButtonData sdlbutton = {};
  974. sdlbutton.buttonid = i;
  975. sdlbutton.text = data.buttons[i].c_str();
  976. if (i == data.enterButtonIndex)
  977. sdlbutton.flags |= SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
  978. if (i == data.escapeButtonIndex)
  979. sdlbutton.flags |= SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;
  980. sdlbuttons.push_back(sdlbutton);
  981. }
  982. sdldata.buttons = &sdlbuttons[0];
  983. int pressedbutton = -2;
  984. SDL_ShowMessageBox(&sdldata, &pressedbutton);
  985. return pressedbutton;
  986. }
  987. void Window::requestAttention(bool continuous)
  988. {
  989. #if defined(LOVE_WINDOWS) && !defined(LOVE_WINDOWS_UWP)
  990. if (hasFocus())
  991. return;
  992. SDL_SysWMinfo wminfo = {};
  993. SDL_VERSION(&wminfo.version);
  994. if (SDL_GetWindowWMInfo(window, &wminfo))
  995. {
  996. FLASHWINFO flashinfo = {};
  997. flashinfo.cbSize = sizeof(FLASHWINFO);
  998. flashinfo.hwnd = wminfo.info.win.window;
  999. flashinfo.uCount = 1;
  1000. flashinfo.dwFlags = FLASHW_ALL;
  1001. if (continuous)
  1002. {
  1003. flashinfo.uCount = 0;
  1004. flashinfo.dwFlags |= FLASHW_TIMERNOFG;
  1005. }
  1006. FlashWindowEx(&flashinfo);
  1007. }
  1008. #elif defined(LOVE_MACOSX)
  1009. love::macosx::requestAttention(continuous);
  1010. #else
  1011. LOVE_UNUSED(continuous);
  1012. #endif
  1013. // TODO: Linux?
  1014. }
  1015. const char *Window::getName() const
  1016. {
  1017. return "love.window.sdl";
  1018. }
  1019. } // sdl
  1020. } // window
  1021. } // love