auto Program::updateMessage() -> void {
  // This function is called every iteration of the GUI run loop. Acquiring the emulator mutex would incur a severe
  // responsiveness penalty, so use a dedicated mutex for message passing.
  lock_guard<recursive_mutex> messageLock(_messageMutex);
  if(chrono::millisecond() - message.timestamp >= 2000) {
    message = {};
    if(!messages.empty()) { message = messages.front(); messages.erase(messages.begin()); }
  }

  if(message.text.length() > 0) {
    presentation.statusLeft.setText(message.text);
  } else if(settings.debugServer.enabled) {
    presentation.statusLeft.setText(nall::GDB::server.getStatusText(settings.debugServer.port, settings.debugServer.useIPv4));
  } else if(configuration) {
    presentation.statusLeft.setText(configuration);
  } else {
    presentation.statusLeft.setText();
  }

  if(vblanksPerSecond > 0 && !paused) {
    presentation.statusRight.setText({ vblanksPerSecond.load(), " VPS" });
  }

  if(!emulator) {
    presentation.statusRight.setText("Unloaded");
  }

  if (message.text == "") {
    if (emulator && keyboardCaptured) {
      presentation.statusLeft.setText("Keyboard capture is active");
    }
  }

  
  bool defocused = settings.input.defocus == "Pause" && !ruby::video.fullScreen() && !presentation.focused();
  if(emulator && defocused) message.text = "Paused";
}

auto Program::showMessage(const string& text) -> void {
  lock_guard<recursive_mutex> messageLock(_messageMutex);
  messages.push_back({chrono::millisecond(), text});
  printf("%s\n", (const char*)text);
}

auto Program::error(const string& text) -> void {
  if(kiosk) {
    fprintf(stderr, "error: %s\n", text.data());
    pendingKioskExit = true;
  } else {
    MessageDialog().setTitle("Error").setText(text).setAlignment(presentation).error();
  }
}
