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.

838 lines
23KB

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