Window.cpp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266
  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. // FIXME: This doesn't account for windowing backends that sometimes
  185. // use 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. std::vector<ContextAttribs> glcontexts = {{2, 1, false, debug}};
  201. glcontexts.insert(preferGL2 ? glcontexts.end() : glcontexts.begin(), {3, 3, false, debug});
  202. std::vector<ContextAttribs> glescontexts = {{2, 0, true, debug}};
  203. // While UWP SDL is above 2.0.4, it still doesn't support OpenGL ES 3+
  204. #ifndef LOVE_WINDOWS_UWP
  205. // OpenGL ES 3+ contexts are only properly supported in SDL 2.0.4+.
  206. if (!hasSDL203orEarlier)
  207. glescontexts.insert(preferGL2 ? glescontexts.end() : glescontexts.begin(), {3, 0, true, debug});
  208. #endif
  209. std::vector<ContextAttribs> attribslist;
  210. if (preferGLES)
  211. {
  212. attribslist.insert(attribslist.end(), glescontexts.begin(), glescontexts.end());
  213. attribslist.insert(attribslist.end(), glcontexts.begin(), glcontexts.end());
  214. }
  215. else
  216. {
  217. attribslist.insert(attribslist.end(), glcontexts.begin(), glcontexts.end());
  218. attribslist.insert(attribslist.end(), glescontexts.begin(), glescontexts.end());
  219. }
  220. return attribslist;
  221. }
  222. bool Window::createWindowAndContext(int x, int y, int w, int h, Uint32 windowflags, int msaa, bool stencil, int depth)
  223. {
  224. std::vector<ContextAttribs> attribslist = getContextAttribsList();
  225. std::string windowerror;
  226. std::string contexterror;
  227. std::string glversion;
  228. // Unfortunately some OpenGL context settings are part of the internal
  229. // window state in the Windows and Linux SDL backends, so we have to
  230. // recreate the window when we want to change those settings...
  231. // Also, apparently some Intel drivers on Windows give back a Microsoft
  232. // OpenGL 1.1 software renderer context when high MSAA values are requested!
  233. const auto create = [&](ContextAttribs attribs) -> bool
  234. {
  235. if (context)
  236. {
  237. SDL_GL_DeleteContext(context);
  238. context = nullptr;
  239. }
  240. if (window)
  241. {
  242. SDL_DestroyWindow(window);
  243. SDL_FlushEvent(SDL_WINDOWEVENT);
  244. window = nullptr;
  245. }
  246. window = SDL_CreateWindow(title.c_str(), x, y, w, h, windowflags);
  247. if (!window)
  248. {
  249. windowerror = std::string(SDL_GetError());
  250. return false;
  251. }
  252. context = SDL_GL_CreateContext(window);
  253. if (!context)
  254. contexterror = std::string(SDL_GetError());
  255. // Make sure the context's version is at least what we requested.
  256. if (context && !checkGLVersion(attribs, glversion))
  257. {
  258. SDL_GL_DeleteContext(context);
  259. context = nullptr;
  260. }
  261. if (!context)
  262. {
  263. SDL_DestroyWindow(window);
  264. window = nullptr;
  265. return false;
  266. }
  267. return true;
  268. };
  269. // Try each context profile in order.
  270. for (ContextAttribs attribs : attribslist)
  271. {
  272. int curMSAA = msaa;
  273. bool curSRGB = love::graphics::isGammaCorrect();
  274. setGLFramebufferAttributes(curMSAA, curSRGB, stencil, depth);
  275. setGLContextAttributes(attribs);
  276. windowerror.clear();
  277. contexterror.clear();
  278. create(attribs);
  279. if (!window && curMSAA > 0)
  280. {
  281. // The MSAA setting could have caused the failure.
  282. setGLFramebufferAttributes(0, curSRGB, stencil, depth);
  283. if (create(attribs))
  284. curMSAA = 0;
  285. }
  286. if (!window && curSRGB)
  287. {
  288. // same with sRGB.
  289. setGLFramebufferAttributes(curMSAA, false, stencil, depth);
  290. if (create(attribs))
  291. curSRGB = false;
  292. }
  293. if (!window && curMSAA > 0 && curSRGB)
  294. {
  295. // Or both!
  296. setGLFramebufferAttributes(0, false, stencil, depth);
  297. if (create(attribs))
  298. {
  299. curMSAA = 0;
  300. curSRGB = false;
  301. }
  302. }
  303. if (window && context)
  304. {
  305. // Store the successful context attributes so we can re-use them in
  306. // subsequent calls to createWindowAndContext.
  307. contextAttribs = attribs;
  308. love::graphics::setGammaCorrect(curSRGB);
  309. break;
  310. }
  311. }
  312. if (!context || !window)
  313. {
  314. std::string title = "Unable to create OpenGL window";
  315. std::string message = "This program requires a graphics card and video drivers which support OpenGL 2.1 or OpenGL ES 2.";
  316. if (!glversion.empty())
  317. message += "\n\nDetected OpenGL version:\n" + glversion;
  318. else if (!contexterror.empty())
  319. message += "\n\nOpenGL context creation error: " + contexterror;
  320. else if (!windowerror.empty())
  321. message += "\n\nSDL window creation error: " + windowerror;
  322. std::cerr << title << std::endl << message << std::endl;
  323. // Display a message box with the error, but only once.
  324. if (!displayedWindowError)
  325. {
  326. showMessageBox(title, message, MESSAGEBOX_ERROR, false);
  327. displayedWindowError = true;
  328. }
  329. close();
  330. return false;
  331. }
  332. open = true;
  333. return true;
  334. }
  335. bool Window::setWindow(int width, int height, WindowSettings *settings)
  336. {
  337. if (!graphics.get())
  338. graphics.set(Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS));
  339. if (graphics.get() && graphics->isCanvasActive())
  340. throw love::Exception("love.window.setMode cannot be called while a Canvas is active in love.graphics.");
  341. WindowSettings f;
  342. if (settings)
  343. f = *settings;
  344. f.minwidth = std::max(f.minwidth, 1);
  345. f.minheight = std::max(f.minheight, 1);
  346. f.display = std::min(std::max(f.display, 0), getDisplayCount() - 1);
  347. // Use the desktop resolution if a width or height of 0 is specified.
  348. if (width == 0 || height == 0)
  349. {
  350. SDL_DisplayMode mode = {};
  351. SDL_GetDesktopDisplayMode(f.display, &mode);
  352. width = mode.w;
  353. height = mode.h;
  354. }
  355. Uint32 sdlflags = SDL_WINDOW_OPENGL;
  356. // On Android we always must have fullscreen type FULLSCREEN_TYPE_DESKTOP
  357. #ifdef LOVE_ANDROID
  358. f.fstype = FULLSCREEN_DESKTOP;
  359. #endif
  360. if (f.fullscreen)
  361. {
  362. if (f.fstype == FULLSCREEN_DESKTOP)
  363. sdlflags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
  364. else
  365. {
  366. sdlflags |= SDL_WINDOW_FULLSCREEN;
  367. SDL_DisplayMode mode = {0, width, height, 0, nullptr};
  368. // Fullscreen window creation will bug out if no mode can be used.
  369. if (SDL_GetClosestDisplayMode(f.display, &mode, &mode) == nullptr)
  370. {
  371. // GetClosestDisplayMode will fail if we request a size larger
  372. // than the largest available display mode, so we'll try to use
  373. // the largest (first) mode in that case.
  374. if (SDL_GetDisplayMode(f.display, 0, &mode) < 0)
  375. return false;
  376. }
  377. width = mode.w;
  378. height = mode.h;
  379. }
  380. }
  381. if (f.resizable)
  382. sdlflags |= SDL_WINDOW_RESIZABLE;
  383. if (f.borderless)
  384. sdlflags |= SDL_WINDOW_BORDERLESS;
  385. if (f.highdpi)
  386. sdlflags |= SDL_WINDOW_ALLOW_HIGHDPI;
  387. int x = f.x;
  388. int y = f.y;
  389. if (f.useposition && !f.fullscreen)
  390. {
  391. // The position needs to be in the global coordinate space.
  392. SDL_Rect displaybounds = {};
  393. SDL_GetDisplayBounds(f.display, &displaybounds);
  394. x += displaybounds.x;
  395. y += displaybounds.y;
  396. }
  397. else
  398. {
  399. if (f.centered)
  400. x = y = SDL_WINDOWPOS_CENTERED_DISPLAY(f.display);
  401. else
  402. x = y = SDL_WINDOWPOS_UNDEFINED_DISPLAY(f.display);
  403. }
  404. close();
  405. if (!createWindowAndContext(x, y, width, height, sdlflags, f.msaa, f.stencil, f.depth))
  406. return false;
  407. // Make sure the window keeps any previously set icon.
  408. setIcon(icon.get());
  409. // Make sure the mouse keeps its previous grab setting.
  410. setMouseGrab(mouseGrabbed);
  411. // Enforce minimum window dimensions.
  412. SDL_SetWindowMinimumSize(window, f.minwidth, f.minheight);
  413. if ((f.useposition || f.centered) && !f.fullscreen)
  414. SDL_SetWindowPosition(window, x, y);
  415. SDL_RaiseWindow(window);
  416. setVSync(f.vsync);
  417. updateSettings(f, false);
  418. if (graphics.get())
  419. {
  420. double scaledw, scaledh;
  421. fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
  422. graphics->setMode((int) scaledw, (int) scaledh, pixelWidth, pixelHeight, f.stencil);
  423. }
  424. #ifdef LOVE_ANDROID
  425. love::android::setImmersive(f.fullscreen);
  426. #endif
  427. return true;
  428. }
  429. bool Window::onSizeChanged(int width, int height)
  430. {
  431. if (!window)
  432. return false;
  433. windowWidth = width;
  434. windowHeight = height;
  435. SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight);
  436. if (graphics.get())
  437. {
  438. double scaledw, scaledh;
  439. fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
  440. graphics->setViewportSize((int) scaledw, (int) scaledh, pixelWidth, pixelHeight);
  441. }
  442. return true;
  443. }
  444. void Window::updateSettings(const WindowSettings &newsettings, bool updateGraphicsViewport)
  445. {
  446. Uint32 wflags = SDL_GetWindowFlags(window);
  447. // Set the new display mode as the current display mode.
  448. SDL_GetWindowSize(window, &windowWidth, &windowHeight);
  449. SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight);
  450. if ((wflags & SDL_WINDOW_FULLSCREEN_DESKTOP) == SDL_WINDOW_FULLSCREEN_DESKTOP)
  451. {
  452. settings.fullscreen = true;
  453. settings.fstype = FULLSCREEN_DESKTOP;
  454. }
  455. else if ((wflags & SDL_WINDOW_FULLSCREEN) == SDL_WINDOW_FULLSCREEN)
  456. {
  457. settings.fullscreen = true;
  458. settings.fstype = FULLSCREEN_EXCLUSIVE;
  459. }
  460. else
  461. {
  462. settings.fullscreen = false;
  463. settings.fstype = newsettings.fstype;
  464. }
  465. #ifdef LOVE_ANDROID
  466. settings.fullscreen = love::android::getImmersive();
  467. #endif
  468. // SDL_GetWindowMinimumSize gives back 0,0 sometimes...
  469. settings.minwidth = newsettings.minwidth;
  470. settings.minheight = newsettings.minheight;
  471. settings.resizable = (wflags & SDL_WINDOW_RESIZABLE) != 0;
  472. settings.borderless = (wflags & SDL_WINDOW_BORDERLESS) != 0;
  473. settings.centered = newsettings.centered;
  474. getPosition(settings.x, settings.y, settings.display);
  475. settings.highdpi = (wflags & SDL_WINDOW_ALLOW_HIGHDPI) != 0;
  476. settings.usedpiscale = newsettings.usedpiscale;
  477. // Only minimize on focus loss if the window is in exclusive-fullscreen mode
  478. if (settings.fullscreen && settings.fstype == FULLSCREEN_EXCLUSIVE)
  479. SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "1");
  480. else
  481. SDL_SetHint(SDL_HINT_VIDEO_MINIMIZE_ON_FOCUS_LOSS, "0");
  482. // Verify MSAA setting.
  483. int buffers = 0;
  484. int samples = 0;
  485. SDL_GL_GetAttribute(SDL_GL_MULTISAMPLEBUFFERS, &buffers);
  486. SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &samples);
  487. settings.msaa = (buffers > 0 ? samples : 0);
  488. settings.vsync = getVSync();
  489. settings.stencil = newsettings.stencil;
  490. settings.depth = newsettings.depth;
  491. SDL_DisplayMode dmode = {};
  492. SDL_GetCurrentDisplayMode(settings.display, &dmode);
  493. // May be 0 if the refresh rate can't be determined.
  494. settings.refreshrate = (double) dmode.refresh_rate;
  495. // Update the viewport size now instead of waiting for event polling.
  496. if (updateGraphicsViewport && graphics.get())
  497. {
  498. double scaledw, scaledh;
  499. fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
  500. graphics->setViewportSize((int) scaledw, (int) scaledh, pixelWidth, pixelHeight);
  501. }
  502. }
  503. void Window::getWindow(int &width, int &height, WindowSettings &newsettings)
  504. {
  505. // The window might have been modified (moved, resized, etc.) by the user.
  506. if (window)
  507. updateSettings(settings, true);
  508. width = windowWidth;
  509. height = windowHeight;
  510. newsettings = settings;
  511. }
  512. void Window::close()
  513. {
  514. close(true);
  515. }
  516. void Window::close(bool allowExceptions)
  517. {
  518. if (graphics.get())
  519. {
  520. if (allowExceptions && graphics->isCanvasActive())
  521. throw love::Exception("love.window.close cannot be called while a Canvas is active in love.graphics.");
  522. graphics->unSetMode();
  523. }
  524. if (context)
  525. {
  526. SDL_GL_DeleteContext(context);
  527. context = nullptr;
  528. }
  529. if (window)
  530. {
  531. SDL_DestroyWindow(window);
  532. window = nullptr;
  533. // The old window may have generated pending events which are no longer
  534. // relevant. Destroy them all!
  535. SDL_FlushEvent(SDL_WINDOWEVENT);
  536. }
  537. open = false;
  538. }
  539. bool Window::setFullscreen(bool fullscreen, Window::FullscreenType fstype)
  540. {
  541. if (!window)
  542. return false;
  543. if (graphics.get() && graphics->isCanvasActive())
  544. throw love::Exception("love.window.setFullscreen cannot be called while a Canvas is active in love.graphics.");
  545. WindowSettings newsettings = settings;
  546. newsettings.fullscreen = fullscreen;
  547. newsettings.fstype = fstype;
  548. Uint32 sdlflags = 0;
  549. if (fullscreen)
  550. {
  551. if (fstype == FULLSCREEN_DESKTOP)
  552. sdlflags = SDL_WINDOW_FULLSCREEN_DESKTOP;
  553. else
  554. {
  555. sdlflags = SDL_WINDOW_FULLSCREEN;
  556. SDL_DisplayMode mode = {};
  557. mode.w = windowWidth;
  558. mode.h = windowHeight;
  559. SDL_GetClosestDisplayMode(SDL_GetWindowDisplayIndex(window), &mode, &mode);
  560. SDL_SetWindowDisplayMode(window, &mode);
  561. }
  562. }
  563. #ifdef LOVE_ANDROID
  564. love::android::setImmersive(fullscreen);
  565. #endif
  566. if (SDL_SetWindowFullscreen(window, sdlflags) == 0)
  567. {
  568. SDL_GL_MakeCurrent(window, context);
  569. updateSettings(newsettings, true);
  570. // Apparently this gets un-set when we exit fullscreen (at least in OS X).
  571. if (!fullscreen)
  572. SDL_SetWindowMinimumSize(window, settings.minwidth, settings.minheight);
  573. return true;
  574. }
  575. return false;
  576. }
  577. bool Window::setFullscreen(bool fullscreen)
  578. {
  579. return setFullscreen(fullscreen, settings.fstype);
  580. }
  581. int Window::getDisplayCount() const
  582. {
  583. return SDL_GetNumVideoDisplays();
  584. }
  585. const char *Window::getDisplayName(int displayindex) const
  586. {
  587. const char *name = SDL_GetDisplayName(displayindex);
  588. if (name == nullptr)
  589. throw love::Exception("Invalid display index: %d", displayindex + 1);
  590. return name;
  591. }
  592. Window::DisplayOrientation Window::getDisplayOrientation(int displayindex) const
  593. {
  594. // TODO: We can expose this everywhere, we just need to watch out for the
  595. // SDL binary being older than the headers on Linux.
  596. #if SDL_VERSION_ATLEAST(2, 0, 9) && (defined(LOVE_ANDROID) || !defined(LOVE_LINUX))
  597. switch (SDL_GetDisplayOrientation(displayindex))
  598. {
  599. case SDL_ORIENTATION_UNKNOWN: return ORIENTATION_UNKNOWN;
  600. case SDL_ORIENTATION_LANDSCAPE: return ORIENTATION_LANDSCAPE;
  601. case SDL_ORIENTATION_LANDSCAPE_FLIPPED: return ORIENTATION_LANDSCAPE_FLIPPED;
  602. case SDL_ORIENTATION_PORTRAIT: return ORIENTATION_PORTRAIT;
  603. case SDL_ORIENTATION_PORTRAIT_FLIPPED: return ORIENTATION_PORTRAIT_FLIPPED;
  604. }
  605. #else
  606. LOVE_UNUSED(displayindex);
  607. #endif
  608. return ORIENTATION_UNKNOWN;
  609. }
  610. std::vector<Window::WindowSize> Window::getFullscreenSizes(int displayindex) const
  611. {
  612. std::vector<WindowSize> sizes;
  613. for (int i = 0; i < SDL_GetNumDisplayModes(displayindex); i++)
  614. {
  615. SDL_DisplayMode mode = {};
  616. SDL_GetDisplayMode(displayindex, i, &mode);
  617. WindowSize w = {mode.w, mode.h};
  618. // SDL2's display mode list has multiple entries for modes of the same
  619. // size with different bits per pixel, so we need to filter those out.
  620. if (std::find(sizes.begin(), sizes.end(), w) == sizes.end())
  621. sizes.push_back(w);
  622. }
  623. return sizes;
  624. }
  625. void Window::getDesktopDimensions(int displayindex, int &width, int &height) const
  626. {
  627. if (displayindex >= 0 && displayindex < getDisplayCount())
  628. {
  629. SDL_DisplayMode mode = {};
  630. SDL_GetDesktopDisplayMode(displayindex, &mode);
  631. width = mode.w;
  632. height = mode.h;
  633. }
  634. else
  635. {
  636. width = 0;
  637. height = 0;
  638. }
  639. }
  640. void Window::setPosition(int x, int y, int displayindex)
  641. {
  642. if (!window)
  643. return;
  644. displayindex = std::min(std::max(displayindex, 0), getDisplayCount() - 1);
  645. SDL_Rect displaybounds = {};
  646. SDL_GetDisplayBounds(displayindex, &displaybounds);
  647. // The position needs to be in the global coordinate space.
  648. x += displaybounds.x;
  649. y += displaybounds.y;
  650. SDL_SetWindowPosition(window, x, y);
  651. settings.useposition = true;
  652. }
  653. void Window::getPosition(int &x, int &y, int &displayindex)
  654. {
  655. if (!window)
  656. {
  657. x = y = 0;
  658. displayindex = 0;
  659. return;
  660. }
  661. displayindex = std::max(SDL_GetWindowDisplayIndex(window), 0);
  662. SDL_GetWindowPosition(window, &x, &y);
  663. // In SDL <= 2.0.3, fullscreen windows are always reported as 0,0. In every
  664. // other case we need to convert the position from global coordinates to the
  665. // monitor's coordinate space.
  666. if (x != 0 || y != 0)
  667. {
  668. SDL_Rect displaybounds = {};
  669. SDL_GetDisplayBounds(displayindex, &displaybounds);
  670. x -= displaybounds.x;
  671. y -= displaybounds.y;
  672. }
  673. }
  674. Rect Window::getSafeArea() const
  675. {
  676. #if defined(LOVE_IOS)
  677. if (window != nullptr)
  678. return love::ios::getSafeArea(window);
  679. #elif defined(LOVE_ANDROID)
  680. if (window != nullptr)
  681. {
  682. int top, left, bottom, right;
  683. if (love::android::getSafeArea(top, left, bottom, right))
  684. {
  685. // DisplayCutout API returns safe area in pixels
  686. // and is affected by display orientation.
  687. double safeLeft, safeTop, safeWidth, safeHeight;
  688. fromPixels(left, top, safeLeft, safeTop);
  689. fromPixels(pixelWidth - left - right, pixelHeight - top - bottom, safeWidth, safeHeight);
  690. return {(int) safeLeft, (int) safeTop, (int) safeWidth, (int) safeHeight};
  691. }
  692. }
  693. #endif
  694. double dw, dh;
  695. fromPixels(pixelWidth, pixelHeight, dw, dh);
  696. return {0, 0, (int) dw, (int) dh};
  697. }
  698. bool Window::isOpen() const
  699. {
  700. return open;
  701. }
  702. void Window::setWindowTitle(const std::string &title)
  703. {
  704. this->title = title;
  705. if (window)
  706. SDL_SetWindowTitle(window, title.c_str());
  707. }
  708. const std::string &Window::getWindowTitle() const
  709. {
  710. return title;
  711. }
  712. bool Window::setIcon(love::image::ImageData *imgd)
  713. {
  714. if (!imgd)
  715. return false;
  716. if (imgd->getFormat() != PIXELFORMAT_RGBA8)
  717. throw love::Exception("setIcon only accepts 32-bit RGBA images.");
  718. icon.set(imgd);
  719. if (!window)
  720. return false;
  721. Uint32 rmask, gmask, bmask, amask;
  722. #ifdef LOVE_BIG_ENDIAN
  723. rmask = 0xFF000000;
  724. gmask = 0x00FF0000;
  725. bmask = 0x0000FF00;
  726. amask = 0x000000FF;
  727. #else
  728. rmask = 0x000000FF;
  729. gmask = 0x0000FF00;
  730. bmask = 0x00FF0000;
  731. amask = 0xFF000000;
  732. #endif
  733. int w = imgd->getWidth();
  734. int h = imgd->getHeight();
  735. int bytesperpixel = (int) getPixelFormatSize(imgd->getFormat());
  736. int pitch = w * bytesperpixel;
  737. SDL_Surface *sdlicon = nullptr;
  738. {
  739. // We don't want another thread modifying the ImageData mid-copy.
  740. love::thread::Lock lock(imgd->getMutex());
  741. sdlicon = SDL_CreateRGBSurfaceFrom(imgd->getData(), w, h, bytesperpixel * 8, pitch, rmask, gmask, bmask, amask);
  742. }
  743. if (!sdlicon)
  744. return false;
  745. SDL_SetWindowIcon(window, sdlicon);
  746. SDL_FreeSurface(sdlicon);
  747. return true;
  748. }
  749. love::image::ImageData *Window::getIcon()
  750. {
  751. return icon.get();
  752. }
  753. void Window::setVSync(int vsync)
  754. {
  755. if (context == nullptr)
  756. return;
  757. SDL_GL_SetSwapInterval(vsync);
  758. // Check if adaptive vsync was requested but not supported, and fall back
  759. // to regular vsync if so.
  760. if (vsync == -1 && SDL_GL_GetSwapInterval() != -1)
  761. SDL_GL_SetSwapInterval(1);
  762. }
  763. int Window::getVSync() const
  764. {
  765. return context != nullptr ? SDL_GL_GetSwapInterval() : 0;
  766. }
  767. void Window::setDisplaySleepEnabled(bool enable)
  768. {
  769. if (enable)
  770. SDL_EnableScreenSaver();
  771. else
  772. SDL_DisableScreenSaver();
  773. }
  774. bool Window::isDisplaySleepEnabled() const
  775. {
  776. return SDL_IsScreenSaverEnabled() != SDL_FALSE;
  777. }
  778. void Window::minimize()
  779. {
  780. if (window != nullptr)
  781. SDL_MinimizeWindow(window);
  782. }
  783. void Window::maximize()
  784. {
  785. if (window != nullptr)
  786. {
  787. SDL_MaximizeWindow(window);
  788. updateSettings(settings, true);
  789. }
  790. }
  791. void Window::restore()
  792. {
  793. if (window != nullptr)
  794. {
  795. SDL_RestoreWindow(window);
  796. updateSettings(settings, true);
  797. }
  798. }
  799. bool Window::isMaximized() const
  800. {
  801. return window != nullptr && (SDL_GetWindowFlags(window) & SDL_WINDOW_MAXIMIZED);
  802. }
  803. bool Window::isMinimized() const
  804. {
  805. return window != nullptr && (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED);
  806. }
  807. void Window::swapBuffers()
  808. {
  809. SDL_GL_SwapWindow(window);
  810. }
  811. bool Window::hasFocus() const
  812. {
  813. return (window && SDL_GetKeyboardFocus() == window);
  814. }
  815. bool Window::hasMouseFocus() const
  816. {
  817. return (window && SDL_GetMouseFocus() == window);
  818. }
  819. bool Window::isVisible() const
  820. {
  821. return window && (SDL_GetWindowFlags(window) & SDL_WINDOW_SHOWN) != 0;
  822. }
  823. void Window::setMouseGrab(bool grab)
  824. {
  825. mouseGrabbed = grab;
  826. if (window)
  827. SDL_SetWindowGrab(window, (SDL_bool) grab);
  828. }
  829. bool Window::isMouseGrabbed() const
  830. {
  831. if (window)
  832. return SDL_GetWindowGrab(window) != SDL_FALSE;
  833. else
  834. return mouseGrabbed;
  835. }
  836. int Window::getWidth() const
  837. {
  838. return windowWidth;
  839. }
  840. int Window::getHeight() const
  841. {
  842. return windowHeight;
  843. }
  844. int Window::getPixelWidth() const
  845. {
  846. return pixelWidth;
  847. }
  848. int Window::getPixelHeight() const
  849. {
  850. return pixelHeight;
  851. }
  852. void Window::windowToPixelCoords(double *x, double *y) const
  853. {
  854. if (x != nullptr)
  855. *x = (*x) * ((double) pixelWidth / (double) windowWidth);
  856. if (y != nullptr)
  857. *y = (*y) * ((double) pixelHeight / (double) windowHeight);
  858. }
  859. void Window::pixelToWindowCoords(double *x, double *y) const
  860. {
  861. if (x != nullptr)
  862. *x = (*x) * ((double) windowWidth / (double) pixelWidth);
  863. if (y != nullptr)
  864. *y = (*y) * ((double) windowHeight / (double) pixelHeight);
  865. }
  866. void Window::windowToDPICoords(double *x, double *y) const
  867. {
  868. double px = x != nullptr ? *x : 0.0;
  869. double py = y != nullptr ? *y : 0.0;
  870. windowToPixelCoords(&px, &py);
  871. double dpix = 0.0;
  872. double dpiy = 0.0;
  873. fromPixels(px, py, dpix, dpiy);
  874. if (x != nullptr)
  875. *x = dpix;
  876. if (y != nullptr)
  877. *y = dpiy;
  878. }
  879. void Window::DPIToWindowCoords(double *x, double *y) const
  880. {
  881. double dpix = x != nullptr ? *x : 0.0;
  882. double dpiy = y != nullptr ? *y : 0.0;
  883. double px = 0.0;
  884. double py = 0.0;
  885. toPixels(dpix, dpiy, px, py);
  886. pixelToWindowCoords(&px, &py);
  887. if (x != nullptr)
  888. *x = px;
  889. if (y != nullptr)
  890. *y = py;
  891. }
  892. double Window::getDPIScale() const
  893. {
  894. return settings.usedpiscale ? getNativeDPIScale() : 1.0;
  895. }
  896. double Window::getNativeDPIScale() const
  897. {
  898. #ifdef LOVE_ANDROID
  899. return love::android::getScreenScale();
  900. #else
  901. return (double) pixelHeight / (double) windowHeight;
  902. #endif
  903. }
  904. double Window::toPixels(double x) const
  905. {
  906. return x * getDPIScale();
  907. }
  908. void Window::toPixels(double wx, double wy, double &px, double &py) const
  909. {
  910. double scale = getDPIScale();
  911. px = wx * scale;
  912. py = wy * scale;
  913. }
  914. double Window::fromPixels(double x) const
  915. {
  916. return x / getDPIScale();
  917. }
  918. void Window::fromPixels(double px, double py, double &wx, double &wy) const
  919. {
  920. double scale = getDPIScale();
  921. wx = px / scale;
  922. wy = py / scale;
  923. }
  924. const void *Window::getHandle() const
  925. {
  926. return window;
  927. }
  928. SDL_MessageBoxFlags Window::convertMessageBoxType(MessageBoxType type) const
  929. {
  930. switch (type)
  931. {
  932. case MESSAGEBOX_ERROR:
  933. return SDL_MESSAGEBOX_ERROR;
  934. case MESSAGEBOX_WARNING:
  935. return SDL_MESSAGEBOX_WARNING;
  936. case MESSAGEBOX_INFO:
  937. default:
  938. return SDL_MESSAGEBOX_INFORMATION;
  939. }
  940. }
  941. bool Window::showMessageBox(const std::string &title, const std::string &message, MessageBoxType type, bool attachtowindow)
  942. {
  943. SDL_MessageBoxFlags flags = convertMessageBoxType(type);
  944. SDL_Window *sdlwindow = attachtowindow ? window : nullptr;
  945. return SDL_ShowSimpleMessageBox(flags, title.c_str(), message.c_str(), sdlwindow) >= 0;
  946. }
  947. int Window::showMessageBox(const MessageBoxData &data)
  948. {
  949. SDL_MessageBoxData sdldata = {};
  950. sdldata.flags = convertMessageBoxType(data.type);
  951. sdldata.title = data.title.c_str();
  952. sdldata.message = data.message.c_str();
  953. sdldata.window = data.attachToWindow ? window : nullptr;
  954. sdldata.numbuttons = (int) data.buttons.size();
  955. std::vector<SDL_MessageBoxButtonData> sdlbuttons;
  956. for (int i = 0; i < (int) data.buttons.size(); i++)
  957. {
  958. SDL_MessageBoxButtonData sdlbutton = {};
  959. sdlbutton.buttonid = i;
  960. sdlbutton.text = data.buttons[i].c_str();
  961. if (i == data.enterButtonIndex)
  962. sdlbutton.flags |= SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
  963. if (i == data.escapeButtonIndex)
  964. sdlbutton.flags |= SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;
  965. sdlbuttons.push_back(sdlbutton);
  966. }
  967. sdldata.buttons = &sdlbuttons[0];
  968. int pressedbutton = -2;
  969. SDL_ShowMessageBox(&sdldata, &pressedbutton);
  970. return pressedbutton;
  971. }
  972. void Window::requestAttention(bool continuous)
  973. {
  974. #if defined(LOVE_WINDOWS) && !defined(LOVE_WINDOWS_UWP)
  975. if (hasFocus())
  976. return;
  977. SDL_SysWMinfo wminfo = {};
  978. SDL_VERSION(&wminfo.version);
  979. if (SDL_GetWindowWMInfo(window, &wminfo))
  980. {
  981. FLASHWINFO flashinfo = {};
  982. flashinfo.cbSize = sizeof(FLASHWINFO);
  983. flashinfo.hwnd = wminfo.info.win.window;
  984. flashinfo.uCount = 1;
  985. flashinfo.dwFlags = FLASHW_ALL;
  986. if (continuous)
  987. {
  988. flashinfo.uCount = 0;
  989. flashinfo.dwFlags |= FLASHW_TIMERNOFG;
  990. }
  991. FlashWindowEx(&flashinfo);
  992. }
  993. #elif defined(LOVE_MACOSX)
  994. love::macosx::requestAttention(continuous);
  995. #else
  996. LOVE_UNUSED(continuous);
  997. #endif
  998. // TODO: Linux?
  999. }
  1000. const char *Window::getName() const
  1001. {
  1002. return "love.window.sdl";
  1003. }
  1004. } // sdl
  1005. } // window
  1006. } // love