From 7846a5637b38d72bbffd4192acf9636af0b9f920 Mon Sep 17 00:00:00 2001 From: mkriskovic Date: Sun, 14 Jun 2026 00:18:54 +0200 Subject: [PATCH 1/7] added lua support for ubuntu --- desmume/src/frontend/posix/gtk/main.cpp | 32 ++- desmume/src/frontend/posix/gtk/menu.ui | 9 + desmume/src/frontend/posix/gtk/meson.build | 4 + .../posix/gtk/tools/luaScriptConsole.cpp | 208 ++++++++++++++++++ .../posix/gtk/tools/luaScriptConsole.h | 32 +++ desmume/src/frontend/posix/meson.build | 21 +- 6 files changed, 296 insertions(+), 10 deletions(-) create mode 100644 desmume/src/frontend/posix/gtk/tools/luaScriptConsole.cpp create mode 100644 desmume/src/frontend/posix/gtk/tools/luaScriptConsole.h diff --git a/desmume/src/frontend/posix/gtk/main.cpp b/desmume/src/frontend/posix/gtk/main.cpp index 553d9f6dc..d32f57330 100644 --- a/desmume/src/frontend/posix/gtk/main.cpp +++ b/desmume/src/frontend/posix/gtk/main.cpp @@ -74,6 +74,11 @@ #include "gdbstub.h" #endif +#ifdef HAVE_LUA + #include "tools/luaScriptConsole.h" + #include "lua-engine.h" +#endif + #if defined(ENABLE_OPENGL_STANDARD) || defined(ENABLE_OPENGL_ES) #if defined(ENABLE_OPENGL_ES) #include "OGLRender_ES3.h" @@ -235,6 +240,10 @@ static void JITMaxBlockSizeChanged(GtkAdjustment* adj,void *); #endif static void GraphicsSettingsDialog(GSimpleAction *action, GVariant *parameter, gpointer user_data); +#ifdef HAVE_LUA +static void AddLuaScript(GSimpleAction *action, GVariant *parameter, gpointer user_data); +#endif + static const GActionEntry app_entries[] = { // File { "open", OpenNdsDialog }, @@ -307,7 +316,10 @@ static const GActionEntry app_entries[] = { { "editjoyctrls", Edit_Joystick_Controls }, // Tools - // Populated in desmume_gtk_menu_tools(). + // dTool entries are populated dynamically in desmume_gtk_menu_tools(). +#ifdef HAVE_LUA + { "addluascript", AddLuaScript }, +#endif // Help { "about", About }, @@ -579,6 +591,13 @@ struct nds_screen_t nds_screen; static guint regMainLoop = 0; +#ifdef HAVE_LUA +static void AddLuaScript(GSimpleAction *action, GVariant *parameter, gpointer user_data) +{ + lua_script_open_console(GTK_WINDOW(pWindow)); +} +#endif + static inline void UpdateStatusBar (const char *message) { gint pStatusBar_Ctx; @@ -2946,8 +2965,19 @@ gboolean EmuLoop(gpointer data) touchpad.click = 0; } +#ifdef HAVE_LUA + NDS_beginProcessingInput(); + CallRegisteredLuaFunctions(LUACALL_BEFOREEMULATION); + NDS_endProcessingInput(); +#endif + desmume_cycle(); /* Emule ! */ +#ifdef HAVE_LUA + CallRegisteredLuaFunctions(LUACALL_AFTEREMULATION); + CallRegisteredLuaFunctions(LUACALL_AFTEREMULATIONGUI); +#endif + _updateDTools(); if (!config.fpslimiter || keys_latch & KEYMASK_(KEY_BOOST - 1)) { diff --git a/desmume/src/frontend/posix/gtk/menu.ui b/desmume/src/frontend/posix/gtk/menu.ui index b32bd4a89..0b1d505cb 100644 --- a/desmume/src/frontend/posix/gtk/menu.ui +++ b/desmume/src/frontend/posix/gtk/menu.ui @@ -763,6 +763,15 @@ _IO regs view app.ioregs + + _Lua Scripts +
+ + _Add new script... + app.addluascript + +
+
View _Layers
diff --git a/desmume/src/frontend/posix/gtk/meson.build b/desmume/src/frontend/posix/gtk/meson.build index 4f60398ce..bb5621b00 100644 --- a/desmume/src/frontend/posix/gtk/meson.build +++ b/desmume/src/frontend/posix/gtk/meson.build @@ -25,6 +25,10 @@ desmume_src = [ gresource, ] +if dep_lua.found() + desmume_src += ['tools/luaScriptConsole.cpp'] +endif + if get_option('glx') and dep_gl.found() desmume_src += [ '../shared/glx_3Demu.cpp', diff --git a/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.cpp b/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.cpp new file mode 100644 index 000000000..5cbb2df4d --- /dev/null +++ b/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.cpp @@ -0,0 +1,208 @@ +/* luaScriptConsole.cpp - this file is part of DeSmuME + * + * Copyright (C) 2006-2025 DeSmuME Team + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_LUA + +#include +#include + +#include "lua-engine.h" + +struct LuaConsole { + int uid; + GtkWidget *window; + GtkWidget *path_entry; + GtkWidget *run_btn; + GtkWidget *stop_btn; + GtkWidget *output_view; + GtkTextBuffer *output_buf; +}; + +static std::map g_consoles; +static int g_next_uid = 1; + +static void console_append(LuaConsole *con, const char *str) +{ + GtkTextIter end; + gtk_text_buffer_get_end_iter(con->output_buf, &end); + gtk_text_buffer_insert(con->output_buf, &end, str, -1); + + GtkTextMark *mark = gtk_text_buffer_get_mark(con->output_buf, "insert"); + gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(con->output_view), mark); +} + +static void lua_print_cb(int uid, const char *str) +{ + auto it = g_consoles.find(uid); + if (it != g_consoles.end()) + console_append(it->second, str); +} + +static void lua_onstart_cb(int uid) +{ + auto it = g_consoles.find(uid); + if (it == g_consoles.end()) return; + LuaConsole *con = it->second; + + gtk_text_buffer_set_text(con->output_buf, "", -1); + gtk_button_set_label(GTK_BUTTON(con->run_btn), "Restart"); + gtk_widget_set_sensitive(con->run_btn, TRUE); + gtk_widget_set_sensitive(con->stop_btn, TRUE); +} + +static void lua_onstop_cb(int uid, bool statusOK) +{ + auto it = g_consoles.find(uid); + if (it == g_consoles.end()) return; + LuaConsole *con = it->second; + + gtk_button_set_label(GTK_BUTTON(con->run_btn), "Run"); + gtk_widget_set_sensitive(con->stop_btn, FALSE); + + if (!statusOK) + console_append(con, "Script stopped with error.\n"); + else + console_append(con, "Script finished.\n"); +} + +static void on_browse_clicked(GtkButton *btn, gpointer user_data) +{ + LuaConsole *con = (LuaConsole*)user_data; + + GtkFileFilter *filter_lua = gtk_file_filter_new(); + gtk_file_filter_set_name(filter_lua, "Lua scripts (*.lua)"); + gtk_file_filter_add_pattern(filter_lua, "*.lua"); + + GtkFileFilter *filter_all = gtk_file_filter_new(); + gtk_file_filter_set_name(filter_all, "All files"); + gtk_file_filter_add_pattern(filter_all, "*"); + + GtkFileChooserNative *fc = gtk_file_chooser_native_new( + "Open Lua Script", + GTK_WINDOW(con->window), + GTK_FILE_CHOOSER_ACTION_OPEN, + "_Open", "_Cancel"); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(fc), filter_lua); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(fc), filter_all); + + if (gtk_native_dialog_run(GTK_NATIVE_DIALOG(fc)) == GTK_RESPONSE_ACCEPT) { + GFile *file = gtk_file_chooser_get_file(GTK_FILE_CHOOSER(fc)); + gchar *path = g_file_get_path(file); + gtk_entry_set_text(GTK_ENTRY(con->path_entry), path); + g_free(path); + g_object_unref(file); + } + + g_object_unref(fc); +} + +static void on_run_clicked(GtkButton *btn, gpointer user_data) +{ + LuaConsole *con = (LuaConsole*)user_data; + const gchar *path = gtk_entry_get_text(GTK_ENTRY(con->path_entry)); + if (!path || path[0] == '\0') return; + + StopLuaScript(con->uid); + RunLuaScriptFile(con->uid, path); +} + +static void on_stop_clicked(GtkButton *btn, gpointer user_data) +{ + LuaConsole *con = (LuaConsole*)user_data; + StopLuaScript(con->uid); +} + +static gboolean on_window_delete(GtkWidget *widget, GdkEvent *event, gpointer user_data) +{ + LuaConsole *con = (LuaConsole*)user_data; + StopLuaScript(con->uid); + CloseLuaContext(con->uid); + g_consoles.erase(con->uid); + delete con; + return FALSE; +} + +void lua_script_open_console(GtkWindow *parent) +{ + LuaConsole *con = new LuaConsole(); + con->uid = g_next_uid++; + + con->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(con->window), "Lua Script Console"); + gtk_window_set_default_size(GTK_WINDOW(con->window), 480, 320); + if (parent) + gtk_window_set_transient_for(GTK_WINDOW(con->window), parent); + + GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 6); + gtk_container_add(GTK_CONTAINER(con->window), vbox); + + /* Path row */ + GtkWidget *path_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); + con->path_entry = gtk_entry_new(); + gtk_widget_set_hexpand(con->path_entry, TRUE); + GtkWidget *browse_btn = gtk_button_new_with_label("Browse..."); + gtk_box_pack_start(GTK_BOX(path_row), con->path_entry, TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(path_row), browse_btn, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), path_row, FALSE, FALSE, 0); + + /* Button row */ + GtkWidget *btn_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); + con->run_btn = gtk_button_new_with_label("Run"); + con->stop_btn = gtk_button_new_with_label("Stop"); + gtk_widget_set_sensitive(con->stop_btn, FALSE); + gtk_box_pack_start(GTK_BOX(btn_row), con->run_btn, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(btn_row), con->stop_btn, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), btn_row, FALSE, FALSE, 0); + + /* Output console */ + GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + con->output_view = gtk_text_view_new(); + gtk_text_view_set_editable(GTK_TEXT_VIEW(con->output_view), FALSE); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(con->output_view), GTK_WRAP_WORD_CHAR); + + GtkCssProvider *css = gtk_css_provider_new(); + gtk_css_provider_load_from_data(css, "textview { font-family: monospace; }", -1, NULL); + gtk_style_context_add_provider( + gtk_widget_get_style_context(con->output_view), + GTK_STYLE_PROVIDER(css), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_object_unref(css); + + con->output_buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(con->output_view)); + gtk_container_add(GTK_CONTAINER(scroll), con->output_view); + gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0); + + /* Connect signals */ + g_signal_connect(browse_btn, "clicked", G_CALLBACK(on_browse_clicked), con); + g_signal_connect(con->run_btn, "clicked", G_CALLBACK(on_run_clicked), con); + g_signal_connect(con->stop_btn, "clicked", G_CALLBACK(on_stop_clicked), con); + g_signal_connect(con->window, "delete-event", G_CALLBACK(on_window_delete), con); + + /* Register Lua context before showing the window */ + OpenLuaContext(con->uid, lua_print_cb, lua_onstart_cb, lua_onstop_cb); + g_consoles[con->uid] = con; + + gtk_widget_show_all(con->window); +} + +#endif /* HAVE_LUA */ diff --git a/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.h b/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.h new file mode 100644 index 000000000..55bd0a598 --- /dev/null +++ b/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.h @@ -0,0 +1,32 @@ +/* luaScriptConsole.h - this file is part of DeSmuME + * + * Copyright (C) 2006-2025 DeSmuME Team + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __LUASCRIPTCONSOLE_H__ +#define __LUASCRIPTCONSOLE_H__ + +#ifdef HAVE_LUA + +#include + +void lua_script_open_console(GtkWindow *parent); + +#endif /* HAVE_LUA */ + +#endif /* __LUASCRIPTCONSOLE_H__ */ diff --git a/desmume/src/frontend/posix/meson.build b/desmume/src/frontend/posix/meson.build index dda45fc1c..4451108bc 100644 --- a/desmume/src/frontend/posix/meson.build +++ b/desmume/src/frontend/posix/meson.build @@ -24,8 +24,11 @@ dep_agg = dependency('libagg', required: false) dep_fontconfig = dependency('fontconfig', required: false) dep_egl = dependency('egl', required: false) -# XXX: something wrong with this one. -#dep_lua = dependency('lua-5.1', required: false) +# Ubuntu/Debian: lua5.1 Fedora/Arch: lua-5.1 some: lua +dep_lua = dependency('lua5.1', required: false) +if not dep_lua.found() + dep_lua = dependency('lua-5.1', required: false) +endif if get_option('wifi') add_global_arguments('-DEXPERIMENTAL_WIFI_COMM', language: ['c', 'cpp']) @@ -295,13 +298,13 @@ if dep_agg.found() ] endif -# TODO: fix Lua on ¬Windows. -#if dep_lua.found() -# dependencies += dep_lua -# libdesmume_src += [ -# '../../lua-engine.cpp', -# ] -#endif +if dep_lua.found() + dependencies += dep_lua + add_global_arguments('-DHAVE_LUA', language: ['c', 'cpp']) + libdesmume_src += [ + '../../lua-engine.cpp', + ] +endif if get_option('gdb-stub') add_global_arguments('-DGDB_STUB', language: ['c', 'cpp']) From 8eb62f4756d33a61baf2aa2b635c29240298f436 Mon Sep 17 00:00:00 2001 From: mkriskovic Date: Sat, 20 Jun 2026 13:14:57 +0200 Subject: [PATCH 2/7] syncing GUI with windows --- desmume/src/frontend/posix/gtk/menu.ui | 4 +- .../posix/gtk/tools/luaScriptConsole.cpp | 408 +++++++++++++++--- 2 files changed, 357 insertions(+), 55 deletions(-) diff --git a/desmume/src/frontend/posix/gtk/menu.ui b/desmume/src/frontend/posix/gtk/menu.ui index 0b1d505cb..5c231e38a 100644 --- a/desmume/src/frontend/posix/gtk/menu.ui +++ b/desmume/src/frontend/posix/gtk/menu.ui @@ -764,10 +764,10 @@ app.ioregs - _Lua Scripts + _Lua Scripting
- _Add new script... + _New Lua Script Window... app.addluascript
diff --git a/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.cpp b/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.cpp index 5cbb2df4d..43fb6e817 100644 --- a/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.cpp +++ b/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.cpp @@ -21,25 +21,168 @@ #ifdef HAVE_LUA #include +#include #include +#include +#include +#include +#include +#include +#include #include "lua-engine.h" +// Match the Windows console character limit +#define MAX_CONSOLE_CHARS 250000 + struct LuaConsole { int uid; + std::string filename; + bool started; + bool closeOnStop; + GtkWidget *window; GtkWidget *path_entry; + GtkWidget *browse_btn; + GtkWidget *edit_btn; GtkWidget *run_btn; GtkWidget *stop_btn; + GtkWidget *stdout_check; GtkWidget *output_view; GtkTextBuffer *output_buf; + + volatile bool watcher_running; + GThread *watcher_thread; + + LuaConsole() + : uid(0), started(false), closeOnStop(false), + watcher_running(false), watcher_thread(nullptr) {} }; static std::map g_consoles; static int g_next_uid = 1; +// ──────────────────────────────────────── +// File watcher (inotify — mirrors Windows FindFirstChangeNotification) +// ──────────────────────────────────────── + +struct WatcherArgs { + LuaConsole *con; + std::string filename; // absolute path of the script + std::string directory; // parent directory to watch + std::string basename; // filename portion +}; + +static gboolean watcher_reload_idle(gpointer data) +{ + int uid = GPOINTER_TO_INT(data); + auto it = g_consoles.find(uid); + if (it == g_consoles.end()) return G_SOURCE_REMOVE; + LuaConsole *con = it->second; + if (con->watcher_running && !con->filename.empty()) { + RequestAbortLuaScript(uid, "terminated to reload the script"); + RunLuaScriptFile(uid, con->filename.c_str()); + } + return G_SOURCE_REMOVE; +} + +static gpointer file_watcher_thread(gpointer arg) +{ + WatcherArgs *wa = static_cast(arg); + LuaConsole *con = wa->con; + int uid = con->uid; + + int ifd = inotify_init(); + if (ifd < 0) { delete wa; return nullptr; } + + int wd = inotify_add_watch(ifd, wa->directory.c_str(), + IN_CLOSE_WRITE | IN_MOVED_TO); + + char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event)))); + + while (con->watcher_running) { + fd_set fds; + FD_ZERO(&fds); + FD_SET(ifd, &fds); + struct timeval tv = {0, 500000}; // 500 ms poll, matching Windows timeout + int ret = select(ifd + 1, &fds, nullptr, nullptr, &tv); + if (!con->watcher_running) break; + if (ret <= 0) continue; + + int len = read(ifd, buf, sizeof(buf)); + if (len < 0) continue; + + const struct inotify_event *ev; + for (char *p = buf; p < buf + len; + p += sizeof(struct inotify_event) + ev->len) { + ev = reinterpret_cast(p); + if (ev->len > 0 && wa->basename == ev->name) { + g_idle_add(watcher_reload_idle, GINT_TO_POINTER(uid)); + break; + } + } + } + + if (wd >= 0) inotify_rm_watch(ifd, wd); + close(ifd); + delete wa; + return nullptr; +} + +static void start_watcher(LuaConsole *con) +{ + if (con->filename.empty()) return; + + // Stop the previous watcher before starting a new one + if (con->watcher_thread) { + con->watcher_running = false; + g_thread_join(con->watcher_thread); + con->watcher_thread = nullptr; + } + + std::string filename = con->filename; + std::string directory = filename; + size_t slash = directory.rfind('/'); + if (slash != std::string::npos) directory.resize(slash); + else directory = "."; + std::string basename = filename.substr( + slash != std::string::npos ? slash + 1 : 0); + + WatcherArgs *wa = new WatcherArgs{con, filename, directory, basename}; + con->watcher_running = true; + con->watcher_thread = g_thread_new("lua-watcher", file_watcher_thread, wa); +} + +static void stop_watcher(LuaConsole *con) +{ + if (!con->watcher_thread) return; + con->watcher_running = false; + g_thread_join(con->watcher_thread); + con->watcher_thread = nullptr; +} + +// ──────────────────────────────────────── +// Console output helpers +// ──────────────────────────────────────── + static void console_append(LuaConsole *con, const char *str) { + // When "stdout" is checked, mirror Windows IDC_USE_STDOUT behaviour + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(con->stdout_check))) { + fputs(str, stdout); + fflush(stdout); + return; + } + + // Discard the first half of the buffer when it gets too long + gint len = gtk_text_buffer_get_char_count(con->output_buf); + if (len >= MAX_CONSOLE_CHARS) { + GtkTextIter start, mid; + gtk_text_buffer_get_start_iter(con->output_buf, &start); + gtk_text_buffer_get_iter_at_offset(con->output_buf, &mid, len / 2); + gtk_text_buffer_delete(con->output_buf, &start, &mid); + } + GtkTextIter end; gtk_text_buffer_get_end_iter(con->output_buf, &end); gtk_text_buffer_insert(con->output_buf, &end, str, -1); @@ -48,6 +191,10 @@ static void console_append(LuaConsole *con, const char *str) gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(con->output_view), mark); } +// ──────────────────────────────────────── +// Lua engine callbacks +// ──────────────────────────────────────── + static void lua_print_cb(int uid, const char *str) { auto it = g_consoles.find(uid); @@ -61,47 +208,110 @@ static void lua_onstart_cb(int uid) if (it == g_consoles.end()) return; LuaConsole *con = it->second; + con->started = true; gtk_text_buffer_set_text(con->output_buf, "", -1); gtk_button_set_label(GTK_BUTTON(con->run_btn), "Restart"); - gtk_widget_set_sensitive(con->run_btn, TRUE); - gtk_widget_set_sensitive(con->stop_btn, TRUE); + gtk_widget_set_sensitive(con->run_btn, TRUE); + gtk_widget_set_sensitive(con->stop_btn, TRUE); + // Disable browse while running (matches Windows: can misbehave in a frame-advance loop) + gtk_widget_set_sensitive(con->browse_btn, FALSE); } -static void lua_onstop_cb(int uid, bool statusOK) +static void lua_onstop_cb(int uid, bool /*statusOK*/) { auto it = g_consoles.find(uid); if (it == g_consoles.end()) return; LuaConsole *con = it->second; + console_append(con, "script stopped.\n"); + + con->started = false; gtk_button_set_label(GTK_BUTTON(con->run_btn), "Run"); - gtk_widget_set_sensitive(con->stop_btn, FALSE); + gtk_widget_set_sensitive(con->stop_btn, FALSE); + gtk_widget_set_sensitive(con->browse_btn, TRUE); - if (!statusOK) - console_append(con, "Script stopped with error.\n"); - else - console_append(con, "Script finished.\n"); + if (con->closeOnStop) + gtk_widget_destroy(con->window); // on_window_destroy handles cleanup } -static void on_browse_clicked(GtkButton *btn, gpointer user_data) +// ──────────────────────────────────────── +// Edit button label / sensitivity +// Mirrors Windows UpdateFileEntered() logic +// ──────────────────────────────────────── + +static void update_edit_button(LuaConsole *con) { - LuaConsole *con = (LuaConsole*)user_data; + const gchar *path = gtk_entry_get_text(GTK_ENTRY(con->path_entry)); + if (!path || path[0] == '\0') { + gtk_button_set_label(GTK_BUTTON(con->edit_btn), "Edit"); + gtk_widget_set_sensitive(con->edit_btn, FALSE); + return; + } + + bool exists = (access(path, F_OK) == 0); + bool writable = (access(path, W_OK) == 0); + const char *ext = strrchr(path, '.'); + bool isLua = ext && (g_ascii_strcasecmp(ext, ".lua") == 0); + + if (exists) { + const char *label = isLua ? (writable ? "Edit" : "View") : "Open"; + gtk_button_set_label(GTK_BUTTON(con->edit_btn), label); + gtk_widget_set_sensitive(con->edit_btn, TRUE); + } else { + gtk_button_set_label(GTK_BUTTON(con->edit_btn), "Create"); + gtk_widget_set_sensitive(con->edit_btn, isLua); + } +} + +// ──────────────────────────────────────── +// Signal handlers +// ──────────────────────────────────────── + +static void on_path_changed(GtkEditable * /*editable*/, gpointer user_data) +{ + LuaConsole *con = static_cast(user_data); + const gchar *path = gtk_entry_get_text(GTK_ENTRY(con->path_entry)); + + if (path && path[0] != '\0' && access(path, F_OK) == 0) { + con->filename = path; + // Set window title to the script's basename (matches Windows behaviour) + const char *slash = strrchr(path, '/'); + gtk_window_set_title(GTK_WINDOW(con->window), + slash ? slash + 1 : path); + start_watcher(con); + } + + update_edit_button(con); +} + +static void on_browse_clicked(GtkButton * /*btn*/, gpointer user_data) +{ + LuaConsole *con = static_cast(user_data); GtkFileFilter *filter_lua = gtk_file_filter_new(); - gtk_file_filter_set_name(filter_lua, "Lua scripts (*.lua)"); + gtk_file_filter_set_name(filter_lua, "Lua Script (*.lua)"); gtk_file_filter_add_pattern(filter_lua, "*.lua"); GtkFileFilter *filter_all = gtk_file_filter_new(); - gtk_file_filter_set_name(filter_all, "All files"); + gtk_file_filter_set_name(filter_all, "All Files (*.*)"); gtk_file_filter_add_pattern(filter_all, "*"); GtkFileChooserNative *fc = gtk_file_chooser_native_new( - "Open Lua Script", + "Load Lua Script", GTK_WINDOW(con->window), GTK_FILE_CHOOSER_ACTION_OPEN, "_Open", "_Cancel"); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(fc), filter_lua); gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(fc), filter_all); + // Open in the directory of the current script, if one is set + if (!con->filename.empty()) { + std::string dir = con->filename; + size_t sl = dir.rfind('/'); + if (sl != std::string::npos) dir.resize(sl); + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fc), dir.c_str()); + } + if (gtk_native_dialog_run(GTK_NATIVE_DIALOG(fc)) == GTK_RESPONSE_ACCEPT) { GFile *file = gtk_file_chooser_get_file(GTK_FILE_CHOOSER(fc)); gchar *path = g_file_get_path(file); @@ -113,40 +323,109 @@ static void on_browse_clicked(GtkButton *btn, gpointer user_data) g_object_unref(fc); } -static void on_run_clicked(GtkButton *btn, gpointer user_data) +static void on_edit_clicked(GtkButton * /*btn*/, gpointer user_data) { - LuaConsole *con = (LuaConsole*)user_data; + LuaConsole *con = static_cast(user_data); const gchar *path = gtk_entry_get_text(GTK_ENTRY(con->path_entry)); if (!path || path[0] == '\0') return; - StopLuaScript(con->uid); - RunLuaScriptFile(con->uid, path); + bool exists = (access(path, F_OK) == 0); + bool created = false; + + if (!exists) { + // "Create" mode — create an empty file, matching Windows behaviour + FILE *f = fopen(path, "w"); + if (f) { fclose(f); exists = true; created = true; } + } + + if (exists) { + // Open in the default editor via xdg-open, mirroring Windows ShellExecute + const gchar *argv[] = {"xdg-open", path, nullptr}; + g_spawn_async(nullptr, const_cast(argv), nullptr, + G_SPAWN_SEARCH_PATH, nullptr, nullptr, nullptr, nullptr); + } + + if (created) + on_path_changed(GTK_EDITABLE(con->path_entry), con); } -static void on_stop_clicked(GtkButton *btn, gpointer user_data) +static void on_run_clicked(GtkButton * /*btn*/, gpointer user_data) { - LuaConsole *con = (LuaConsole*)user_data; - StopLuaScript(con->uid); + LuaConsole *con = static_cast(user_data); + if (con->filename.empty()) { + const gchar *path = gtk_entry_get_text(GTK_ENTRY(con->path_entry)); + if (!path || path[0] == '\0') return; + con->filename = path; + } + start_watcher(con); + RunLuaScriptFile(con->uid, con->filename.c_str()); } -static gboolean on_window_delete(GtkWidget *widget, GdkEvent *event, gpointer user_data) +static void on_stop_clicked(GtkButton * /*btn*/, gpointer user_data) { - LuaConsole *con = (LuaConsole*)user_data; + LuaConsole *con = static_cast(user_data); + console_append(con, "user clicked stop button\n"); StopLuaScript(con->uid); +} + +static void on_window_destroy(GtkWidget * /*widget*/, gpointer user_data) +{ + LuaConsole *con = static_cast(user_data); + stop_watcher(con); CloseLuaContext(con->uid); g_consoles.erase(con->uid); delete con; - return FALSE; } +static gboolean on_window_delete(GtkWidget * /*widget*/, GdkEvent * /*event*/, + gpointer user_data) +{ + LuaConsole *con = static_cast(user_data); + console_append(con, "user closed script window\n"); + // Stop the watcher immediately so it cannot trigger a reload during teardown + stop_watcher(con); + StopLuaScript(con->uid); + + if (con->started) { + // Script is still running; defer destroy until onstop fires + con->closeOnStop = true; + return TRUE; // block the window close + } + + return FALSE; // allow close → "destroy" signal fires next +} + +// Accept files dropped onto the window (mirrors Windows DragAcceptFiles) +static void on_drag_data_received(GtkWidget * /*widget*/, GdkDragContext *ctx, + gint /*x*/, gint /*y*/, GtkSelectionData *data, + guint /*info*/, guint time, gpointer user_data) +{ + LuaConsole *con = static_cast(user_data); + gchar **uris = gtk_selection_data_get_uris(data); + if (uris && uris[0]) { + gchar *path = g_filename_from_uri(uris[0], nullptr, nullptr); + if (path) { + gtk_entry_set_text(GTK_ENTRY(con->path_entry), path); + g_free(path); + } + g_strfreev(uris); + } + gtk_drag_finish(ctx, TRUE, FALSE, time); +} + +// ──────────────────────────────────────── +// Public API +// ──────────────────────────────────────── + void lua_script_open_console(GtkWindow *parent) { LuaConsole *con = new LuaConsole(); con->uid = g_next_uid++; + // ── Window ── con->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); gtk_window_set_title(GTK_WINDOW(con->window), "Lua Script Console"); - gtk_window_set_default_size(GTK_WINDOW(con->window), 480, 320); + gtk_window_set_default_size(GTK_WINDOW(con->window), 405, 244); if (parent) gtk_window_set_transient_for(GTK_WINDOW(con->window), parent); @@ -154,51 +433,74 @@ void lua_script_open_console(GtkWindow *parent) gtk_container_set_border_width(GTK_CONTAINER(vbox), 6); gtk_container_add(GTK_CONTAINER(con->window), vbox); - /* Path row */ - GtkWidget *path_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); + // ── Row 1: path entry ── con->path_entry = gtk_entry_new(); - gtk_widget_set_hexpand(con->path_entry, TRUE); - GtkWidget *browse_btn = gtk_button_new_with_label("Browse..."); - gtk_box_pack_start(GTK_BOX(path_row), con->path_entry, TRUE, TRUE, 0); - gtk_box_pack_start(GTK_BOX(path_row), browse_btn, FALSE, FALSE, 0); - gtk_box_pack_start(GTK_BOX(vbox), path_row, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), con->path_entry, FALSE, FALSE, 0); - /* Button row */ + // ── Row 2: [Browse...][Edit] ··· [Stop][Run] + // Matches Windows: Browse/Edit left-aligned, Stop/Run right-aligned GtkWidget *btn_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4); - con->run_btn = gtk_button_new_with_label("Run"); - con->stop_btn = gtk_button_new_with_label("Stop"); + + con->browse_btn = gtk_button_new_with_label("Browse..."); + con->edit_btn = gtk_button_new_with_label("Edit"); + con->stop_btn = gtk_button_new_with_label("Stop"); + con->run_btn = gtk_button_new_with_label("Run"); + + gtk_widget_set_sensitive(con->edit_btn, FALSE); gtk_widget_set_sensitive(con->stop_btn, FALSE); - gtk_box_pack_start(GTK_BOX(btn_row), con->run_btn, FALSE, FALSE, 0); + + gtk_box_pack_start(GTK_BOX(btn_row), con->browse_btn, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(btn_row), con->edit_btn, FALSE, FALSE, 0); + // Expanding spacer pushes Stop/Run to the right + gtk_box_pack_start(GTK_BOX(btn_row), + gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0), TRUE, TRUE, 0); gtk_box_pack_start(GTK_BOX(btn_row), con->stop_btn, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(btn_row), con->run_btn, FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(vbox), btn_row, FALSE, FALSE, 0); - /* Output console */ - GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL); + // ── Row 3: stdout checkbox (right-aligned, matches Windows IDC_USE_STDOUT) ── + GtkWidget *stdout_row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0); + con->stdout_check = gtk_check_button_new_with_label("stdout"); + gtk_box_pack_end(GTK_BOX(stdout_row), con->stdout_check, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), stdout_row, FALSE, FALSE, 0); + + // ── Row 4: output console (scrolled, monospace, read-only) ── + GtkWidget *scroll = gtk_scrolled_window_new(nullptr, nullptr); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), - GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + con->output_view = gtk_text_view_new(); gtk_text_view_set_editable(GTK_TEXT_VIEW(con->output_view), FALSE); gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(con->output_view), GTK_WRAP_WORD_CHAR); - - GtkCssProvider *css = gtk_css_provider_new(); - gtk_css_provider_load_from_data(css, "textview { font-family: monospace; }", -1, NULL); - gtk_style_context_add_provider( - gtk_widget_get_style_context(con->output_view), - GTK_STYLE_PROVIDER(css), - GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); - g_object_unref(css); + gtk_text_view_set_monospace(GTK_TEXT_VIEW(con->output_view), TRUE); con->output_buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(con->output_view)); gtk_container_add(GTK_CONTAINER(scroll), con->output_view); gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0); - /* Connect signals */ - g_signal_connect(browse_btn, "clicked", G_CALLBACK(on_browse_clicked), con); - g_signal_connect(con->run_btn, "clicked", G_CALLBACK(on_run_clicked), con); - g_signal_connect(con->stop_btn, "clicked", G_CALLBACK(on_stop_clicked), con); - g_signal_connect(con->window, "delete-event", G_CALLBACK(on_window_delete), con); - - /* Register Lua context before showing the window */ + // ── Drag-and-drop ── + gtk_drag_dest_set(con->window, GTK_DEST_DEFAULT_ALL, nullptr, 0, GDK_ACTION_COPY); + gtk_drag_dest_add_uri_targets(con->window); + + // ── Signals ── + g_signal_connect(con->path_entry, "changed", + G_CALLBACK(on_path_changed), con); + g_signal_connect(con->browse_btn, "clicked", + G_CALLBACK(on_browse_clicked), con); + g_signal_connect(con->edit_btn, "clicked", + G_CALLBACK(on_edit_clicked), con); + g_signal_connect(con->run_btn, "clicked", + G_CALLBACK(on_run_clicked), con); + g_signal_connect(con->stop_btn, "clicked", + G_CALLBACK(on_stop_clicked), con); + g_signal_connect(con->window, "delete-event", + G_CALLBACK(on_window_delete), con); + g_signal_connect(con->window, "destroy", + G_CALLBACK(on_window_destroy), con); + g_signal_connect(con->window, "drag-data-received", + G_CALLBACK(on_drag_data_received), con); + + // ── Register Lua context before showing ── OpenLuaContext(con->uid, lua_print_cb, lua_onstart_cb, lua_onstop_cb); g_consoles[con->uid] = con; From 0a8177a0f317d013f9a7e0266d1d65c2394c7ecf Mon Sep 17 00:00:00 2001 From: mkriskovic Date: Sat, 20 Jun 2026 13:19:25 +0200 Subject: [PATCH 3/7] added "close all script windows" button --- desmume/src/frontend/posix/gtk/main.cpp | 6 ++++++ desmume/src/frontend/posix/gtk/menu.ui | 4 ++++ .../src/frontend/posix/gtk/tools/luaScriptConsole.cpp | 11 +++++++++++ .../src/frontend/posix/gtk/tools/luaScriptConsole.h | 1 + 4 files changed, 22 insertions(+) diff --git a/desmume/src/frontend/posix/gtk/main.cpp b/desmume/src/frontend/posix/gtk/main.cpp index d32f57330..1040f4397 100644 --- a/desmume/src/frontend/posix/gtk/main.cpp +++ b/desmume/src/frontend/posix/gtk/main.cpp @@ -242,6 +242,7 @@ static void GraphicsSettingsDialog(GSimpleAction *action, GVariant *parameter, g #ifdef HAVE_LUA static void AddLuaScript(GSimpleAction *action, GVariant *parameter, gpointer user_data); +static void CloseAllLuaScripts(GSimpleAction *action, GVariant *parameter, gpointer user_data); #endif static const GActionEntry app_entries[] = { @@ -319,6 +320,7 @@ static const GActionEntry app_entries[] = { // dTool entries are populated dynamically in desmume_gtk_menu_tools(). #ifdef HAVE_LUA { "addluascript", AddLuaScript }, + { "closealluascripts", CloseAllLuaScripts }, #endif // Help @@ -596,6 +598,10 @@ static void AddLuaScript(GSimpleAction *action, GVariant *parameter, gpointer us { lua_script_open_console(GTK_WINDOW(pWindow)); } +static void CloseAllLuaScripts(GSimpleAction *action, GVariant *parameter, gpointer user_data) +{ + lua_script_close_all(); +} #endif static inline void UpdateStatusBar (const char *message) diff --git a/desmume/src/frontend/posix/gtk/menu.ui b/desmume/src/frontend/posix/gtk/menu.ui index 5c231e38a..bc6b095a5 100644 --- a/desmume/src/frontend/posix/gtk/menu.ui +++ b/desmume/src/frontend/posix/gtk/menu.ui @@ -770,6 +770,10 @@ _New Lua Script Window... app.addluascript + + _Close All Script Windows + app.closealluascripts +
diff --git a/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.cpp b/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.cpp index 43fb6e817..ebd92b0bc 100644 --- a/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.cpp +++ b/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.cpp @@ -417,6 +417,17 @@ static void on_drag_data_received(GtkWidget * /*widget*/, GdkDragContext *ctx, // Public API // ──────────────────────────────────────── +void lua_script_close_all() +{ + // Collect windows first to avoid iterator invalidation during close + // Iterate in reverse order, matching Windows IDC_CLOSE_LUA_SCRIPTS behaviour + std::vector windows; + for (auto &pair : g_consoles) + windows.push_back(pair.second->window); + for (int i = (int)windows.size() - 1; i >= 0; i--) + gtk_window_close(GTK_WINDOW(windows[i])); +} + void lua_script_open_console(GtkWindow *parent) { LuaConsole *con = new LuaConsole(); diff --git a/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.h b/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.h index 55bd0a598..0c83bf422 100644 --- a/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.h +++ b/desmume/src/frontend/posix/gtk/tools/luaScriptConsole.h @@ -26,6 +26,7 @@ #include void lua_script_open_console(GtkWindow *parent); +void lua_script_close_all(); #endif /* HAVE_LUA */ From 68721fe27ec9f2312b3e89ea766a09bfb3a40179 Mon Sep 17 00:00:00 2001 From: mkriskovic Date: Sun, 21 Jun 2026 01:20:49 +0200 Subject: [PATCH 4/7] added gtk2 support --- desmume/src/frontend/posix/gtk2/main.cpp | 43 ++ desmume/src/frontend/posix/gtk2/meson.build | 4 + .../posix/gtk2/tools/luaScriptConsole.cpp | 532 ++++++++++++++++++ .../posix/gtk2/tools/luaScriptConsole.h | 33 ++ 4 files changed, 612 insertions(+) create mode 100644 desmume/src/frontend/posix/gtk2/tools/luaScriptConsole.cpp create mode 100644 desmume/src/frontend/posix/gtk2/tools/luaScriptConsole.h diff --git a/desmume/src/frontend/posix/gtk2/main.cpp b/desmume/src/frontend/posix/gtk2/main.cpp index e68ea5973..2e58adf4b 100644 --- a/desmume/src/frontend/posix/gtk2/main.cpp +++ b/desmume/src/frontend/posix/gtk2/main.cpp @@ -72,6 +72,11 @@ #include "gdbstub.h" #endif +#ifdef HAVE_LUA + #include "tools/luaScriptConsole.h" + #include "lua-engine.h" +#endif + #if defined(ENABLE_OPENGL_STANDARD) || defined(ENABLE_OPENGL_ES) #if defined(ENABLE_OPENGL_ES) #include "OGLRender_ES3.h" @@ -203,6 +208,10 @@ static void JITMaxBlockSizeChanged(GtkAdjustment* adj,void *); #endif static void GraphicsSettingsDialog(); +#ifdef HAVE_LUA +static void AddLuaScript(); +static void CloseAllLuaScripts(); +#endif static const char *ui_description = "" @@ -406,6 +415,13 @@ static const char *ui_description = " " " " " " +#ifdef HAVE_LUA +" " +" " +" " +" " +" " +#endif " " " " " " @@ -487,6 +503,11 @@ static const GtkActionEntry action_entries[] = { { "ToolsMenu", NULL, "_Tools" }, { "dumpram", NULL, "Dump ram to ...", NULL, NULL, DumpRamDialog }, +#ifdef HAVE_LUA + { "LuaScriptingMenu", NULL, "_Lua Scripting" }, + { "addluascript", NULL, "_New Lua Script Window...", NULL, NULL, AddLuaScript }, + { "closealluascripts",NULL, "_Close All Script Windows", NULL, NULL, CloseAllLuaScripts }, +#endif { "LayersMenu", NULL, "View _Layers" }, { "HelpMenu", NULL, "_Help" }, @@ -1331,6 +1352,17 @@ static void SaveStateDialog() gtk_widget_destroy(pFileSelection); } +#ifdef HAVE_LUA +static void AddLuaScript() +{ + lua_script_open_console(GTK_WINDOW(pWindow)); +} +static void CloseAllLuaScripts() +{ + lua_script_close_all(); +} +#endif + static void DumpRamDialog() { GtkFileFilter *pFilter_ds, *pFilter_any; @@ -3160,8 +3192,19 @@ gboolean EmuLoop(gpointer data) touchpad.click = 0; } +#ifdef HAVE_LUA + NDS_beginProcessingInput(); + CallRegisteredLuaFunctions(LUACALL_BEFOREEMULATION); + NDS_endProcessingInput(); +#endif + desmume_cycle(); /* Emule ! */ +#ifdef HAVE_LUA + CallRegisteredLuaFunctions(LUACALL_AFTEREMULATION); + CallRegisteredLuaFunctions(LUACALL_AFTEREMULATIONGUI); +#endif + _updateDTools(); if (!config.fpslimiter || keys_latch & KEYMASK_(KEY_BOOST - 1)) { diff --git a/desmume/src/frontend/posix/gtk2/meson.build b/desmume/src/frontend/posix/gtk2/meson.build index a469fe960..ef962802b 100644 --- a/desmume/src/frontend/posix/gtk2/meson.build +++ b/desmume/src/frontend/posix/gtk2/meson.build @@ -18,6 +18,10 @@ desmume_src = [ 'main.cpp', ] +if dep_lua.found() + desmume_src += ['tools/luaScriptConsole.cpp'] +endif + if get_option('glx') and dep_gl.found() desmume_src += [ '../shared/glx_3Demu.cpp', diff --git a/desmume/src/frontend/posix/gtk2/tools/luaScriptConsole.cpp b/desmume/src/frontend/posix/gtk2/tools/luaScriptConsole.cpp new file mode 100644 index 000000000..33877c994 --- /dev/null +++ b/desmume/src/frontend/posix/gtk2/tools/luaScriptConsole.cpp @@ -0,0 +1,532 @@ +/* luaScriptConsole.cpp - this file is part of DeSmuME + * + * Copyright (C) 2006-2025 DeSmuME Team + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_LUA + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "lua-engine.h" + +#define MAX_CONSOLE_CHARS 250000 + +struct LuaConsole { + int uid; + std::string filename; + bool started; + bool closeOnStop; + + GtkWidget *window; + GtkWidget *path_entry; + GtkWidget *browse_btn; + GtkWidget *edit_btn; + GtkWidget *run_btn; + GtkWidget *stop_btn; + GtkWidget *stdout_check; + GtkWidget *output_view; + GtkTextBuffer *output_buf; + + volatile bool watcher_running; + GThread *watcher_thread; + + LuaConsole() + : uid(0), started(false), closeOnStop(false), + watcher_running(false), watcher_thread(nullptr) {} +}; + +static std::map g_consoles; +static int g_next_uid = 1; + +// ──────────────────────────────────────── +// File watcher (inotify) +// ──────────────────────────────────────── + +struct WatcherArgs { + LuaConsole *con; + std::string filename; + std::string directory; + std::string basename; +}; + +static gboolean watcher_reload_idle(gpointer data) +{ + int uid = GPOINTER_TO_INT(data); + auto it = g_consoles.find(uid); + if (it == g_consoles.end()) return G_SOURCE_REMOVE; + LuaConsole *con = it->second; + if (con->watcher_running && !con->filename.empty()) { + RequestAbortLuaScript(uid, "terminated to reload the script"); + RunLuaScriptFile(uid, con->filename.c_str()); + } + return G_SOURCE_REMOVE; +} + +static gpointer file_watcher_thread(gpointer arg) +{ + WatcherArgs *wa = static_cast(arg); + LuaConsole *con = wa->con; + int uid = con->uid; + + int ifd = inotify_init(); + if (ifd < 0) { delete wa; return nullptr; } + + int wd = inotify_add_watch(ifd, wa->directory.c_str(), + IN_CLOSE_WRITE | IN_MOVED_TO); + + char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event)))); + + while (con->watcher_running) { + fd_set fds; + FD_ZERO(&fds); + FD_SET(ifd, &fds); + struct timeval tv = {0, 500000}; + int ret = select(ifd + 1, &fds, nullptr, nullptr, &tv); + if (!con->watcher_running) break; + if (ret <= 0) continue; + + int len = read(ifd, buf, sizeof(buf)); + if (len < 0) continue; + + const struct inotify_event *ev; + for (char *p = buf; p < buf + len; + p += sizeof(struct inotify_event) + ev->len) { + ev = reinterpret_cast(p); + if (ev->len > 0 && wa->basename == ev->name) { + g_idle_add(watcher_reload_idle, GINT_TO_POINTER(uid)); + break; + } + } + } + + if (wd >= 0) inotify_rm_watch(ifd, wd); + close(ifd); + delete wa; + return nullptr; +} + +static void start_watcher(LuaConsole *con) +{ + if (con->filename.empty()) return; + + if (con->watcher_thread) { + con->watcher_running = false; + g_thread_join(con->watcher_thread); + con->watcher_thread = nullptr; + } + + std::string filename = con->filename; + std::string directory = filename; + size_t slash = directory.rfind('/'); + if (slash != std::string::npos) directory.resize(slash); + else directory = "."; + std::string basename = filename.substr( + slash != std::string::npos ? slash + 1 : 0); + + WatcherArgs *wa = new WatcherArgs{con, filename, directory, basename}; + con->watcher_running = true; + con->watcher_thread = g_thread_new("lua-watcher", file_watcher_thread, wa); +} + +static void stop_watcher(LuaConsole *con) +{ + if (!con->watcher_thread) return; + con->watcher_running = false; + g_thread_join(con->watcher_thread); + con->watcher_thread = nullptr; +} + +// ──────────────────────────────────────── +// Console output helpers +// ──────────────────────────────────────── + +static void console_append(LuaConsole *con, const char *str) +{ + if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(con->stdout_check))) { + fputs(str, stdout); + fflush(stdout); + return; + } + + // GTK2's text buffer mishandles \r in \r\n sequences — strip \r before insert + std::string normalized; + for (const char *p = str; *p; ++p) { + if (*p != '\r') + normalized += *p; + } + const char *text = normalized.c_str(); + + gint len = gtk_text_buffer_get_char_count(con->output_buf); + if (len >= MAX_CONSOLE_CHARS) { + GtkTextIter start, mid; + gtk_text_buffer_get_start_iter(con->output_buf, &start); + gtk_text_buffer_get_iter_at_offset(con->output_buf, &mid, len / 2); + gtk_text_buffer_delete(con->output_buf, &start, &mid); + } + + GtkTextIter end; + gtk_text_buffer_get_end_iter(con->output_buf, &end); + gtk_text_buffer_insert(con->output_buf, &end, text, -1); + + GtkTextMark *mark = gtk_text_buffer_get_mark(con->output_buf, "insert"); + gtk_text_view_scroll_mark_onscreen(GTK_TEXT_VIEW(con->output_view), mark); +} + +// ──────────────────────────────────────── +// Lua engine callbacks +// ──────────────────────────────────────── + +static void lua_print_cb(int uid, const char *str) +{ + auto it = g_consoles.find(uid); + if (it != g_consoles.end()) + console_append(it->second, str); +} + +static void lua_onstart_cb(int uid) +{ + auto it = g_consoles.find(uid); + if (it == g_consoles.end()) return; + LuaConsole *con = it->second; + + con->started = true; + gtk_text_buffer_set_text(con->output_buf, "", -1); + gtk_button_set_label(GTK_BUTTON(con->run_btn), "Restart"); + gtk_widget_set_sensitive(con->run_btn, TRUE); + gtk_widget_set_sensitive(con->stop_btn, TRUE); + gtk_widget_set_sensitive(con->browse_btn, FALSE); +} + +static void lua_onstop_cb(int uid, bool /*statusOK*/) +{ + auto it = g_consoles.find(uid); + if (it == g_consoles.end()) return; + LuaConsole *con = it->second; + + console_append(con, "script stopped.\n"); + + con->started = false; + gtk_button_set_label(GTK_BUTTON(con->run_btn), "Run"); + gtk_widget_set_sensitive(con->stop_btn, FALSE); + gtk_widget_set_sensitive(con->browse_btn, TRUE); + + if (con->closeOnStop) + gtk_widget_destroy(con->window); +} + +// ──────────────────────────────────────── +// Edit button label / sensitivity +// ──────────────────────────────────────── + +static void update_edit_button(LuaConsole *con) +{ + const gchar *path = gtk_entry_get_text(GTK_ENTRY(con->path_entry)); + if (!path || path[0] == '\0') { + gtk_button_set_label(GTK_BUTTON(con->edit_btn), "Edit"); + gtk_widget_set_sensitive(con->edit_btn, FALSE); + return; + } + + bool exists = (access(path, F_OK) == 0); + bool writable = (access(path, W_OK) == 0); + const char *ext = strrchr(path, '.'); + bool isLua = ext && (g_ascii_strcasecmp(ext, ".lua") == 0); + + if (exists) { + const char *label = isLua ? (writable ? "Edit" : "View") : "Open"; + gtk_button_set_label(GTK_BUTTON(con->edit_btn), label); + gtk_widget_set_sensitive(con->edit_btn, TRUE); + } else { + gtk_button_set_label(GTK_BUTTON(con->edit_btn), "Create"); + gtk_widget_set_sensitive(con->edit_btn, isLua); + } +} + +// ──────────────────────────────────────── +// Signal handlers +// ──────────────────────────────────────── + +static void on_path_changed(GtkEditable * /*editable*/, gpointer user_data) +{ + LuaConsole *con = static_cast(user_data); + const gchar *path = gtk_entry_get_text(GTK_ENTRY(con->path_entry)); + + if (path && path[0] != '\0' && access(path, F_OK) == 0) { + con->filename = path; + const char *slash = strrchr(path, '/'); + gtk_window_set_title(GTK_WINDOW(con->window), + slash ? slash + 1 : path); + start_watcher(con); + } + + update_edit_button(con); +} + +static void on_browse_clicked(GtkButton * /*btn*/, gpointer user_data) +{ + LuaConsole *con = static_cast(user_data); + + GtkFileFilter *filter_lua = gtk_file_filter_new(); + gtk_file_filter_set_name(filter_lua, "Lua Script (*.lua)"); + gtk_file_filter_add_pattern(filter_lua, "*.lua"); + + GtkFileFilter *filter_all = gtk_file_filter_new(); + gtk_file_filter_set_name(filter_all, "All Files (*.*)"); + gtk_file_filter_add_pattern(filter_all, "*"); + + // GTK2 uses GtkFileChooserDialog instead of GtkFileChooserNative + GtkWidget *fc = gtk_file_chooser_dialog_new( + "Load Lua Script", + GTK_WINDOW(con->window), + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, + NULL); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(fc), filter_lua); + gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(fc), filter_all); + + if (!con->filename.empty()) { + std::string dir = con->filename; + size_t sl = dir.rfind('/'); + if (sl != std::string::npos) dir.resize(sl); + gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(fc), dir.c_str()); + } + + if (gtk_dialog_run(GTK_DIALOG(fc)) == GTK_RESPONSE_ACCEPT) { + gchar *path = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fc)); + if (path) { + gtk_entry_set_text(GTK_ENTRY(con->path_entry), path); + g_free(path); + } + } + + gtk_widget_destroy(fc); +} + +static void on_edit_clicked(GtkButton * /*btn*/, gpointer user_data) +{ + LuaConsole *con = static_cast(user_data); + const gchar *path = gtk_entry_get_text(GTK_ENTRY(con->path_entry)); + if (!path || path[0] == '\0') return; + + bool exists = (access(path, F_OK) == 0); + bool created = false; + + if (!exists) { + FILE *f = fopen(path, "w"); + if (f) { fclose(f); exists = true; created = true; } + } + + if (exists) { + const gchar *argv[] = {"xdg-open", path, nullptr}; + g_spawn_async(nullptr, const_cast(argv), nullptr, + G_SPAWN_SEARCH_PATH, nullptr, nullptr, nullptr, nullptr); + } + + if (created) + on_path_changed(GTK_EDITABLE(con->path_entry), con); +} + +static void on_run_clicked(GtkButton * /*btn*/, gpointer user_data) +{ + LuaConsole *con = static_cast(user_data); + if (con->filename.empty()) { + const gchar *path = gtk_entry_get_text(GTK_ENTRY(con->path_entry)); + if (!path || path[0] == '\0') return; + con->filename = path; + } + start_watcher(con); + RunLuaScriptFile(con->uid, con->filename.c_str()); +} + +static void on_stop_clicked(GtkButton * /*btn*/, gpointer user_data) +{ + LuaConsole *con = static_cast(user_data); + console_append(con, "user clicked stop button\n"); + StopLuaScript(con->uid); +} + +static void on_window_destroy(GtkWidget * /*widget*/, gpointer user_data) +{ + LuaConsole *con = static_cast(user_data); + stop_watcher(con); + CloseLuaContext(con->uid); + g_consoles.erase(con->uid); + delete con; +} + +static gboolean on_window_delete(GtkWidget * /*widget*/, GdkEvent * /*event*/, + gpointer user_data) +{ + LuaConsole *con = static_cast(user_data); + console_append(con, "user closed script window\n"); + stop_watcher(con); + StopLuaScript(con->uid); + + if (con->started) { + con->closeOnStop = true; + return TRUE; + } + + return FALSE; +} + +static void on_drag_data_received(GtkWidget * /*widget*/, GdkDragContext *ctx, + gint /*x*/, gint /*y*/, GtkSelectionData *data, + guint /*info*/, guint time, gpointer user_data) +{ + LuaConsole *con = static_cast(user_data); + gchar **uris = gtk_selection_data_get_uris(data); + if (uris && uris[0]) { + gchar *path = g_filename_from_uri(uris[0], nullptr, nullptr); + if (path) { + gtk_entry_set_text(GTK_ENTRY(con->path_entry), path); + g_free(path); + } + g_strfreev(uris); + } + gtk_drag_finish(ctx, TRUE, FALSE, time); +} + +// ──────────────────────────────────────── +// Public API +// ──────────────────────────────────────── + +void lua_script_close_all() +{ + std::vector windows; + for (auto &pair : g_consoles) + windows.push_back(pair.second->window); + // Synthesize a GDK_DELETE event — mirrors gtk_window_close() from GTK3 + for (int i = (int)windows.size() - 1; i >= 0; i--) { + GtkWidget *w = windows[i]; + GdkWindow *gdk_win = gtk_widget_get_window(w); + if (gdk_win) { + GdkEvent *event = gdk_event_new(GDK_DELETE); + event->any.window = GDK_WINDOW(g_object_ref(gdk_win)); + gtk_main_do_event(event); + gdk_event_free(event); + } + } +} + +void lua_script_open_console(GtkWindow *parent) +{ + LuaConsole *con = new LuaConsole(); + con->uid = g_next_uid++; + + // ── Window ── + con->window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + gtk_window_set_title(GTK_WINDOW(con->window), "Lua Script Console"); + gtk_window_set_default_size(GTK_WINDOW(con->window), 405, 244); + if (parent) + gtk_window_set_transient_for(GTK_WINDOW(con->window), parent); + + // GTK2: use gtk_vbox_new instead of gtk_box_new(GTK_ORIENTATION_VERTICAL) + GtkWidget *vbox = gtk_vbox_new(FALSE, 4); + gtk_container_set_border_width(GTK_CONTAINER(vbox), 6); + gtk_container_add(GTK_CONTAINER(con->window), vbox); + + // ── Row 1: path entry ── + con->path_entry = gtk_entry_new(); + gtk_box_pack_start(GTK_BOX(vbox), con->path_entry, FALSE, FALSE, 0); + + // ── Row 2: [Browse...][Edit] ··· [Stop][Run] ── + // GTK2: use gtk_hbox_new instead of gtk_box_new(GTK_ORIENTATION_HORIZONTAL) + GtkWidget *btn_row = gtk_hbox_new(FALSE, 4); + + con->browse_btn = gtk_button_new_with_label("Browse..."); + con->edit_btn = gtk_button_new_with_label("Edit"); + con->stop_btn = gtk_button_new_with_label("Stop"); + con->run_btn = gtk_button_new_with_label("Run"); + + gtk_widget_set_sensitive(con->edit_btn, FALSE); + gtk_widget_set_sensitive(con->stop_btn, FALSE); + + gtk_box_pack_start(GTK_BOX(btn_row), con->browse_btn, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(btn_row), con->edit_btn, FALSE, FALSE, 0); + // Expanding spacer pushes Stop/Run to the right + gtk_box_pack_start(GTK_BOX(btn_row), gtk_hbox_new(FALSE, 0), TRUE, TRUE, 0); + gtk_box_pack_start(GTK_BOX(btn_row), con->stop_btn, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(btn_row), con->run_btn, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), btn_row, FALSE, FALSE, 0); + + // ── Row 3: stdout checkbox (right-aligned) ── + GtkWidget *stdout_row = gtk_hbox_new(FALSE, 0); + con->stdout_check = gtk_check_button_new_with_label("stdout"); + gtk_box_pack_end(GTK_BOX(stdout_row), con->stdout_check, FALSE, FALSE, 0); + gtk_box_pack_start(GTK_BOX(vbox), stdout_row, FALSE, FALSE, 0); + + // ── Row 4: output console (scrolled, monospace, read-only) ── + GtkWidget *scroll = gtk_scrolled_window_new(NULL, NULL); + gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + + con->output_view = gtk_text_view_new(); + gtk_text_view_set_editable(GTK_TEXT_VIEW(con->output_view), FALSE); + gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(con->output_view), GTK_WRAP_WORD_CHAR); + + // GTK2: set monospace font via Pango (no CSS) + PangoFontDescription *font = pango_font_description_from_string("Monospace 10"); + gtk_widget_modify_font(con->output_view, font); + pango_font_description_free(font); + + con->output_buf = gtk_text_view_get_buffer(GTK_TEXT_VIEW(con->output_view)); + gtk_container_add(GTK_CONTAINER(scroll), con->output_view); + gtk_box_pack_start(GTK_BOX(vbox), scroll, TRUE, TRUE, 0); + + // ── Drag-and-drop ── + gtk_drag_dest_set(con->window, GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY); + gtk_drag_dest_add_uri_targets(con->window); + + // ── Signals ── + g_signal_connect(con->path_entry, "changed", + G_CALLBACK(on_path_changed), con); + g_signal_connect(con->browse_btn, "clicked", + G_CALLBACK(on_browse_clicked), con); + g_signal_connect(con->edit_btn, "clicked", + G_CALLBACK(on_edit_clicked), con); + g_signal_connect(con->run_btn, "clicked", + G_CALLBACK(on_run_clicked), con); + g_signal_connect(con->stop_btn, "clicked", + G_CALLBACK(on_stop_clicked), con); + g_signal_connect(con->window, "delete-event", + G_CALLBACK(on_window_delete), con); + g_signal_connect(con->window, "destroy", + G_CALLBACK(on_window_destroy), con); + g_signal_connect(con->window, "drag-data-received", + G_CALLBACK(on_drag_data_received), con); + + // ── Register Lua context before showing ── + OpenLuaContext(con->uid, lua_print_cb, lua_onstart_cb, lua_onstop_cb); + g_consoles[con->uid] = con; + + gtk_widget_show_all(con->window); +} + +#endif /* HAVE_LUA */ diff --git a/desmume/src/frontend/posix/gtk2/tools/luaScriptConsole.h b/desmume/src/frontend/posix/gtk2/tools/luaScriptConsole.h new file mode 100644 index 000000000..0c83bf422 --- /dev/null +++ b/desmume/src/frontend/posix/gtk2/tools/luaScriptConsole.h @@ -0,0 +1,33 @@ +/* luaScriptConsole.h - this file is part of DeSmuME + * + * Copyright (C) 2006-2025 DeSmuME Team + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef __LUASCRIPTCONSOLE_H__ +#define __LUASCRIPTCONSOLE_H__ + +#ifdef HAVE_LUA + +#include + +void lua_script_open_console(GtkWindow *parent); +void lua_script_close_all(); + +#endif /* HAVE_LUA */ + +#endif /* __LUASCRIPTCONSOLE_H__ */ From 612c52ff7793e32dade2cd75911e016eff36d6f6 Mon Sep 17 00:00:00 2001 From: mkriskovic Date: Mon, 22 Jun 2026 12:53:47 +0200 Subject: [PATCH 5/7] missing link_args fix --- desmume/src/frontend/posix/gtk/meson.build | 1 + desmume/src/frontend/posix/gtk2/meson.build | 1 + 2 files changed, 2 insertions(+) diff --git a/desmume/src/frontend/posix/gtk/meson.build b/desmume/src/frontend/posix/gtk/meson.build index bb5621b00..a93cb506c 100644 --- a/desmume/src/frontend/posix/gtk/meson.build +++ b/desmume/src/frontend/posix/gtk/meson.build @@ -56,6 +56,7 @@ executable('desmume', include_directories: includes, link_with: libdesmume, cpp_args: ['-DGTK_DISABLE_DEPRECATED'], + link_args: ['-rdynamic'], install: true, ) diff --git a/desmume/src/frontend/posix/gtk2/meson.build b/desmume/src/frontend/posix/gtk2/meson.build index ef962802b..35a1d9423 100644 --- a/desmume/src/frontend/posix/gtk2/meson.build +++ b/desmume/src/frontend/posix/gtk2/meson.build @@ -48,6 +48,7 @@ executable('desmume', dependencies: gtk_dependencies, include_directories: includes, link_with: libdesmume, + link_args: ['-rdynamic'], install: true, ) From 1f22ee71e6da308a94beba3e1560ca2e15b26eaa Mon Sep 17 00:00:00 2001 From: mkriskovic Date: Tue, 23 Jun 2026 11:28:24 +0200 Subject: [PATCH 6/7] exposing only lua symbols --- desmume/src/frontend/posix/gtk/meson.build | 2 +- desmume/src/frontend/posix/gtk2/meson.build | 2 +- desmume/src/frontend/posix/lua-exports.txt | 16 ++++++++++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 desmume/src/frontend/posix/lua-exports.txt diff --git a/desmume/src/frontend/posix/gtk/meson.build b/desmume/src/frontend/posix/gtk/meson.build index a93cb506c..00e09d813 100644 --- a/desmume/src/frontend/posix/gtk/meson.build +++ b/desmume/src/frontend/posix/gtk/meson.build @@ -56,7 +56,7 @@ executable('desmume', include_directories: includes, link_with: libdesmume, cpp_args: ['-DGTK_DISABLE_DEPRECATED'], - link_args: ['-rdynamic'], + link_args: ['-Wl,--dynamic-list,' + meson.current_source_dir() / '../lua-exports.txt'], install: true, ) diff --git a/desmume/src/frontend/posix/gtk2/meson.build b/desmume/src/frontend/posix/gtk2/meson.build index 35a1d9423..9104feffd 100644 --- a/desmume/src/frontend/posix/gtk2/meson.build +++ b/desmume/src/frontend/posix/gtk2/meson.build @@ -48,7 +48,7 @@ executable('desmume', dependencies: gtk_dependencies, include_directories: includes, link_with: libdesmume, - link_args: ['-rdynamic'], + link_args: ['-Wl,--dynamic-list,' + meson.current_source_dir() / '../lua-exports.txt'], install: true, ) diff --git a/desmume/src/frontend/posix/lua-exports.txt b/desmume/src/frontend/posix/lua-exports.txt new file mode 100644 index 000000000..143191313 --- /dev/null +++ b/desmume/src/frontend/posix/lua-exports.txt @@ -0,0 +1,16 @@ +{ + lua_createtable; + lua_gettop; + lua_objlen; + lua_pushcclosure; + lua_pushlstring; + lua_pushnil; + lua_pushstring; + lua_rawget; + lua_rawgeti; + lua_setfield; + lua_settop; + lua_tointeger; + lua_type; + luaL_optlstring; +}; From 2ae6aae28231257b6d6beec6464287d329af960f Mon Sep 17 00:00:00 2001 From: mkriskovic Date: Tue, 23 Jun 2026 14:37:41 +0200 Subject: [PATCH 7/7] exposing more lua symbols --- desmume/src/frontend/posix/lua-exports.txt | 144 ++++++++++++++++++++- 1 file changed, 138 insertions(+), 6 deletions(-) diff --git a/desmume/src/frontend/posix/lua-exports.txt b/desmume/src/frontend/posix/lua-exports.txt index 143191313..df6bf5e45 100644 --- a/desmume/src/frontend/posix/lua-exports.txt +++ b/desmume/src/frontend/posix/lua-exports.txt @@ -1,16 +1,148 @@ { - lua_createtable; + /* state */ + lua_newstate; + lua_close; + lua_newthread; + lua_atpanic; + + /* stack */ lua_gettop; + lua_settop; + lua_pushvalue; + lua_remove; + lua_insert; + lua_replace; + lua_checkstack; + lua_xmove; + + /* access */ + lua_isnumber; + lua_isstring; + lua_iscfunction; + lua_isuserdata; + lua_type; + lua_typename; + lua_equal; + lua_rawequal; + lua_lessthan; + lua_tonumber; + lua_tointeger; + lua_toboolean; + lua_tolstring; lua_objlen; - lua_pushcclosure; - lua_pushlstring; + lua_tocfunction; + lua_touserdata; + lua_tothread; + lua_topointer; + + /* push */ lua_pushnil; + lua_pushnumber; + lua_pushinteger; + lua_pushlstring; lua_pushstring; + lua_pushvfstring; + lua_pushfstring; + lua_pushcclosure; + lua_pushboolean; + lua_pushlightuserdata; + lua_pushthread; + + /* get */ + lua_gettable; + lua_getfield; lua_rawget; lua_rawgeti; + lua_createtable; + lua_newuserdata; + lua_getmetatable; + lua_getfenv; + + /* set */ + lua_settable; lua_setfield; - lua_settop; - lua_tointeger; - lua_type; + lua_rawset; + lua_rawseti; + lua_setmetatable; + lua_setfenv; + + /* call/load */ + lua_call; + lua_pcall; + lua_cpcall; + lua_load; + lua_dump; + + /* coroutine */ + lua_yield; + lua_resume; + lua_status; + + /* misc */ + lua_gc; + lua_error; + lua_next; + lua_concat; + lua_getallocf; + lua_setallocf; + lua_setlevel; + + /* debug */ + lua_getstack; + lua_getinfo; + lua_getlocal; + lua_setlocal; + lua_getupvalue; + lua_setupvalue; + lua_sethook; + lua_gethook; + lua_gethookmask; + lua_gethookcount; + + /* auxlib */ + luaI_openlib; + luaL_register; + luaL_getmetafield; + luaL_callmeta; + luaL_typerror; + luaL_argerror; + luaL_checklstring; luaL_optlstring; + luaL_checknumber; + luaL_optnumber; + luaL_checkinteger; + luaL_optinteger; + luaL_checkstack; + luaL_checktype; + luaL_checkany; + luaL_newmetatable; + luaL_checkudata; + luaL_where; + luaL_error; + luaL_checkoption; + luaL_ref; + luaL_unref; + luaL_loadfile; + luaL_loadbuffer; + luaL_loadstring; + luaL_newstate; + luaL_gsub; + luaL_findtable; + luaL_buffinit; + luaL_prepbuffer; + luaL_addlstring; + luaL_addstring; + luaL_addvalue; + luaL_pushresult; + luaL_openlibs; + + /* standard libs */ + luaopen_base; + luaopen_math; + luaopen_string; + luaopen_table; + luaopen_io; + luaopen_os; + luaopen_package; + luaopen_debug; };