You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

744 lines
21KB

  1. #include <map>
  2. #include <queue>
  3. #include <thread>
  4. #if defined ARCH_MAC
  5. // For CGAssociateMouseAndMouseCursorPosition
  6. #include <ApplicationServices/ApplicationServices.h>
  7. #endif
  8. #include <stb_image_write.h>
  9. #include <osdialog.h>
  10. #include <Window.hpp>
  11. #include <asset.hpp>
  12. #include <widget/Widget.hpp>
  13. #include <app/Scene.hpp>
  14. #include <keyboard.hpp>
  15. #include <gamepad.hpp>
  16. #include <context.hpp>
  17. #include <patch.hpp>
  18. #include <settings.hpp>
  19. #include <plugin.hpp> // used in Window::screenshot
  20. #include <system.hpp> // used in Window::screenshot
  21. namespace rack {
  22. static const math::Vec minWindowSize = math::Vec(640, 480);
  23. void Font::loadFile(const std::string& filename, NVGcontext* vg) {
  24. this->vg = vg;
  25. handle = nvgCreateFont(vg, filename.c_str(), filename.c_str());
  26. if (handle < 0)
  27. throw Exception("Failed to load font %s", filename.c_str());
  28. INFO("Loaded font %s", filename.c_str());
  29. }
  30. Font::~Font() {
  31. // There is no NanoVG deleteFont() function yet, so do nothing
  32. }
  33. std::shared_ptr<Font> Font::load(const std::string& filename) {
  34. return APP->window->loadFont(filename);
  35. }
  36. void Image::loadFile(const std::string& filename, NVGcontext* vg) {
  37. this->vg = vg;
  38. handle = nvgCreateImage(vg, filename.c_str(), NVG_IMAGE_REPEATX | NVG_IMAGE_REPEATY);
  39. if (handle <= 0)
  40. throw Exception("Failed to load image %s", filename.c_str());
  41. INFO("Loaded image %s", filename.c_str());
  42. }
  43. Image::~Image() {
  44. // TODO What if handle is invalid?
  45. if (handle >= 0)
  46. nvgDeleteImage(vg, handle);
  47. }
  48. std::shared_ptr<Image> Image::load(const std::string& filename) {
  49. return APP->window->loadImage(filename);
  50. }
  51. struct Window::Internal {
  52. std::string lastWindowTitle;
  53. int lastWindowX = 0;
  54. int lastWindowY = 0;
  55. int lastWindowWidth = 0;
  56. int lastWindowHeight = 0;
  57. int frame = 0;
  58. bool ignoreNextMouseDelta = false;
  59. int frameSwapInterval = -1;
  60. double monitorRefreshRate = 0.0;
  61. double frameTime = 0.0;
  62. double lastFrameDuration = 0.0;
  63. math::Vec lastMousePos;
  64. std::map<std::string, std::shared_ptr<Font>> fontCache;
  65. std::map<std::string, std::shared_ptr<Image>> imageCache;
  66. bool fbDirtyOnSubpixelChange = true;
  67. };
  68. static void windowPosCallback(GLFWwindow* win, int x, int y) {
  69. if (glfwGetWindowAttrib(win, GLFW_MAXIMIZED))
  70. return;
  71. if (glfwGetWindowMonitor(win))
  72. return;
  73. settings::windowPos = math::Vec(x, y);
  74. // DEBUG("windowPosCallback %d %d", x, y);
  75. }
  76. static void windowSizeCallback(GLFWwindow* win, int width, int height) {
  77. if (glfwGetWindowAttrib(win, GLFW_MAXIMIZED))
  78. return;
  79. if (glfwGetWindowMonitor(win))
  80. return;
  81. settings::windowSize = math::Vec(width, height);
  82. // DEBUG("windowSizeCallback %d %d", width, height);
  83. }
  84. static void windowMaximizeCallback(GLFWwindow* win, int maximized) {
  85. settings::windowMaximized = maximized;
  86. // DEBUG("windowMaximizeCallback %d", maximized);
  87. }
  88. static void mouseButtonCallback(GLFWwindow* win, int button, int action, int mods) {
  89. contextSet((Context*) glfwGetWindowUserPointer(win));
  90. #if defined ARCH_MAC
  91. // Remap Ctrl-left click to right click on Mac
  92. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == GLFW_MOD_CONTROL) {
  93. button = GLFW_MOUSE_BUTTON_RIGHT;
  94. mods &= ~GLFW_MOD_CONTROL;
  95. }
  96. // Remap Ctrl-shift-left click to middle click on Mac
  97. if (button == GLFW_MOUSE_BUTTON_LEFT && (mods & RACK_MOD_MASK) == (GLFW_MOD_CONTROL | GLFW_MOD_SHIFT)) {
  98. button = GLFW_MOUSE_BUTTON_MIDDLE;
  99. mods &= ~(GLFW_MOD_CONTROL | GLFW_MOD_SHIFT);
  100. }
  101. #endif
  102. APP->event->handleButton(APP->window->internal->lastMousePos, button, action, mods);
  103. }
  104. static void cursorPosCallback(GLFWwindow* win, double xpos, double ypos) {
  105. contextSet((Context*) glfwGetWindowUserPointer(win));
  106. math::Vec mousePos = math::Vec(xpos, ypos).div(APP->window->pixelRatio / APP->window->windowRatio).round();
  107. math::Vec mouseDelta = mousePos.minus(APP->window->internal->lastMousePos);
  108. // Workaround for GLFW warping mouse to a different position when the cursor is locked or unlocked.
  109. if (APP->window->internal->ignoreNextMouseDelta) {
  110. APP->window->internal->ignoreNextMouseDelta = false;
  111. mouseDelta = math::Vec();
  112. }
  113. int cursorMode = glfwGetInputMode(win, GLFW_CURSOR);
  114. (void) cursorMode;
  115. #if defined ARCH_MAC
  116. // Workaround for Mac. We can't use GLFW_CURSOR_DISABLED because it's buggy, so implement it on our own.
  117. // This is not an ideal implementation. For example, if the user drags off the screen, the new mouse position will be clamped.
  118. if (cursorMode == GLFW_CURSOR_HIDDEN) {
  119. // CGSetLocalEventsSuppressionInterval(0.0);
  120. glfwSetCursorPos(win, APP->window->internal->lastMousePos.x, APP->window->internal->lastMousePos.y);
  121. CGAssociateMouseAndMouseCursorPosition(true);
  122. mousePos = APP->window->internal->lastMousePos;
  123. }
  124. // Because sometimes the cursor turns into an arrow when its position is on the boundary of the window
  125. glfwSetCursor(win, NULL);
  126. #endif
  127. APP->window->internal->lastMousePos = mousePos;
  128. APP->event->handleHover(mousePos, mouseDelta);
  129. // Keyboard/mouse MIDI driver
  130. int width, height;
  131. glfwGetWindowSize(win, &width, &height);
  132. math::Vec scaledPos(xpos / width, ypos / height);
  133. keyboard::mouseMove(scaledPos);
  134. }
  135. static void cursorEnterCallback(GLFWwindow* win, int entered) {
  136. contextSet((Context*) glfwGetWindowUserPointer(win));
  137. if (!entered) {
  138. APP->event->handleLeave();
  139. }
  140. }
  141. static void scrollCallback(GLFWwindow* win, double x, double y) {
  142. contextSet((Context*) glfwGetWindowUserPointer(win));
  143. math::Vec scrollDelta = math::Vec(x, y);
  144. #if defined ARCH_MAC
  145. scrollDelta = scrollDelta.mult(10.0);
  146. #else
  147. scrollDelta = scrollDelta.mult(50.0);
  148. #endif
  149. APP->event->handleScroll(APP->window->internal->lastMousePos, scrollDelta);
  150. }
  151. static void charCallback(GLFWwindow* win, unsigned int codepoint) {
  152. contextSet((Context*) glfwGetWindowUserPointer(win));
  153. if (APP->event->handleText(APP->window->internal->lastMousePos, codepoint))
  154. return;
  155. }
  156. static void keyCallback(GLFWwindow* win, int key, int scancode, int action, int mods) {
  157. contextSet((Context*) glfwGetWindowUserPointer(win));
  158. if (APP->event->handleKey(APP->window->internal->lastMousePos, key, scancode, action, mods))
  159. return;
  160. // Keyboard/mouse MIDI driver
  161. if (action == GLFW_PRESS && (mods & RACK_MOD_MASK) == 0) {
  162. keyboard::press(key);
  163. }
  164. if (action == GLFW_RELEASE) {
  165. keyboard::release(key);
  166. }
  167. }
  168. static void dropCallback(GLFWwindow* win, int count, const char** paths) {
  169. contextSet((Context*) glfwGetWindowUserPointer(win));
  170. std::vector<std::string> pathsVec;
  171. for (int i = 0; i < count; i++) {
  172. pathsVec.push_back(paths[i]);
  173. }
  174. APP->event->handleDrop(APP->window->internal->lastMousePos, pathsVec);
  175. }
  176. static void errorCallback(int error, const char* description) {
  177. WARN("GLFW error %d: %s", error, description);
  178. }
  179. Window::Window() {
  180. internal = new Internal;
  181. int err;
  182. // Set window hints
  183. #if defined NANOVG_GL2
  184. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
  185. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
  186. #elif defined NANOVG_GL3
  187. glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
  188. glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
  189. glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
  190. glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
  191. #endif
  192. glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
  193. glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
  194. #if defined ARCH_MAC
  195. glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_TRUE);
  196. #endif
  197. // Create window
  198. win = glfwCreateWindow(1024, 768, "", NULL, NULL);
  199. if (!win) {
  200. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not open GLFW window. Does your graphics card support OpenGL 2.0 or greater? If so, make sure you have the latest graphics drivers installed.");
  201. exit(1);
  202. }
  203. float contentScale;
  204. glfwGetWindowContentScale(win, &contentScale, NULL);
  205. INFO("Window content scale: %f", contentScale);
  206. glfwSetWindowSizeLimits(win, minWindowSize.x, minWindowSize.y, GLFW_DONT_CARE, GLFW_DONT_CARE);
  207. glfwSetWindowSize(win, settings::windowSize.x, settings::windowSize.y);
  208. if (settings::windowPos.isFinite()) {
  209. glfwSetWindowPos(win, settings::windowPos.x, settings::windowPos.y);
  210. }
  211. if (settings::windowMaximized) {
  212. glfwMaximizeWindow(win);
  213. }
  214. glfwShowWindow(win);
  215. glfwSetWindowUserPointer(win, contextGet());
  216. glfwSetInputMode(win, GLFW_LOCK_KEY_MODS, 1);
  217. glfwMakeContextCurrent(win);
  218. glfwSwapInterval(1);
  219. const GLFWvidmode* monitorMode = glfwGetVideoMode(glfwGetPrimaryMonitor());
  220. internal->monitorRefreshRate = monitorMode->refreshRate;
  221. // Set window callbacks
  222. glfwSetWindowPosCallback(win, windowPosCallback);
  223. glfwSetWindowSizeCallback(win, windowSizeCallback);
  224. glfwSetWindowMaximizeCallback(win, windowMaximizeCallback);
  225. glfwSetMouseButtonCallback(win, mouseButtonCallback);
  226. // Call this ourselves, but on every frame instead of only when the mouse moves
  227. // glfwSetCursorPosCallback(win, cursorPosCallback);
  228. glfwSetCursorEnterCallback(win, cursorEnterCallback);
  229. glfwSetScrollCallback(win, scrollCallback);
  230. glfwSetCharCallback(win, charCallback);
  231. glfwSetKeyCallback(win, keyCallback);
  232. glfwSetDropCallback(win, dropCallback);
  233. // Set up GLEW
  234. glewExperimental = GL_TRUE;
  235. err = glewInit();
  236. if (err != GLEW_OK) {
  237. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize GLEW. Does your graphics card support OpenGL 2.0 or greater? If so, make sure you have the latest graphics drivers installed.");
  238. exit(1);
  239. }
  240. const GLubyte* vendor = glGetString(GL_VENDOR);
  241. const GLubyte* renderer = glGetString(GL_RENDERER);
  242. const GLubyte* version = glGetString(GL_VERSION);
  243. INFO("Renderer: %s %s", vendor, renderer);
  244. INFO("OpenGL: %s", version);
  245. // GLEW generates GL error because it calls glGetString(GL_EXTENSIONS), we'll consume it here.
  246. glGetError();
  247. // Set up NanoVG
  248. int nvgFlags = NVG_ANTIALIAS;
  249. #if defined NANOVG_GL2
  250. vg = nvgCreateGL2(nvgFlags);
  251. fbVg = nvgCreateSharedGL2(vg, nvgFlags);
  252. #elif defined NANOVG_GL3
  253. vg = nvgCreateGL3(nvgFlags);
  254. #elif defined NANOVG_GLES2
  255. vg = nvgCreateGLES2(nvgFlags);
  256. #endif
  257. if (!vg) {
  258. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize NanoVG. Does your graphics card support OpenGL 2.0 or greater? If so, make sure you have the latest graphics drivers installed.");
  259. exit(1);
  260. }
  261. // Load default Blendish font
  262. uiFont = loadFont(asset::system("res/fonts/DejaVuSans.ttf"));
  263. bndSetFont(uiFont->handle);
  264. if (APP->scene) {
  265. widget::Widget::ContextCreateEvent e;
  266. APP->scene->onContextCreate(e);
  267. }
  268. }
  269. Window::~Window() {
  270. if (APP->scene) {
  271. widget::Widget::ContextDestroyEvent e;
  272. APP->scene->onContextDestroy(e);
  273. }
  274. // Fonts and Images in the cache must be deleted before the NanoVG context is deleted
  275. internal->fontCache.clear();
  276. internal->imageCache.clear();
  277. // nvgDeleteClone(fbVg);
  278. #if defined NANOVG_GL2
  279. nvgDeleteGL2(vg);
  280. nvgDeleteGL2(fbVg);
  281. #elif defined NANOVG_GL3
  282. nvgDeleteGL3(vg);
  283. #elif defined NANOVG_GLES2
  284. nvgDeleteGLES2(vg);
  285. #endif
  286. glfwDestroyWindow(win);
  287. delete internal;
  288. }
  289. math::Vec Window::getSize() {
  290. int width, height;
  291. glfwGetWindowSize(win, &width, &height);
  292. return math::Vec(width, height);
  293. }
  294. void Window::setSize(math::Vec size) {
  295. size = size.max(minWindowSize);
  296. glfwSetWindowSize(win, size.x, size.y);
  297. }
  298. void Window::run() {
  299. internal->frame = 0;
  300. while (!glfwWindowShouldClose(win)) {
  301. step();
  302. }
  303. }
  304. void Window::step() {
  305. double frameTime = system::getTime();
  306. double lastFrameTime = internal->frameTime;
  307. internal->frameTime = frameTime;
  308. internal->lastFrameDuration = frameTime - lastFrameTime;
  309. // DEBUG("%.2lf Hz", 1.0 / internal->lastFrameDuration);
  310. double t1 = 0.0, t2 = 0.0, t3 = 0.0, t4 = 0.0, t5 = 0.0;
  311. // Make event handlers and step() have a clean NanoVG context
  312. nvgReset(vg);
  313. bndSetFont(uiFont->handle);
  314. // Poll events
  315. // Save and restore context because event handler set their own context based on which window they originate from.
  316. Context* context = contextGet();
  317. glfwPollEvents();
  318. contextSet(context);
  319. // In case glfwPollEvents() sets another OpenGL context
  320. glfwMakeContextCurrent(win);
  321. if (settings::frameSwapInterval != internal->frameSwapInterval) {
  322. glfwSwapInterval(settings::frameSwapInterval);
  323. internal->frameSwapInterval = settings::frameSwapInterval;
  324. }
  325. // Call cursorPosCallback every frame, not just when the mouse moves
  326. {
  327. double xpos, ypos;
  328. glfwGetCursorPos(win, &xpos, &ypos);
  329. cursorPosCallback(win, xpos, ypos);
  330. }
  331. gamepad::step();
  332. // Set window title
  333. std::string windowTitle = APP_NAME + " " + APP_VARIANT + " " + APP_VERSION;
  334. if (APP->patch->path != "") {
  335. windowTitle += " - ";
  336. if (!APP->history->isSaved())
  337. windowTitle += "*";
  338. windowTitle += system::getFilename(APP->patch->path);
  339. }
  340. if (windowTitle != internal->lastWindowTitle) {
  341. glfwSetWindowTitle(win, windowTitle.c_str());
  342. internal->lastWindowTitle = windowTitle;
  343. }
  344. // Get desired scaling
  345. float newPixelRatio;
  346. glfwGetWindowContentScale(win, &newPixelRatio, NULL);
  347. newPixelRatio = std::floor(newPixelRatio + 0.5);
  348. if (newPixelRatio != pixelRatio) {
  349. APP->event->handleDirty();
  350. pixelRatio = newPixelRatio;
  351. }
  352. // Get framebuffer/window ratio
  353. int fbWidth, fbHeight;
  354. glfwGetFramebufferSize(win, &fbWidth, &fbHeight);
  355. int winWidth, winHeight;
  356. glfwGetWindowSize(win, &winWidth, &winHeight);
  357. windowRatio = (float)fbWidth / winWidth;
  358. t1 = system::getTime();
  359. if (APP->scene) {
  360. // DEBUG("%f %f %d %d", pixelRatio, windowRatio, fbWidth, winWidth);
  361. // Resize scene
  362. APP->scene->box.size = math::Vec(fbWidth, fbHeight).div(pixelRatio);
  363. // Step scene
  364. APP->scene->step();
  365. t2 = system::getTime();
  366. // Render scene
  367. bool visible = glfwGetWindowAttrib(win, GLFW_VISIBLE) && !glfwGetWindowAttrib(win, GLFW_ICONIFIED);
  368. if (visible) {
  369. // Update and render
  370. nvgBeginFrame(vg, fbWidth, fbHeight, pixelRatio);
  371. nvgScale(vg, pixelRatio, pixelRatio);
  372. // Draw scene
  373. widget::Widget::DrawArgs args;
  374. args.vg = vg;
  375. args.clipBox = APP->scene->box.zeroPos();
  376. APP->scene->draw(args);
  377. t3 = system::getTime();
  378. glViewport(0, 0, fbWidth, fbHeight);
  379. glClearColor(0.0, 0.0, 0.0, 1.0);
  380. glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  381. nvgEndFrame(vg);
  382. t4 = system::getTime();
  383. }
  384. }
  385. glfwSwapBuffers(win);
  386. // On some platforms, glfwSwapBuffers() doesn't wait on monitor refresh, so we have to sleep as a fallback.
  387. double frameDurationRemaining = getFrameDurationRemaining();
  388. if (frameDurationRemaining > 0.0) {
  389. std::this_thread::sleep_for(std::chrono::duration<double>(frameDurationRemaining));
  390. }
  391. t5 = system::getTime();
  392. // DEBUG("pre-step %6.1f step %6.1f draw %6.1f nvgEndFrame %6.1f glfwSwapBuffers %6.1f total %6.1f",
  393. // (t1 - frameTime) * 1e3f,
  394. // (t2 - t1) * 1e3f,
  395. // (t3 - t2) * 1e3f,
  396. // (t4 - t2) * 1e3f,
  397. // (t5 - t4) * 1e3f,
  398. // (t5 - frameTime) * 1e3f
  399. // );
  400. internal->frame++;
  401. }
  402. static void flipBitmap(uint8_t* pixels, int width, int height, int depth) {
  403. for (int y = 0; y < height / 2; y++) {
  404. int flipY = height - y - 1;
  405. uint8_t tmp[width * depth];
  406. std::memcpy(tmp, &pixels[y * width * depth], width * depth);
  407. std::memcpy(&pixels[y * width * depth], &pixels[flipY * width * depth], width * depth);
  408. std::memcpy(&pixels[flipY * width * depth], tmp, width * depth);
  409. }
  410. }
  411. void Window::screenshot(const std::string& screenshotPath) {
  412. // Get window framebuffer size
  413. int width, height;
  414. glfwGetFramebufferSize(APP->window->win, &width, &height);
  415. // Allocate pixel color buffer
  416. uint8_t* pixels = new uint8_t[height * width * 4];
  417. // glReadPixels defaults to GL_BACK, but the back-buffer is unstable, so use the front buffer (what the user sees)
  418. glReadBuffer(GL_FRONT);
  419. glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
  420. // Write pixels to PNG
  421. flipBitmap(pixels, width, height, 4);
  422. stbi_write_png(screenshotPath.c_str(), width, height, 4, pixels, width * 4);
  423. delete[] pixels;
  424. }
  425. void Window::screenshotModules(const std::string& screenshotsDir, float zoom) {
  426. // Iterate plugins and create directories
  427. system::createDirectories(screenshotsDir);
  428. for (plugin::Plugin* p : plugin::plugins) {
  429. std::string dir = system::join(screenshotsDir, p->slug);
  430. system::createDirectory(dir);
  431. for (plugin::Model* model : p->models) {
  432. std::string filename = system::join(dir, model->slug + ".png");
  433. // Skip model if screenshot already exists
  434. if (system::isFile(filename))
  435. continue;
  436. INFO("Screenshotting %s %s to %s", p->slug.c_str(), model->slug.c_str(), filename.c_str());
  437. // Create widgets
  438. widget::FramebufferWidget* fbw = new widget::FramebufferWidget;
  439. fbw->oversample = 2;
  440. app::ModuleWidget* mw = model->createModuleWidget(NULL);
  441. fbw->addChild(mw);
  442. fbw->box.size = mw->box.size;
  443. // Step to allow the ModuleWidget state to set its default appearance.
  444. fbw->step();
  445. // Draw to framebuffer
  446. fbw->render(math::Vec(zoom, zoom));
  447. // Read pixels
  448. nvgluBindFramebuffer(fbw->getFramebuffer());
  449. int width, height;
  450. nvgImageSize(vg, fbw->getImageHandle(), &width, &height);
  451. uint8_t* pixels = new uint8_t[height * width * 4];
  452. glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
  453. // Write pixels to PNG
  454. flipBitmap(pixels, width, height, 4);
  455. stbi_write_png(filename.c_str(), width, height, 4, pixels, width * 4);
  456. // Cleanup
  457. delete[] pixels;
  458. nvgluBindFramebuffer(NULL);
  459. delete fbw;
  460. }
  461. }
  462. }
  463. void Window::close() {
  464. glfwSetWindowShouldClose(win, GLFW_TRUE);
  465. }
  466. void Window::cursorLock() {
  467. if (!settings::allowCursorLock)
  468. return;
  469. #if defined ARCH_MAC
  470. glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
  471. #else
  472. glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_DISABLED);
  473. #endif
  474. internal->ignoreNextMouseDelta = true;
  475. }
  476. void Window::cursorUnlock() {
  477. if (!settings::allowCursorLock)
  478. return;
  479. glfwSetInputMode(win, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
  480. internal->ignoreNextMouseDelta = true;
  481. }
  482. bool Window::isCursorLocked() {
  483. return glfwGetInputMode(win, GLFW_CURSOR) != GLFW_CURSOR_NORMAL;
  484. }
  485. int Window::getMods() {
  486. int mods = 0;
  487. if (glfwGetKey(win, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS)
  488. mods |= GLFW_MOD_SHIFT;
  489. if (glfwGetKey(win, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS)
  490. mods |= GLFW_MOD_CONTROL;
  491. if (glfwGetKey(win, GLFW_KEY_LEFT_ALT) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS)
  492. mods |= GLFW_MOD_ALT;
  493. if (glfwGetKey(win, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS || glfwGetKey(win, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS)
  494. mods |= GLFW_MOD_SUPER;
  495. return mods;
  496. }
  497. void Window::setFullScreen(bool fullScreen) {
  498. if (!fullScreen) {
  499. glfwSetWindowMonitor(win, NULL, internal->lastWindowX, internal->lastWindowY, internal->lastWindowWidth, internal->lastWindowHeight, GLFW_DONT_CARE);
  500. }
  501. else {
  502. glfwGetWindowPos(win, &internal->lastWindowX, &internal->lastWindowY);
  503. glfwGetWindowSize(win, &internal->lastWindowWidth, &internal->lastWindowHeight);
  504. GLFWmonitor* monitor = glfwGetPrimaryMonitor();
  505. const GLFWvidmode* mode = glfwGetVideoMode(monitor);
  506. glfwSetWindowMonitor(win, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
  507. }
  508. }
  509. bool Window::isFullScreen() {
  510. GLFWmonitor* monitor = glfwGetWindowMonitor(win);
  511. return monitor != NULL;
  512. }
  513. double Window::getMonitorRefreshRate() {
  514. return internal->monitorRefreshRate;
  515. }
  516. double Window::getFrameTime() {
  517. return internal->frameTime;
  518. }
  519. double Window::getLastFrameDuration() {
  520. return internal->lastFrameDuration;
  521. }
  522. double Window::getFrameDurationRemaining() {
  523. double frameDurationDesired = internal->frameSwapInterval / internal->monitorRefreshRate;
  524. return frameDurationDesired - (system::getTime() - internal->frameTime);
  525. }
  526. std::shared_ptr<Font> Window::loadFont(const std::string& filename) {
  527. const auto& pair = internal->fontCache.find(filename);
  528. if (pair != internal->fontCache.end())
  529. return pair->second;
  530. // Load font
  531. std::shared_ptr<Font> font;
  532. try {
  533. font = std::make_shared<Font>();
  534. font->loadFile(filename, vg);
  535. }
  536. catch (Exception& e) {
  537. WARN("%s", e.what());
  538. font = NULL;
  539. }
  540. internal->fontCache[filename] = font;
  541. return font;
  542. }
  543. std::shared_ptr<Image> Window::loadImage(const std::string& filename) {
  544. const auto& pair = internal->imageCache.find(filename);
  545. if (pair != internal->imageCache.end())
  546. return pair->second;
  547. // Load image
  548. std::shared_ptr<Image> image;
  549. try {
  550. image = std::make_shared<Image>();
  551. image->loadFile(filename, vg);
  552. }
  553. catch (Exception& e) {
  554. WARN("%s", e.what());
  555. image = NULL;
  556. }
  557. internal->imageCache[filename] = image;
  558. return image;
  559. }
  560. bool& Window::fbDirtyOnSubpixelChange() {
  561. return internal->fbDirtyOnSubpixelChange;
  562. }
  563. void windowInit() {
  564. int err;
  565. // Set up GLFW
  566. #if defined ARCH_MAC
  567. glfwInitHint(GLFW_COCOA_CHDIR_RESOURCES, GLFW_TRUE);
  568. glfwInitHint(GLFW_COCOA_MENUBAR, GLFW_TRUE);
  569. #endif
  570. glfwSetErrorCallback(errorCallback);
  571. err = glfwInit();
  572. if (err != GLFW_TRUE) {
  573. osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "Could not initialize GLFW.");
  574. exit(1);
  575. }
  576. }
  577. void windowDestroy() {
  578. glfwTerminate();
  579. }
  580. } // namespace rack