TDME2  1.9.200
Application.cpp
Go to the documentation of this file.
1 #if defined(_MSC_VER)
2  // this suppresses a warning redefinition of APIENTRY macro
3  #define NOMINMAX
4  #include <windows.h>
5 #endif
6 #define GLFW_INCLUDE_NONE
7 #include <GLFW/glfw3.h>
8 
9 #if defined(_WIN32)
10  #define GLFW_EXPOSE_NATIVE_WIN32
11  #include <GLFW/glfw3native.h>
12 #endif
13 
14 #if defined(_WIN32)
15  #include <windows.h>
16  #include <dbghelp.h>
17  #include <stdio.h>
18  #include <string.h>
19  #include <tdme/os/threading/Mutex.h>
20 #endif
21 
22 #if defined(__APPLE__)
23  #include <Carbon/Carbon.h>
24 #endif
25 
26 #if !defined(_MSC_VER)
27  #include <dlfcn.h>
28 #endif
29 
30 #include <stdlib.h>
31 
32 #include <array>
33 #include <clocale>
34 #include <memory>
35 #include <string>
36 
37 #include <tdme/tdme.h>
38 
41 #include <tdme/audio/Audio.h>
42 #include <tdme/engine/Texture.h>
45 #include <tdme/engine/Engine.h>
51 #include <tdme/utilities/Console.h>
53 #include <tdme/utilities/Hex.h>
54 #include <tdme/utilities/RTTI.h>
57 #include <tdme/utilities/Time.h>
58 
59 using std::array;
60 using std::shared_ptr;
61 using std::string;
62 using std::to_string;
63 
66 using tdme::audio::Audio;
83 
84 Renderer* Application::renderer = nullptr;
85 Application* Application::application = nullptr;
86 InputEventHandler* Application::inputEventHandler = nullptr;
87 int64_t Application::timeLast = -1L;
88 bool Application::limitFPS = true;
89 
90 GLFWwindow* Application::glfwWindow = nullptr;
91 array<unsigned int, 10> Application::glfwMouseButtonDownFrames;
92 int Application::glfwMouseButtonLast = -1;
93 bool Application::glfwCapsLockEnabled = false;
94 GLFWcursor* Application::glfwHandCursor = nullptr;
95 
96 int Application::mouseCursor = MOUSE_CURSOR_ENABLED;
97 
98 string Application::execute(const string& command) {
99  // see: https://stackoverflow.com/questions/478898/how-to-execute-a-command-and-get-output-of-command-within-c-using-posix
100  array<char, 128> buffer;
101  string result;
102  #if defined(_MSC_VER)
103  shared_ptr<FILE> pipe(_popen(command.c_str(), "r"), _pclose);
104  #else
105  shared_ptr<FILE> pipe(popen(command.c_str(), "r"), pclose);
106  #endif
107  if (!pipe) throw std::runtime_error("popen() failed!");
108  while (!feof(pipe.get())) {
109  if (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr)
110  result += buffer.data();
111  }
112  return result;
113 }
114 
115 void Application::executeBackground(const string& command) {
116  #if defined(_WIN32)
117  system(("start " + command).c_str());
118  #else
119  system((command + " </dev/null &>/dev/null &").c_str());
120  #endif
121 }
122 
123 void Application::openBrowser(const string& url) {
124  #if defined(_WIN32)
125  execute("explorer \"" + url + "\"");
126  #elif defined(__APPLE__) || defined(__HAIKU__)
127  execute("open \"" + url + "\"");
128  #else
129  execute("xdg-open \"" + url + "\"");
130  #endif
131 }
132 
134  glfwSetWindowShouldClose(glfwWindow, GLFW_FALSE);
135 }
136 
137 void Application::exit(int exitCode) {
138  if (Application::application == nullptr) {
139  Console::shutdown();
140  ::exit(exitCode);
141  } else {
142  if (Application::application->initialized == false) {
143  ::exit(exitCode);
144  } else {
146  glfwSetWindowShouldClose(glfwWindow, GLFW_TRUE);
147  }
148  }
149 }
150 
151 #if defined(_WIN32)
152  LONG WINAPI windowsExceptionHandler(struct _EXCEPTION_POINTERS* exceptionInfo) {
153  // see:
154  // https://asmaloney.com/2017/08/code/crash-reporting-for-mingw-32-windows-and-clang-macos-with-qt/
155  // https://www.gamedev.net/forums/topic/478943-stackwalk64-and-x86/
156  // https://stackoverflow.com/questions/12280472/stackwalk64-does-not-work-with-release-build
157 
158  static tdme::os::threading::Mutex mutex("windowsexceptionhandler");
159 
160  mutex.lock();
161 
162  HANDLE process = GetCurrentProcess();
163  HANDLE thread = GetCurrentThread();
164 
165  vector<string> backtraceLines;
166  auto backtraceHeaderLine = "windowsExceptionHandler(): process " + to_string((uint64_t)process) + " crashed: Printing stacktrace(thread " + to_string((uint64_t)thread) + ")";
167  Console::println(backtraceHeaderLine);
168  backtraceLines.push_back(backtraceHeaderLine);
169 
170  CHAR _pathToExecutable[MAX_PATH];
171  GetModuleFileName(GetModuleHandleW(NULL), _pathToExecutable, MAX_PATH);
172  string pathToExecutable = _pathToExecutable;
173 
174  #if defined(_MSC_VER) == false
175  auto addr2lineToolCmd = StringTools::substring(pathToExecutable, 0, StringTools::lastIndexOf(pathToExecutable, '\\')) + "\\addr2line.exe";
176  if (FileSystem::getInstance()->fileExists(addr2lineToolCmd) == false) {
177  Console::println("handler(): " + addr2lineToolCmd + ": not found! Please copy addr2line utility to binary location!");
178  mutex.unlock();
179 
180  //
181  Console::println();
182 
183  // shutdown console
184  Console::shutdown();
185 
186  //
187  return EXCEPTION_EXECUTE_HANDLER;
188  }
189  addr2lineToolCmd = "\"" + StringTools::replace(addr2lineToolCmd, "\\", "\\\\") + "\"";
190  #endif
191 
192  auto backtraceExecutableLine = "windowsExceptionHandler(): path to executable: " + pathToExecutable;
193  Console::println(backtraceExecutableLine);
194  backtraceLines.push_back(backtraceExecutableLine);
195 
196  pathToExecutable = string("\"") + StringTools::replace(pathToExecutable, "\\", "\\\\") + "\"";
197 
198  SymSetOptions(
199  SYMOPT_UNDNAME |
200  SYMOPT_LOAD_LINES |
201  SYMOPT_OMAP_FIND_NEAREST |
202  SYMOPT_DEFERRED_LOADS |
203  SymGetOptions()
204  );
205 
206  SymInitialize(
207  process,
208  nullptr,
209  true
210  );
211 
212  STACKFRAME64 stackFrame;
213  memset(&stackFrame, 0, sizeof(STACKFRAME64));
214 
215  CONTEXT context;
216  memset(&context, 0, sizeof(context));
217  context.ContextFlags = CONTEXT_FULL;
218  RtlCaptureContext(&context);
219 
220  DWORD image;
221  #ifdef _M_IX86
222  image = IMAGE_FILE_MACHINE_I386;
223  stackFrame.AddrPC.Offset = context.Eip;
224  stackFrame.AddrPC.Mode = AddrModeFlat;
225  stackFrame.AddrFrame.Offset = context.Ebp;
226  stackFrame.AddrFrame.Mode = AddrModeFlat;
227  stackFrame.AddrStack.Offset = context.Esp;
228  stackFrame.AddrStack.Mode = AddrModeFlat;
229  #elif _M_X64
230  image = IMAGE_FILE_MACHINE_AMD64;
231  stackFrame.AddrPC.Offset = context.Rip;
232  stackFrame.AddrPC.Mode = AddrModeFlat;
233  stackFrame.AddrFrame.Offset = context.Rsp;
234  stackFrame.AddrFrame.Mode = AddrModeFlat;
235  stackFrame.AddrStack.Offset = context.Rsp;
236  stackFrame.AddrStack.Mode = AddrModeFlat;
237  #elif _M_IA64
238  image = IMAGE_FILE_MACHINE_IA64;
239  stackFrame.AddrPC.Offset = context.StIIP;
240  stackFrame.AddrPC.Mode = AddrModeFlat;
241  stackFrame.AddrFrame.Offset = context.IntSp;
242  stackFrame.AddrFrame.Mode = AddrModeFlat;
243  stackFrame.AddrBStore.Offset = context.RsBSP;
244  stackFrame.AddrBStore.Mode = AddrModeFlat;
245  stackFrame.AddrStack.Offset = context.IntSp;
246  stackFrame.AddrStack.Mode = AddrModeFlat;
247  #else
248  #error "Machine not supported"
249  #endif
250 
251  // construct backtrace and save it to console.log and crash.log
252  int frameIdx = 1;
253  while (StackWalk64(image, process, thread, &stackFrame, &context, NULL, &SymFunctionTableAccess64, &SymGetModuleBase64, NULL)) {
254  if (stackFrame.AddrPC.Offset == stackFrame.AddrReturn.Offset) break;
255  if (stackFrame.AddrPC.Offset == 0) continue;
256 
257  auto line = 0;
258  char _fileName[1024];
259  char _functionName[1024];
260 
261  // Get function name
262  {
263  #define cnBufferSize 1024
264  unsigned char byBuffer[sizeof(IMAGEHLP_SYMBOL64) + cnBufferSize];
265  IMAGEHLP_SYMBOL64* pSymbol = (IMAGEHLP_SYMBOL64*)byBuffer;
266  DWORD64 dwDisplacement;
267  memset(pSymbol, 0, sizeof(IMAGEHLP_SYMBOL64) + cnBufferSize);
268  pSymbol->SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
269  pSymbol->MaxNameLength = cnBufferSize;
270  if (!SymGetSymFromAddr64(process, stackFrame.AddrPC.Offset, &dwDisplacement, pSymbol)) {
271  strcpy(_functionName, "??");
272  } else {
273  pSymbol->Name[cnBufferSize-1] = '\0';
274  strcpy(_functionName, pSymbol->Name);
275  }
276  }
277 
278  // Get file/line number
279  {
280  IMAGEHLP_LINE64 theLine;
281  DWORD dwDisplacement;
282  memset(&theLine, 0, sizeof(theLine));
283  theLine.SizeOfStruct = sizeof(theLine);
284  if(!SymGetLineFromAddr64(process, stackFrame.AddrPC.Offset, &dwDisplacement, &theLine))
285  {
286  strcpy(_fileName, "??");
287  }
288  else
289  {
290  const char* pszFile = strrchr(theLine.FileName, '\\');
291  if (!pszFile) pszFile = theLine.FileName; else ++pszFile;
292  strncpy(_fileName, pszFile, cnBufferSize);
293  line = theLine.LineNumber;
294  }
295  }
296 
297  //
298  string functionName = _functionName;
299  string fileName = _fileName;
300  #if defined(_MSC_VER) == false
301  if (functionName == "??") {
302  string hexAddr;
303  Hex::encodeInt(stackFrame.AddrPC.Offset, hexAddr);
304  string addr2LineCommand = "\"" + addr2lineToolCmd + " -f -p -e " + string(pathToExecutable) + " " + hexAddr + "\"";
305  auto addr2LineOutput = Application::execute(addr2LineCommand);
306  StringTokenizer t;
307  t.tokenize(addr2LineOutput, " ");
308  if (t.hasMoreTokens() == true) {
309  auto addr2lineFunctionName = t.nextToken();
310  addr2LineOutput = StringTools::replace(StringTools::replace(addr2LineOutput, addr2lineFunctionName, RTTI::demangle(addr2lineFunctionName.c_str())), "\n", "");
311  }
312  auto backtraceLine = to_string(frameIdx) + ": " + addr2LineOutput;
313  Console::println(backtraceLine);
314  backtraceLines.push_back(backtraceLine);
315  } else {
316  auto backtraceLine = to_string(frameIdx) + ": " + string(RTTI::demangle(_functionName)) + " at " + fileName + ":" + to_string(line);
317  Console::println(backtraceLine);
318  backtraceLines.push_back(backtraceLine);
319  }
320  #else
321  Console::println(to_string(frameIdx) + ": " + functionName + " at " + fileName + ":" + to_string(line));
322  #endif
323  frameIdx++;
324  }
325 
326  // store also to crash.log
327  FileSystem::getInstance()->setContentFromStringArray(".", "crash.log", backtraceLines);
328 
329  SymCleanup(process);
330 
331  mutex.unlock();
332 
333  Console::println();
334 
335  // shutdown console
336  Console::shutdown();
337 
338  //
339  return EXCEPTION_EXECUTE_HANDLER;
340  }
341 #endif
342 
346 }
347 
349 }
350 
352  Engine::getRenderer()->setVSync(vSync);
353  if (Engine::getRenderer()->getRendererType() != Renderer::RENDERERTYPE_VULKAN) {
354  glfwSwapInterval(vSync == true?1:0);
355  }
356 }
357 
359  #if defined(__FreeBSD__)
360  return "FreeBSD";
361  #elif defined(__HAIKU__)
362  return "Haiku";
363  #elif defined(__linux__)
364  return "Linux";
365  #elif defined(__APPLE__)
366  return "MacOSX";
367  #elif defined(__NetBSD__)
368  return "NetBSD";
369  #elif defined(__OpenBSD__)
370  return "OpenBSD";
371  #elif defined(_MSC_VER)
372  return "Windows-MSC";
373  #elif defined(_WIN32)
374  return "Windows-MINGW";
375  #else
376  return "Unknown";
377  #endif
378 }
379 
381  #if defined(__amd64__) || defined(_M_X64)
382  return "x64";
383  #elif defined(__ia64__) || defined(_M_IA64)
384  return "ia64";
385  #elif defined(__aarch64__)
386  return "arm64";
387  #elif defined(__arm__) || defined(_M_ARM)
388  return "arm";
389  #elif defined(__powerpc64__)
390  return "ppc64";
391  #elif defined(__powerpc__)
392  return "ppc";
393  #else
394  return "Unknown";
395  #endif
396 }
397 
398 void Application::setLocale(const string& locale) {
399  setlocale(LC_ALL, locale.c_str());
400 }
401 
404 }
405 
407  #if defined(_WIN32)
408  return GetActiveWindow() == GetForegroundWindow();
409  #else
410  return true;
411  #endif
412 }
413 
415  if (glfwWindow != nullptr) glfwGetWindowPos(glfwWindow, &windowXPosition, &windowYPosition);
416  return windowXPosition;
417 }
418 
419 void Application::setWindowXPosition(int windowXPosition) {
420  if (glfwWindow != nullptr) glfwSetWindowPos(glfwWindow, windowXPosition, windowYPosition);
421  this->windowXPosition = windowXPosition;
422 }
423 
425  if (glfwWindow != nullptr) glfwGetWindowPos(glfwWindow, &windowXPosition, &windowYPosition);
426  return windowYPosition;
427 }
428 
429 void Application::setWindowYPosition(int windowYPosition) {
430  if (glfwWindow != nullptr) glfwSetWindowPos(glfwWindow, windowXPosition, windowYPosition);
431  this->windowYPosition = windowYPosition;
432 }
433 
435  return windowWidth;
436 }
437 
438 void Application::setWindowWidth(int windowWidth) {
439  this->windowWidth = windowWidth;
440  if (initialized == true) {
441  if (fullScreen == false) glfwSetWindowSize(glfwWindow, windowWidth, windowHeight);
442  }
443 }
444 
446  return windowHeight;
447 }
448 
449 void Application::setWindowHeight(int windowHeight) {
450  this->windowHeight = windowHeight;
451  if (initialized == true) {
452  if (fullScreen == false) glfwSetWindowSize(glfwWindow, windowWidth, windowHeight);
453  }
454 }
455 
457  return fullScreen;
458 }
459 
460 void Application::setFullScreen(bool fullScreen) {
461  this->fullScreen = fullScreen;
462  if (initialized == true) {
463  auto windowMonitor = glfwGetWindowMonitor(glfwWindow);
464  if (windowMonitor == nullptr && fullScreen == true) {
465  auto monitor = glfwGetPrimaryMonitor();
466  auto mode = glfwGetVideoMode(monitor);
467  glfwSetWindowMonitor(glfwWindow, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
468  } else
469  if (windowMonitor != nullptr && fullScreen == false) {
470  glfwSetWindowMonitor(glfwWindow, NULL, windowXPosition, windowYPosition, windowWidth, windowHeight, 0);
472  glfwSetWindowPos(glfwWindow, windowXPosition, windowYPosition);
473  glfwGetWindowSize(glfwWindow, &windowWidth, &windowHeight);
475  }
476  }
477  }
478 }
479 
481  #if defined(_WIN32)
482  SetUnhandledExceptionFilter(windowsExceptionHandler);
483  #endif
484 }
485 
486 void Application::setMouseCursor(int mouseCursor) {
488  glfwSetCursor(glfwWindow, nullptr);
489  glfwSetInputMode(glfwWindow, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
490  } else
492  glfwSetCursor(glfwWindow, nullptr);
493  glfwSetInputMode(glfwWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
494  } else
496  glfwSetCursor(glfwWindow, glfwHandCursor);
497  glfwSetInputMode(glfwWindow, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
498  }
500 }
501 
503  double mouseX, mouseY;
504  glfwGetCursorPos(glfwWindow, &mouseX, &mouseY);
505  return static_cast<int>(mouseX);
506 }
507 
509  double mouseX, mouseY;
510  glfwGetCursorPos(glfwWindow, &mouseX, &mouseY);
511  return static_cast<int>(mouseY);
512 }
513 
514 void Application::setMousePosition(int x, int y) {
515  #if defined(__APPLE__)
516  int windowXPos, windowYPos;
517  glfwGetWindowPos(Application::glfwWindow, &windowXPos, &windowYPos);
518  CGPoint point;
519  point.x = windowXPos + x;
520  point.y = windowYPos + y;
521  CGWarpMouseCursorPosition(point);
522  CGAssociateMouseAndMouseCursorPosition(true);
523  #else
524  glfwSetCursorPos(glfwWindow, x, y);
525  #endif
526 }
527 
529  if (Engine::getRenderer()->getRendererType() != Renderer::RENDERERTYPE_VULKAN) glfwSwapBuffers(glfwWindow);
530 }
531 
533  return string(glfwGetClipboardString(glfwWindow));
534 }
535 
536 void Application::setClipboardContent(const string& content) {
537  glfwSetClipboardString(glfwWindow, content.c_str());
538 }
539 
540 static void glfwErrorCallback(int error, const char* description) {
541  Console::println(string("glfwErrorCallback(): ") + description);
542 }
543 
544 int Application::run(int argc, char** argv, const string& title, InputEventHandler* inputEventHandler, int windowHints) {
545  string rendererLibrary = "libopengl3corerenderer";
546  for (auto i = 1; i < argc; i++) {
547  auto argValue = string(argv[i]);
548  if (argValue == "--debug") debuggingEnabled = true; else
549  if (argValue == "--gles2") rendererLibrary = "libopengles2renderer"; else
550  if (argValue == "--gl2") rendererLibrary = "libopengl2renderer"; else
551  if (argValue == "--gl3core") rendererLibrary = "libopengl3corerenderer"; else
552  if (argValue == "--vulkan") rendererLibrary = "libvulkanrenderer";
553  }
554 
555  #if defined(_WIN32)
556  rendererLibrary = rendererLibrary + ".dll";
557  #elif defined(__APPLE__)
558  rendererLibrary = rendererLibrary + ".dylib";
559  #else
560  rendererLibrary = rendererLibrary + ".so";
561  #endif
562 
563  //
564  this->title = title;
565  this->windowHints = windowHints;
566  executableFileName = FileSystem::getInstance()->getFileName(argv[0]);
568  glfwSetErrorCallback(glfwErrorCallback);
569  if (glfwInit() == false) {
570  Console::println("glflInit(): failed!");
571  return EXITCODE_FAILURE;
572  }
573 
574  // TODO: dispose
575  Application::glfwHandCursor = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
576 
577  // determine window position of not yet done
578  if (windowXPosition == -1 || windowYPosition == -1) {
579  // have some random position if position determination does fail
580  windowXPosition = 100;
581  windowYPosition = 100;
582  // otherwise center application window on primary monitor
583  GLFWmonitor* monitor = glfwGetPrimaryMonitor();
584  const GLFWvidmode* monitorMode = glfwGetVideoMode(monitor);
585  if (monitorMode != nullptr) {
586  int monitorX = -1;
587  int monitorY = -1;
588  glfwGetMonitorPos(monitor, &monitorX, &monitorY);
589  windowXPosition = monitorX + (monitorMode->width - windowWidth) / 2;
590  windowYPosition = monitorY + (monitorMode->height - windowHeight) / 2;
591  }
592  }
593 
594  //
595  Console::println("Application::run(): Opening renderer library: " + rendererLibrary);
596 
597  // load renderer library
598  #if defined(_MSC_VER)
599  //
600  auto rendererLibraryHandle = LoadLibrary(rendererLibrary.c_str());
601  if (rendererLibraryHandle == nullptr) {
602  Console::println("Application::run(): Could not open renderer library");
603  glfwTerminate();
604  return EXITCODE_FAILURE;
605  }
606  //
607  Renderer* (*rendererCreateInstance)() = (Renderer*(*)())GetProcAddress(rendererLibraryHandle, "createInstance");
608  //
609  if (rendererCreateInstance == nullptr) {
610  Console::println("Application::run(): Could not find renderer library createInstance() entry point");
611  glfwTerminate();
612  return EXITCODE_FAILURE;
613  }
614  //
615  renderer = (Renderer*)rendererCreateInstance();
616  if (renderer == nullptr) {
617  Console::println("Application::run(): Could not create renderer");
618  glfwTerminate();
619  return EXITCODE_FAILURE;
620  }
621  #else
622  //
623  #if defined(__HAIKU__)
624  auto rendererLibraryHandle = dlopen(("lib/" + rendererLibrary).c_str(), RTLD_NOW);
625  #else
626  auto rendererLibraryHandle = dlopen(rendererLibrary.c_str(), RTLD_NOW);
627  #endif
628  if (rendererLibraryHandle == nullptr) {
629  Console::println("Application::run(): Could not open renderer library");
630  glfwTerminate();
631  return EXITCODE_FAILURE;
632  }
633  //
634  Renderer* (*rendererCreateInstance)() = (Renderer*(*)())dlsym(rendererLibraryHandle, "createInstance");
635  //
636  if (rendererCreateInstance == nullptr) {
637  Console::println("Application::run(): Could not find renderer library createInstance() entry point");
638  glfwTerminate();
639  return EXITCODE_FAILURE;
640  }
641  //
642  renderer = (Renderer*)rendererCreateInstance();
643  if (renderer == nullptr) {
644  Console::println("Application::run(): Could not create renderer");
645  glfwTerminate();
646  return EXITCODE_FAILURE;
647  }
648  #endif
649 
650  // window hints
651  if ((windowHints & WINDOW_HINT_NOTRESIZEABLE) == WINDOW_HINT_NOTRESIZEABLE) glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
652  if ((windowHints & WINDOW_HINT_NOTDECORATED) == WINDOW_HINT_NOTDECORATED) glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
653  if ((windowHints & WINDOW_HINT_INVISIBLE) == WINDOW_HINT_INVISIBLE) glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
654  if ((windowHints & WINDOW_HINT_MAXIMIZED) == WINDOW_HINT_MAXIMIZED) glfwWindowHint(GLFW_MAXIMIZED, GLFW_TRUE);
655 
656  //
657  for (auto i = 0; renderer->prepareWindowSystemRendererContext(i) == true; i++) {
658  glfwWindow = glfwCreateWindow(windowWidth, windowHeight, title.c_str(), NULL, NULL);
659  if (glfwWindow != nullptr) break;
660  }
661 
662  //
663  if (glfwWindow == nullptr) {
664  Console::println("glfwCreateWindow(): Could not create window");
665  glfwTerminate();
666  return EXITCODE_FAILURE;
667  }
668 
669  //
671  Console::println("glfwCreateWindow(): Could not initialize window system renderer context");
672  glfwTerminate();
673  return EXITCODE_FAILURE;
674  }
675 
676  //
678 
679  //
680  setIcon();
681 
682  //
683  glfwSetCharCallback(glfwWindow, Application::glfwOnChar);
684  glfwSetKeyCallback(glfwWindow, Application::glfwOnKey);
685  glfwSetCursorPosCallback(glfwWindow, Application::glfwOnMouseMoved);
686  glfwSetMouseButtonCallback(glfwWindow, Application::glfwOnMouseButton);
687  glfwSetJoystickCallback(Application::glfwOnJoystickConnect);
688  glfwSetScrollCallback(glfwWindow, Application::glfwOnMouseWheel);
689  glfwSetWindowSizeCallback(glfwWindow, Application::glfwOnWindowResize);
690  glfwSetWindowCloseCallback(glfwWindow, Application::glfwOnClose);
691  glfwSetDropCallback(glfwWindow, glfwOnDrop);
693  glfwGetWindowPos(glfwWindow, &windowXPosition, &windowYPosition);
694  glfwGetWindowSize(glfwWindow, &windowWidth, &windowHeight);
696  }
697  #if defined(__APPLE__)
698  // change working directory on MacOSX if started from app bundle
699  auto executablePathName = string(argv[0]);
700  if (executablePathName.find(".app/Contents/MacOS/") != string::npos) {
701  auto appBundleName = StringTools::substring(executablePathName, 0, executablePathName.rfind(".app") + string(".app").size());
702  auto workingPathName = StringTools::substring(appBundleName, 0, appBundleName.rfind('/'));
703  FileSystem::getStandardFileSystem()->changePath(workingPathName);
704  }
705  #endif
706 
707  //
708  try {
709  auto gameControllerDatabase = FileSystem::getInstance()->getContentAsString("resources/engine/misc", "gamecontrollerdb.txt");
710  glfwUpdateGamepadMappings(gameControllerDatabase.c_str());
711  } catch (Exception& exception) {
712  Console::println("An error occurred: " + string(exception.what()));
713  }
714 
715  //
716  for (auto joystickIdx = GLFW_JOYSTICK_1; joystickIdx <= GLFW_JOYSTICK_16; joystickIdx++) {
717  if (glfwJoystickPresent(joystickIdx) == true) glfwOnJoystickConnect(joystickIdx, GLFW_CONNECTED);
718  }
719 
720  //
721  while (glfwWindowShouldClose(glfwWindow) == false) {
722  displayInternal();
723  if (Engine::getRenderer()->getRendererType() != Renderer::RENDERERTYPE_VULKAN) glfwSwapBuffers(glfwWindow);
724  //
725  glfwPollEvents();
726  //
727  for (const auto gamepadIdx: connectedGamepads) updateGamepadInput(gamepadIdx);
728  for (const auto joystickIdx: connectedJoysticks) updateJoystickInput(joystickIdx);
729  }
730  glfwTerminate();
731  //
732  auto localExitCode = exitCode;
733  if (Application::application != nullptr) {
734  Console::println("Application::run(): Shutting down application");
736  Engine::shutdown();
737  Audio::shutdown();
738  delete Application::renderer;
739  Application::renderer = nullptr;
740  Console::shutdown();
741  //
743  Application::application = nullptr;
744  }
745  //
746  return localExitCode;
747 }
748 
750  auto logoFileName = StringTools::replace(StringTools::toLowerCase(executableFileName), ".exe", "") + "-icon.png";
751  if (FileSystem::getInstance()->exists("resources/platforms/icons/" + logoFileName) == false) logoFileName = "default-icon.png";
752  auto texture = TextureReader::read("resources/platforms/icons", logoFileName, false, false);
753  if (texture != nullptr) {
754  auto textureData = texture->getRGBTextureData();
755  auto textureWidth = texture->getTextureWidth();
756  auto textureHeight = texture->getTextureHeight();
757  auto textureBytePerPixel = texture->getRGBDepthBitsPerPixel() == 32?4:3;
758  vector<uint8_t> glfwPixels(textureWidth * textureHeight * 4);
759  for (auto y = 0; y < textureHeight; y++)
760  for (auto x = 0; x < textureWidth; x++) {
761  glfwPixels[y * textureWidth * 4 + x * 4 + 0] = textureData.get(y * textureWidth * textureBytePerPixel + x * textureBytePerPixel + 0);
762  glfwPixels[y * textureWidth * 4 + x * 4 + 1] = textureData.get(y * textureWidth * textureBytePerPixel + x * textureBytePerPixel + 1);
763  glfwPixels[y * textureWidth * 4 + x * 4 + 2] = textureData.get(y * textureWidth * textureBytePerPixel + x * textureBytePerPixel + 2);
764  glfwPixels[y * textureWidth * 4 + x * 4 + 3] = textureBytePerPixel == 3?255:textureData.get(y * textureWidth * textureBytePerPixel + x * textureBytePerPixel + 3);
765  }
766  GLFWimage glfwIcon;
767  glfwIcon.width = texture->getTextureWidth();
768  glfwIcon.height = texture->getTextureHeight();
769  glfwIcon.pixels = glfwPixels.data();
770  glfwSetWindowIcon(glfwWindow, 1, &glfwIcon);
771  texture->releaseReference();
772  }
773 }
774 
776  if (Application::application->initialized == false) {
781  }
782  int64_t timeNow = Time::getCurrentMillis();
783  int64_t timeFrame = 1000/Application::FPS;
784  if (Application::timeLast != -1L) {
785  int64_t timePassed = timeNow - timeLast;
786  if (limitFPS == true && timePassed < timeFrame) Thread::sleep(timeFrame - timePassed);
787  }
788  Application::timeLast = timeNow;
790 }
791 
792 void Application::reshapeInternal(int width, int height) {
793  if (Application::application->initialized == false) {
796  }
797  Application::application->reshape(width, height);
798 }
799 
800 void Application::glfwOnChar(GLFWwindow* window, unsigned int key) {
801  if (Application::inputEventHandler == nullptr) return;
802  double mouseX, mouseY;
803  glfwGetCursorPos(window, &mouseX, &mouseY);
804  Application::inputEventHandler->onChar(key, static_cast<int>(mouseX), static_cast<int>(mouseY));
805 }
806 
807 void Application::glfwOnKey(GLFWwindow* window, int key, int scanCode, int action, int mods) {
808  if (Application::inputEventHandler == nullptr) return;
809  double mouseX, mouseY;
810  glfwGetCursorPos(window, &mouseX, &mouseY);
811  // TODO: Use GLFW_MOD_CAPS_LOCK, which does not seem to be available with my version, need to update perhabs
812  if (key == GLFW_KEY_CAPS_LOCK) {
813  if (action == GLFW_PRESS) {
814  glfwCapsLockEnabled = glfwCapsLockEnabled == false?true:false;
815  }
816  }
817  if (action == GLFW_PRESS || action == GLFW_REPEAT) {
818  auto keyName = key == GLFW_KEY_SPACE?" ":glfwGetKeyName(key, scanCode);
820  keyName == nullptr?-1:((mods & GLFW_MOD_SHIFT) == 0 && glfwCapsLockEnabled == false?Character::toLowerCase(keyName[0]):keyName[0]),
821  key,
822  static_cast<int>(mouseX),
823  static_cast<int>(mouseY),
824  action == GLFW_REPEAT,
825  mods
826  );
827  } else
828  if (action == GLFW_RELEASE) {
829  auto keyName = key == GLFW_KEY_SPACE?" ":glfwGetKeyName(key, scanCode);
831  keyName == nullptr?-1:((mods & GLFW_MOD_SHIFT) == 0 && glfwCapsLockEnabled == false?Character::toLowerCase(keyName[0]):keyName[0]),
832  key,
833  (int)mouseX,
834  (int)mouseY
835  );
836  }
837 }
838 
839 void Application::glfwOnMouseMoved(GLFWwindow* window, double x, double y) {
840  if (Application::inputEventHandler == nullptr) return;
842  Application::inputEventHandler->onMouseDragged(static_cast<int>(x), static_cast<int>(y));
843  } else {
844  Application::inputEventHandler->onMouseMoved(static_cast<int>(x), static_cast<int>(y));
845  }
846 }
847 
848 void Application::glfwOnMouseButton(GLFWwindow* window, int button, int action, int mods) {
849  if (Application::inputEventHandler == nullptr) return;
850  double mouseX, mouseY;
851  glfwGetCursorPos(window, &mouseX, &mouseY);
852  Application::inputEventHandler->onMouseButton(button, action == GLFW_PRESS?MOUSE_BUTTON_DOWN:MOUSE_BUTTON_UP, static_cast<int>(mouseX), static_cast<int>(mouseY));
853  if (action == GLFW_PRESS) {
854  glfwMouseButtonDownFrames[button]++;
855  } else {
856  glfwMouseButtonDownFrames[button] = 0;
857  }
858  glfwMouseButtonLast = action == MOUSE_BUTTON_DOWN?button:-1;
859 }
860 
861 void Application::glfwOnMouseWheel(GLFWwindow* window, double x, double y) {
862  if (Application::inputEventHandler == nullptr) return;
863  double mouseX, mouseY;
864  glfwGetCursorPos(window, &mouseX, &mouseY);
865  if (x != 0.0) Application::inputEventHandler->onMouseWheel(0, static_cast<int>(x), static_cast<int>(mouseX), static_cast<int>(mouseY));
866  if (y != 0.0) Application::inputEventHandler->onMouseWheel(1, static_cast<int>(y), static_cast<int>(mouseX), static_cast<int>(mouseY));
867 }
868 
869 void Application::glfwOnWindowResize(GLFWwindow* window, int width, int height) {
870  Application::reshapeInternal(width, height);
871 }
872 
873 void Application::glfwOnClose(GLFWwindow* window) {
875 }
876 
877 void Application::glfwOnDrop(GLFWwindow* window, int count, const char** paths) {
878  Console::println("Application::glfwOnDrop(): " + to_string(count) + " items have been dropped");
879  vector<string> pathsVector;
880  for (auto i = 0; i < count; i++) {
881  Console::println("\t" + string(paths[i]));
882  pathsVector.push_back(paths[i]);
883  }
884  Application::application->onDrop(pathsVector);
885 }
886 
887 void Application::glfwOnJoystickConnect(int joystickIdx, int event) {
888  if (Application::application == nullptr) return;
889  //
890  if (event == GLFW_CONNECTED) {
891  if (glfwJoystickIsGamepad(joystickIdx) == true) {
892  Console::println("Application::glfwOnJoystickConnect(): connected gamepad with idx = " + to_string(joystickIdx) + ", name = " + glfwGetJoystickName(joystickIdx));
893  Application::application->connectedGamepads.insert(joystickIdx);
894  } else {
895  Console::println("Application::glfwOnJoystickConnect(): connected joystick with idx = " + to_string(joystickIdx) + ", name = " + glfwGetJoystickName(joystickIdx));
896  Application::application->connectedJoysticks.insert(joystickIdx);
897  }
898  } else if (event == GLFW_DISCONNECTED) {
899  Console::println("Application::glfwOnJoystickConnect(): disconnected joystick/gamepad with idx = " + to_string(joystickIdx));
900  Application::application->connectedGamepads.erase(joystickIdx);
901  Application::application->connectedJoysticks.erase(joystickIdx);
902  }
903 }
904 
905 void Application::updateJoystickInput(int joystickIdx) {
906  if (Application::inputEventHandler == nullptr) return;
907  // no joystick support yet, as I just dont have a joystick :DDD
908 }
909 
910 void Application::updateGamepadInput(int joystickIdx) {
911  if (Application::inputEventHandler == nullptr) return;
912  //
913  double mouseX, mouseY;
914  glfwGetCursorPos(glfwWindow, &mouseX, &mouseY);
915  GLFWgamepadstate gamepadState;
916  //
917  auto now = Time::getCurrentMillis();
918  auto handleButton = [&](int buttonIdx, int keyCode, int modifier = KEYBOARD_MODIFIER_NONE) -> void {
919  // press
920  if (gamepadState.buttons[buttonIdx] == GLFW_PRESS &&
921  joystickButtons[joystickIdx][buttonIdx] == -1LL) {
922  Application::inputEventHandler->onKeyDown(-1, keyCode, static_cast<int>(mouseX), static_cast<int>(mouseY), false, modifier);
923  joystickButtons[joystickIdx][buttonIdx] = now;
924  } else
925  // release
926  if (gamepadState.buttons[buttonIdx] == GLFW_RELEASE &&
927  joystickButtons[joystickIdx][buttonIdx] != -1LL) {
928  Application::inputEventHandler->onKeyUp(-1, keyCode, static_cast<int>(mouseX), static_cast<int>(mouseY));
929  joystickButtons[joystickIdx][buttonIdx] = -1LL;
930  } else
931  // repeat
932  if (gamepadState.buttons[buttonIdx] == GLFW_PRESS &&
933  joystickButtons[joystickIdx][buttonIdx] != 0LL &&
934  now - joystickButtons[joystickIdx][buttonIdx] >= JOYSTICK_BUTTON_TIME_REPEAT) {
935  Application::inputEventHandler->onKeyDown(-1, keyCode, static_cast<int>(mouseX), static_cast<int>(mouseY), true, modifier);
936  joystickButtons[joystickIdx][buttonIdx] = now;
937  }
938  };
939  //
940  if (glfwGetGamepadState(joystickIdx, &gamepadState) == true) {
941  // left
942  handleButton(GLFW_GAMEPAD_BUTTON_DPAD_LEFT, KEYBOARD_KEYCODE_LEFT);
943  // right
944  handleButton(GLFW_GAMEPAD_BUTTON_DPAD_RIGHT, KEYBOARD_KEYCODE_RIGHT);
945  // up
946  handleButton(GLFW_GAMEPAD_BUTTON_DPAD_UP, KEYBOARD_KEYCODE_UP);
947  // down
948  handleButton(GLFW_GAMEPAD_BUTTON_DPAD_DOWN, KEYBOARD_KEYCODE_DOWN);
949  // shift/tab
950  handleButton(GLFW_GAMEPAD_BUTTON_LEFT_BUMPER, KEYBOARD_KEYCODE_TAB, KEYBOARD_MODIFIER_SHIFT);
951  // tab
952  handleButton(GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER, KEYBOARD_KEYCODE_TAB);
953  // space
954  handleButton(GLFW_GAMEPAD_BUTTON_CROSS, KEYBOARD_KEYCODE_SPACE);
955  // back
956  handleButton(GLFW_GAMEPAD_BUTTON_CIRCLE, KEYBOARD_KEYCODE_ESCAPE);
957  }
958 }
959 
961 }
962 
963 void Application::onDrop(const vector<string>& paths) {
964 }
965 
static void glfwErrorCallback(int error, const char *description)
#define MOUSE_CURSOR_HAND
Definition: Application.h:14
#define MOUSE_CURSOR_ENABLED
Definition: Application.h:12
#define MOUSE_CURSOR_DISABLED
Definition: Application.h:11
#define KEYBOARD_MODIFIER_SHIFT
#define KEYBOARD_KEYCODE_LEFT
#define KEYBOARD_MODIFIER_NONE
#define KEYBOARD_KEYCODE_TAB
#define MOUSE_BUTTON_DOWN
#define KEYBOARD_KEYCODE_RIGHT
#define KEYBOARD_KEYCODE_SPACE
#define MOUSE_BUTTON_UP
#define KEYBOARD_KEYCODE_ESCAPE
#define KEYBOARD_KEYCODE_DOWN
#define KEYBOARD_KEYCODE_UP
Application base class, please make sure to allocate application on heap to have correct application ...
Definition: Application.h:41
static void setMousePosition(int x, int y)
Set mouse position.
static STATIC_DLL_IMPEXT Renderer * renderer
Definition: Application.h:343
static STATIC_DLL_IMPEXT int glfwMouseButtonLast
Definition: Application.h:362
static void glfwOnMouseWheel(GLFWwindow *window, double x, double y)
GLFW on key.
static constexpr int EXITCODE_FAILURE
Definition: Application.h:58
static void executeBackground(const string &command)
Execute a command in background.
static STATIC_DLL_IMPEXT Application * application
Definition: Application.h:344
static void swapBuffers()
Swap rendering buffers.
array< array< int64_t, 16 >, 16 > joystickButtons
Definition: Application.h:370
void setIcon()
Set application icon.
virtual void reshape(int width, int height)=0
Resize.
static void glfwOnMouseMoved(GLFWwindow *window, double x, double y)
GLFW on mouse moved.
int run(int argc, char **argv, const string &title, InputEventHandler *inputEventHandler=nullptr, int windowHints=WINDOW_HINT_NONE)
Run this application.
void setWindowXPosition(int windowXPosition)
Set window X position when initializing.
static void glfwOnClose(GLFWwindow *window)
GLFW on close.
static void glfwOnWindowResize(GLFWwindow *window, int width, int height)
GLFW on window resize.
static STATIC_DLL_IMPEXT array< unsigned int, 10 > glfwMouseButtonDownFrames
Definition: Application.h:361
void setWindowYPosition(int windowYPosition)
Set window Y position when initializing.
static string execute(const string &command)
Execute a command and wait until it finished running.
Definition: Application.cpp:98
void setClipboardContent(const string &content)
Set clipboard content.
static void glfwOnDrop(GLFWwindow *window, int count, const char **paths)
GLFW on drop.
static void displayInternal()
Display function.
static constexpr int64_t JOYSTICK_BUTTON_TIME_REPEAT
Definition: Application.h:60
static void glfwOnChar(GLFWwindow *window, unsigned int key)
GLFW on char.
static void reshapeInternal(int width, int height)
Reshape function.
void setWindowWidth(int windowWidth)
Set window width.
static STATIC_DLL_IMPEXT bool limitFPS
Definition: Application.h:356
void updateJoystickInput(int joystickIdx)
Update joystick input for given joystick index.
virtual ~Application()
Destructor.
static void cancelExit()
Cancels a users requested exit (ALT-F4 or X button)
virtual void onClose()
On close.
static STATIC_DLL_IMPEXT bool glfwCapsLockEnabled
Definition: Application.h:363
static void installExceptionHandler()
Windows only: Install exception handler that will print a stack trace if crashing.
virtual void display()=0
Display.
static STATIC_DLL_IMPEXT int64_t timeLast
Definition: Application.h:355
void setFullScreen(bool fullScreen)
Set full screen mode.
static constexpr int WINDOW_HINT_MAXIMIZED
Definition: Application.h:53
static constexpr int FPS
Definition: Application.h:55
static STATIC_DLL_IMPEXT InputEventHandler * inputEventHandler
Definition: Application.h:345
virtual void initialize()=0
Init.
static STATIC_DLL_IMPEXT int mouseCursor
Definition: Application.h:366
static constexpr int WINDOW_HINT_NOTDECORATED
Definition: Application.h:51
static constexpr int WINDOW_HINT_INVISIBLE
Definition: Application.h:52
static void exit(int exitCode)
Exits this application with given exit code.
void updateGamepadInput(int gamepadIdx)
Update gamepad input for given gamepad index.
unordered_set< int > connectedGamepads
Definition: Application.h:369
static STATIC_DLL_IMPEXT GLFWwindow * glfwWindow
Definition: Application.h:360
static constexpr int WINDOW_HINT_NOTRESIZEABLE
Definition: Application.h:50
unordered_set< int > connectedJoysticks
Definition: Application.h:368
static void setVSyncEnabled(bool vSync)
Set vsync enabled.
void setWindowHeight(int windowHeight)
Set window height.
virtual void dispose()=0
Disposes.
static void glfwOnJoystickConnect(int joystickIdx, int event)
GLFW on joystick connect/disconnect.
static void glfwOnKey(GLFWwindow *window, int key, int scanCode, int action, int mods)
GLFW on key.
static void openBrowser(const string &url)
Open browser with given url.
void setInputEventHandler(InputEventHandler *inputEventHandler)
Set input event handler.
static void setLocale(const string &locale)
Set locale.
Application()
Public constructor.
static void glfwOnMouseButton(GLFWwindow *window, int button, int action, int mods)
GLFW on key.
static void setMouseCursor(int mouseCursor)
Set mouse cursor.
static STATIC_DLL_IMPEXT GLFWcursor * glfwHandCursor
Definition: Application.h:364
virtual void onDrop(const vector< string > &paths)
On drop.
Application input event handler interface.
virtual void onMouseWheel(int button, int direction, int x, int y)=0
On mouse wheen.
virtual void onMouseButton(int button, int state, int x, int y)=0
On mouse moved.
virtual void onMouseMoved(int x, int y)=0
On mouse moved.
virtual void onChar(int key, int x, int y)=0
On char.
virtual void onKeyUp(int key, int keyCode, int x, int y)=0
On key up.
virtual void onMouseDragged(int x, int y)=0
On mouse dragged.
virtual void onKeyDown(int key, int keyCode, int x, int y, bool repeat, int modifiers)=0
On key down.
Interface to audio module.
Definition: Audio.h:29
Engine main class.
Definition: Engine.h:131
Texture entity.
Definition: Texture.h:24
virtual bool initializeWindowSystemRendererContext(GLFWwindow *glfwWindow)=0
Initialize window system renderer context.
virtual bool prepareWindowSystemRendererContext(int tryIdx)=0
Prepare window system renderer context.
File system singleton class.
Definition: FileSystem.h:17
Mutex implementation.
Definition: Mutex.h:19
Base class for threads.
Definition: Thread.h:20
Byte buffer class.
Definition: ByteBuffer.h:27
Character class.
Definition: Character.h:17
Console class.
Definition: Console.h:29
Integer to hex string conversion utility class.
Definition: Hex.h:16
Run time type information utility class.
Definition: RTTI.h:14
String tokenizer class.
void tokenize(const string &str, const string &delimiters, bool emptyTokens=false)
Tokenize.
String tools class.
Definition: StringTools.h:22
Time utility class.
Definition: Time.h:20
std::exception Exception
Exception base class.
Definition: Exception.h:18