summaryrefslogtreecommitdiffstats
path: root/plugins/python_wrapper/python_wrapper_plugin.c
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/python_wrapper/python_wrapper_plugin.c')
-rw-r--r--plugins/python_wrapper/python_wrapper_plugin.c220
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;
+}