diff options
Diffstat (limited to 'plugins/python_wrapper/python_wrapper_plugin.c')
-rw-r--r-- | plugins/python_wrapper/python_wrapper_plugin.c | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/plugins/python_wrapper/python_wrapper_plugin.c b/plugins/python_wrapper/python_wrapper_plugin.c new file mode 100644 index 0000000..bd57ffc --- /dev/null +++ b/plugins/python_wrapper/python_wrapper_plugin.c @@ -0,0 +1,220 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2014-2023 Antenore Gatta, Giovanni Panozzo + * Copyright (C) 2023-2024 Hiroyuki Tanaka, Sunil Bhat + * + * This program 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 of the License, or + * (at your option) any later version. + * + * This program 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; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N C L U D E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include <gtk/gtk.h> +#ifdef GDK_WINDOWING_X11 +#include <gdk/gdkx.h> +#elif defined(GDK_WINDOWING_WAYLAND) +#include <gdk/gdkwayland.h> +#endif +#include "config.h" +#include "remmina/plugin.h" +#include "remmina/remmina_trace_calls.h" +#include "python_wrapper_common.h" +#include "python_wrapper_plugin.h" +#include "python_wrapper_remmina.h" +#include "python_wrapper_protocol_widget.h" + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// D E C L A R A T I O N S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * An null terminated array of commands that are executed after the initialization of the Python engine. Every entry + * represents a line of Python code. + */ + +static const gchar* python_wrapper_supported_extensions[] = {"py", NULL}; + +static RemminaLanguageWrapperPlugin remmina_python_wrapper = +{ + REMMINA_PLUGIN_TYPE_LANGUAGE_WRAPPER, // Type + "Python Wrapper", // Name + "Enables Python plugins for Remmina", // Description + GETTEXT_PACKAGE, // Translation domain + "Python Wrapper for Remmina v0.1", // Version number + python_wrapper_supported_extensions, // Supported extentions + python_wrapper_init, // Plugin initialization + python_wrapper_load, // Plugin load Python file +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// U T I L S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * @brief Extracts the filename without extension from a path. + * + * @param in The string to extract the filename from + * @param out The resulting filename without extension (must point to allocated memory). + * + * @return The length of the filename extracted. + */ +static int basename_no_ext(const char* in, char** out) +{ + TRACE_CALL(__func__); + + assert(in); + assert(out); + + const char* base = strrchr(in, '/'); + if (base) + { + base++; + } else { + return 0; + } + + const char* base_end = strrchr(base, '.'); + if (!base_end) + { + base_end = base + strlen(base); + } + + const int length = base_end - base; + const int memsize = sizeof(char*) * ((length) + 1); + + *out = (char*)python_wrapper_malloc(memsize); + + memset(*out, 0, memsize); + strncpy(*out, base, length); + (*out)[length] = '\0'; + + return length; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + +gboolean python_wrapper_init(RemminaLanguageWrapperPlugin* plugin) +{ + TRACE_CALL(__func__); + assert(plugin); + return TRUE; +} + +gboolean python_wrapper_load(RemminaLanguageWrapperPlugin* plugin, const char* name) +{ + TRACE_CALL(__func__); + + assert(plugin); + assert(name); + + + char* filename = NULL; + if (basename_no_ext(name, &filename) == 0) + { + g_printerr("[%s:%d]: Can not extract filename from '%s'!\n", __FILE__, __LINE__, name); + return FALSE; + } + + PyObject* plugin_name = PyUnicode_DecodeFSDefault(filename); + + if (!plugin_name) + { + free(filename); + g_printerr("[%s:%d]: Error converting plugin filename to PyUnicode!\n", __FILE__, __LINE__); + return FALSE; + } + + wchar_t* program_name = NULL; + Py_ssize_t len = PyUnicode_AsWideChar(plugin_name, program_name, 0); + if (len <= 0) + { + free(filename); + g_printerr("[%s:%d]: Failed allocating %lu bytes!\n", __FILE__, __LINE__, (sizeof(wchar_t) * len)); + return FALSE; + } + + program_name = (wchar_t*)python_wrapper_malloc(sizeof(wchar_t) * len); + if (!program_name) + { + free(filename); + g_printerr("[%s:%d]: Failed allocating %lu bytes!\n", __FILE__, __LINE__, (sizeof(wchar_t) * len)); + return FALSE; + } + + PyUnicode_AsWideChar(plugin_name, program_name, len); + + PySys_SetArgv(1, &program_name); + + if (PyImport_Import(plugin_name)) + { + free(filename); + return TRUE; + } + + g_print("[%s:%d]: Failed to load python plugin file: '%s'\n", __FILE__, __LINE__, name); + PyErr_Print(); + free(filename); + + return FALSE; +} + + +G_MODULE_EXPORT gboolean remmina_plugin_entry(RemminaPluginService *service) +{ + TRACE_CALL(__func__); + python_wrapper_set_service(service); + + python_wrapper_module_init(); + Py_InitializeEx(0); + + gchar* plugin_dir; + plugin_dir = g_build_path("/", g_get_user_config_dir(), "remmina", "plugins", NULL); + + gchar* python_command = g_strdup_printf("sys.path.append('%s')", plugin_dir); + + char* python_init_commands[] = { "import sys", python_command, "sys.path.append('" REMMINA_RUNTIME_PLUGINDIR "')", NULL }; // Sentinel}; + + for (char** ptr = python_init_commands; *ptr; ++ptr) + { + PyRun_SimpleString(*ptr); + } + + g_free(python_command); + g_free(plugin_dir); + + python_wrapper_protocol_widget_init(); + + service->register_plugin((RemminaPlugin*)&remmina_python_wrapper); + + return TRUE; +} |