Files
Xenia-Canary/src/xenia/ui/windowed_app_context_gtk.cc

107 lines
4.0 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/ui/windowed_app_context_gtk.h"
#include <gdk/gdk.h>
#include <glib.h>
#include <gtk/gtk.h>
namespace xe {
namespace ui {
GTKWindowedAppContext::~GTKWindowedAppContext() {
// Remove the idle sources as their data pointer (to this context) is now
// outdated.
if (quit_idle_pending_) {
g_source_remove(quit_idle_pending_);
}
if (pending_functions_idle_pending_) {
g_source_remove(pending_functions_idle_pending_);
}
}
void GTKWindowedAppContext::NotifyUILoopOfPendingFunctions() {
std::lock_guard<std::mutex> pending_functions_idle_pending_lock(
pending_functions_idle_pending_mutex_);
if (!pending_functions_idle_pending_) {
pending_functions_idle_pending_ =
gdk_threads_add_idle(PendingFunctionsSourceFunc, this);
}
}
void GTKWindowedAppContext::PlatformQuitFromUIThread() {
if (quit_idle_pending_ || !gtk_main_level()) {
return;
}
gtk_main_quit();
// Quit from all loops in the context, current inner, upcoming inner (this is
// why the idle function is added even at the main level of 1), until we can
// return to the tail of RunMainGTKLoop.
quit_idle_pending_ = gdk_threads_add_idle(QuitSourceFunc, this);
}
void GTKWindowedAppContext::RunMainGTKLoop() {
// For safety, in case the quit request somehow happened before the loop.
if (HasQuitFromUIThread()) {
return;
}
assert_zero(gtk_main_level());
gtk_main();
// Something else - not QuitFromUIThread - might have done gtk_main_quit for
// the outer loop. If it has exited for some reason, let the context know, so
// pending function won't be added pointlessly.
QuitFromUIThread();
// Don't interfere with main loops of other contexts that will possibly be
// executed in this thread later (if this is not the only app that will be
// executed in this process, for example, or if some WindowedAppContext-less
// dialog is opened after the windowed app has quit) - cancel the scheduled
// quit chain as we have quit already.
if (quit_idle_pending_) {
g_source_remove(quit_idle_pending_);
quit_idle_pending_ = 0;
}
}
gboolean GTKWindowedAppContext::PendingFunctionsSourceFunc(gpointer data) {
auto& app_context = *static_cast<GTKWindowedAppContext*>(data);
// Allow new pending function notifications to be made, and stop tracking the
// lifetime of the idle source that is already being executed and thus
// removed. This must be done before executing the functions (if done after,
// notifications may be missed if they happen between the execution of the
// functions and the reset of pending_functions_idle_pending_).
{
std::lock_guard<std::mutex> pending_functions_idle_pending_lock(
app_context.pending_functions_idle_pending_mutex_);
app_context.pending_functions_idle_pending_ = 0;
}
app_context.ExecutePendingFunctionsFromUIThread();
return G_SOURCE_REMOVE;
}
gboolean GTKWindowedAppContext::QuitSourceFunc(gpointer data) {
auto app_context = static_cast<GTKWindowedAppContext*>(data);
guint main_level = gtk_main_level();
assert_not_zero(main_level);
if (!main_level) {
app_context->quit_idle_pending_ = 0;
return G_SOURCE_REMOVE;
}
gtk_main_quit();
// Quit from all loops in the context, current inner, upcoming inner (this is
// why the idle function is added even at the main level of 1), until we can
// return to the tail of RunMainGTKLoop.
app_context->quit_idle_pending_ =
gdk_threads_add_idle(QuitSourceFunc, app_context);
return G_SOURCE_REMOVE;
}
} // namespace ui
} // namespace xe