32#include <GLFW/glfw3.h>
39#include <mach-o/dyld.h>
59 const char* home = std::getenv(
"HOME");
60 const std::string logPath =
61 (home !=
nullptr) ? std::string(home) +
"/Library/Logs/MetaImGUI/metaimgui.log" :
"metaimgui.log";
65 const char* localAppData = std::getenv(
"LOCALAPPDATA");
66 const std::string logPath = (localAppData !=
nullptr)
67 ? std::string(localAppData) +
"\\MetaImGUI\\logs\\metaimgui.log"
68 :
"logs\\metaimgui.log";
72 const char* home = std::getenv(
"HOME");
73 const std::string logPath =
74 (home !=
nullptr) ? std::string(home) +
"/.local/share/MetaImGUI/logs/metaimgui.log" :
"logs/metaimgui.log";
77 LOG_INFO(
"Initializing MetaImGUI v{}", Version::VERSION);
81 curl_global_init(CURL_GLOBAL_DEFAULT);
84 m_configManager = std::make_unique<ConfigManager>();
85 if (m_configManager->Load()) {
86 LOG_INFO(
"Configuration loaded successfully");
88 LOG_INFO(
"Using default configuration");
94 std::vector<std::string> translationPaths = {
95 "resources/translations/translations.json",
100 const char* appdir = std::getenv(
"METAIMGUI_APPDIR");
101 if (appdir !=
nullptr) {
102 const std::string appdir_path =
103 std::string(appdir) +
"/usr/share/MetaImGUI/resources/translations/translations.json";
104 translationPaths.insert(translationPaths.begin(), appdir_path);
110 char executablePath[1024];
111 uint32_t size =
sizeof(executablePath);
112 if (_NSGetExecutablePath(executablePath, &size) == 0) {
114 std::string exePath(executablePath);
116 size_t macosPos = exePath.rfind(
"/MacOS/");
117 if (macosPos != std::string::npos) {
118 std::string bundleResourcePath =
119 exePath.substr(0, macosPos) +
"/Resources/resources/translations/translations.json";
120 translationPaths.insert(translationPaths.begin(), bundleResourcePath);
121 LOG_DEBUG(
"macOS bundle resource path: {}", bundleResourcePath);
125 translationPaths.emplace_back(
"../Resources/resources/translations/translations.json");
126 translationPaths.emplace_back(
"MetaImGUI.app/Contents/Resources/resources/translations/translations.json");
130 translationPaths.emplace_back(
131 "../share/MetaImGUI/resources/translations/translations.json");
132 translationPaths.emplace_back(
133 "/usr/share/MetaImGUI/resources/translations/translations.json");
134 translationPaths.emplace_back(
135 "/usr/local/share/MetaImGUI/resources/translations/translations.json");
137 bool translationsLoaded =
false;
138 for (
const auto& path : translationPaths) {
140 translationsLoaded =
true;
145 if (!translationsLoaded) {
146 LOG_ERROR(
"========================================");
147 LOG_ERROR(
"CRITICAL: Failed to load translations!");
148 LOG_ERROR(
"UI will show translation keys instead of actual text");
149 LOG_ERROR(
"Tried the following locations:");
150 for (
const auto& path : translationPaths) {
153 LOG_ERROR(
"This is a PACKAGING ERROR - file is missing from bundle");
154 LOG_ERROR(
"========================================");
157 const std::string language = m_configManager->GetString(
"language").value_or(
"en");
161 auto windowSize = m_configManager->GetWindowSize();
162 const int width = windowSize ? windowSize->first : DEFAULT_WIDTH;
163 const int height = windowSize ? windowSize->second : DEFAULT_HEIGHT;
165 m_windowManager = std::make_unique<WindowManager>(WINDOW_TITLE, width, height);
166 if (!m_windowManager->Initialize()) {
167 LOG_ERROR(
"Failed to initialize window manager");
170 LOG_INFO(
"Window manager initialized");
173 m_windowManager->SetFramebufferSizeCallback(
174 [
this](
int width,
int height) { this->OnFramebufferSizeChanged(width, height); });
175 m_windowManager->SetKeyCallback(
176 [
this](
int key,
int scancode,
int action,
int mods) { this->OnKeyPressed(key, scancode, action, mods); });
177 m_windowManager->SetWindowCloseCallback([
this]() { this->OnWindowCloseRequested(); });
178 m_windowManager->SetContextLossCallback([
this]() {
return this->OnContextLoss(); });
181 m_uiRenderer = std::make_unique<UIRenderer>();
182 if (!m_uiRenderer->Initialize(m_windowManager->GetNativeWindow())) {
183 LOG_ERROR(
"Failed to initialize UI renderer");
186 LOG_INFO(
"UI renderer initialized");
189 m_dialogManager = std::make_unique<DialogManager>();
190 LOG_INFO(
"Dialog manager initialized");
193 m_updateChecker = std::make_unique<UpdateChecker>(
"andynicholson",
"MetaImGUI");
194 LOG_INFO(
"Update checker initialized");
197 m_issTracker = std::make_unique<ISSTracker>();
198 LOG_INFO(
"ISS tracker initialized");
213 m_initialized =
true;
214 LOG_INFO(
"Application initialized successfully");
226 if (!m_initialized) {
230 LOG_INFO(
"Shutting down application...");
233 if (m_configManager && m_windowManager) {
237 m_windowManager->GetWindowSize(width, height);
238 m_configManager->SetWindowSize(width, height);
239 LOG_INFO(
"Saving window size: {}x{}", width, height);
244 if (m_configManager->Save()) {
245 LOG_INFO(
"Configuration saved successfully");
251 m_issTracker->StopTracking();
253 m_issTracker.reset();
254 m_updateChecker.reset();
255 m_dialogManager.reset();
256 m_uiRenderer.reset();
257 m_windowManager.reset();
258 m_configManager.reset();
261 curl_global_cleanup();
263 m_initialized =
false;
264 LOG_INFO(
"Application shut down successfully");
271 return m_windowManager && m_windowManager->ShouldClose();
274void Application::ProcessInput() {
275 if (m_windowManager) {
276 m_windowManager->PollEvents();
280void Application::Render() {
281 if (!m_windowManager || !m_uiRenderer) {
288 m_lastFrameTime = ImGui::GetIO().Framerate;
290 m_windowManager->BeginFrame();
291 m_uiRenderer->BeginFrame();
293 RenderMainViewport();
294 RenderFloatingWindows();
297 m_uiRenderer->EndFrame();
298 m_windowManager->EndFrame();
301void Application::PollAsyncResults() {
304 const std::lock_guard<std::mutex> lock(m_updateResultMutex);
305 if (!m_pendingUpdateResult) {
309 m_updateCheckInProgress =
false;
310 m_latestUpdateInfo = std::move(m_pendingUpdateResult);
314 switch (m_latestUpdateInfo->status) {
316 m_showUpdateNotification =
true;
317 m_statusMessage =
"Update available: v" + m_latestUpdateInfo->latestVersion;
318 LOG_INFO(
"Update available: v{} (current: v{})", m_latestUpdateInfo->latestVersion,
319 m_latestUpdateInfo->currentVersion);
322 m_statusMessage =
"Ready";
323 LOG_INFO(
"No updates available (current version: v{})", m_latestUpdateInfo->currentVersion);
326 m_statusMessage =
"Update check rate-limited; retry later";
329 m_statusMessage =
"Update check failed (network)";
332 m_statusMessage =
"Update check failed (response)";
335 m_statusMessage =
"Update check cancelled";
338 m_statusMessage =
"Ready";
343void Application::RenderMainViewport() {
344 ImGuiViewport* viewport = ImGui::GetMainViewport();
345 ImGui::SetNextWindowPos(viewport->Pos);
346 ImGui::SetNextWindowSize(viewport->Size);
348 constexpr ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
349 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings |
350 ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoBringToFrontOnFocus;
352 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
353 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
354 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
356 if (ImGui::Begin(
"MetaImGUI Main",
nullptr, window_flags)) {
357 m_uiRenderer->RenderMenuBar(m_uiEvents, m_showDemoWindow, m_showISSTracker);
358 m_uiRenderer->RenderMainWindow(m_uiEvents);
359 m_uiRenderer->RenderStatusBar(m_statusMessage, m_lastFrameTime, Version::VERSION, m_updateCheckInProgress);
363 ImGui::PopStyleVar(3);
366void Application::RenderFloatingWindows() {
367 if (m_showAboutWindow) {
368 m_uiRenderer->RenderAboutWindow(m_showAboutWindow);
370 if (m_showDemoWindow) {
371 m_uiRenderer->ShowDemoWindow(m_showDemoWindow);
373 if (m_showUpdateNotification) {
374 m_uiRenderer->RenderUpdateNotification(m_showUpdateNotification, m_latestUpdateInfo.get());
376 if (m_showISSTracker) {
377 m_uiRenderer->RenderISSTrackerWindow(m_showISSTracker, m_issTracker.get());
381void Application::RenderDialogs() {
382 if (m_dialogManager) {
383 m_dialogManager->Render();
389void Application::OnWindowCloseRequested() {
391 if (m_windowManager) {
392 m_windowManager->CancelClose();
397void Application::OnExitRequested() {
402void Application::StartExitFlow() {
403 if (m_exitDialogActive || !m_dialogManager) {
406 m_exitDialogActive =
true;
409 std::string title = loc.Tr(
"exit.title");
410 std::string message = loc.Tr(
"exit.message");
415 [](
Application* self, std::string title, std::string message) -> Task {
416 const bool confirmed =
co_await self->m_dialogManager->AwaitConfirmation(std::move(title), std::move(message));
417 self->m_exitDialogActive =
false;
418 if (self->m_windowManager) {
420 self->m_windowManager->RequestClose();
422 self->m_windowManager->CancelClose();
425 }(
this, std::move(title), std::move(message));
428void Application::OnToggleDemoWindow() {
429 m_showDemoWindow = !m_showDemoWindow;
432void Application::OnCheckUpdatesRequested() {
436void Application::OnShowAboutRequested() {
437 m_showAboutWindow =
true;
440void Application::OnShowInputDialogRequested() {
441 if (m_dialogManager) {
443 m_dialogManager->ShowInputDialog(loc.Tr(
"input_dialog.title"), loc.Tr(
"input_dialog.prompt"),
"",
444 [
this](
const std::string& result) {
445 auto& loc = Localization::Instance();
446 if (!result.empty()) {
447 m_statusMessage = loc.Tr(
"status.input_received") +
" " + result;
448 LOG_INFO(
"User input: {}", result);
450 m_statusMessage = loc.Tr(
"status.input_cancelled");
456void Application::OnToggleISSTracker() {
457 m_showISSTracker = !m_showISSTracker;
462void Application::OnFramebufferSizeChanged([[maybe_unused]]
int width, [[maybe_unused]]
int height) {
467void Application::OnKeyPressed(
int key, [[maybe_unused]]
int scancode,
int action,
int mods) {
468 if (action == GLFW_PRESS) {
470 case GLFW_KEY_ESCAPE:
474 if ((mods & GLFW_MOD_CONTROL) != 0) {
475 OnShowAboutRequested();
480 if ((mods & GLFW_MOD_SHIFT) != 0) {
481 LOG_WARNING(
"DEBUG: User triggered context loss simulation via Shift+F9");
482 if (OnContextLoss()) {
483 m_statusMessage =
"DEBUG: Context recovery successful";
485 m_statusMessage =
"DEBUG: Context recovery failed";
498void Application::CheckForUpdates() {
499 if (!m_updateChecker || m_updateCheckInProgress) {
503 m_updateCheckInProgress =
true;
504 m_statusMessage =
"Checking for updates...";
507 m_updateChecker->CheckForUpdatesAsync([
this](
const UpdateInfo& info) { this->OnUpdateCheckComplete(info); });
510void Application::OnUpdateCheckComplete(
const UpdateInfo& updateInfo) {
515 const std::lock_guard<std::mutex> lock(m_updateResultMutex);
516 m_pendingUpdateResult = std::make_unique<UpdateInfo>(updateInfo);
519bool Application::OnContextLoss() {
520 LOG_WARNING(
"Application handling context loss - attempting to recreate UI renderer");
524 m_uiRenderer->Shutdown();
528 if (!m_uiRenderer->Initialize(m_windowManager->GetNativeWindow())) {
529 LOG_ERROR(
"Failed to reinitialize UI renderer after context loss");
530 m_statusMessage =
"ERROR: Failed to recover from context loss";
534 LOG_INFO(
"UI renderer successfully reinitialized after context loss");
535 m_statusMessage =
"Recovered from display context loss";