/* * Remmina - The GTK+ Remote Desktop Client * Copyright (C) 2014-2023 Antenore Gatta, Giovanni Panozzo, Mathias Winterhalter (ToolsDevler) * * 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 "python_wrapper_common.h" #include #include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" #include "pygobject.h" #pragma GCC diagnostic pop //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // D E C L A R A T I O N S //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /** * A cache to store the last result that has been returned by the Python code using CallPythonMethod * (@see python_wrapper_common.h) */ static PyObject* __last_result; static GPtrArray* plugin_map = NULL; static RemminaPluginService* remmina_plugin_service; const char* ATTR_NAME = "name"; const char* ATTR_ICON_NAME = "icon_name"; const char* ATTR_DESCRIPTION = "description"; const char* ATTR_VERSION = "version"; const char* ATTR_ICON_NAME_SSH = "icon_name_ssh"; const char* ATTR_FEATURES = "features"; const char* ATTR_BASIC_SETTINGS = "basic_settings"; const char* ATTR_ADVANCED_SETTINGS = "advanced_settings"; const char* ATTR_SSH_SETTING = "ssh_setting"; const char* ATTR_EXPORT_HINTS = "export_hints"; const char* ATTR_PREF_LABEL = "pref_label"; const char* ATTR_INIT_ORDER = "init_order"; /** * To prevent some memory related attacks or accidental allocation of an excessive amount of byes, this limit should * always be used to check for a sane amount of bytes to allocate. */ static const int REASONABLE_LIMIT_FOR_MALLOC = 1024 * 1024; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // A P I //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// PyObject* python_wrapper_last_result(void) { TRACE_CALL(__func__); return __last_result; } PyObject* python_wrapper_last_result_set(PyObject* last_result) { TRACE_CALL(__func__); return __last_result = last_result; } gboolean python_wrapper_check_error(void) { TRACE_CALL(__func__); if (PyErr_Occurred()) { PyErr_Print(); return TRUE; } return FALSE; } void python_wrapper_log_method_call(PyObject* instance, const char* method) { TRACE_CALL(__func__); assert(instance); assert(method); g_print("Python@%ld: %s.%s(...) -> %s\n", PyObject_Hash(instance), instance->ob_type->tp_name, method, PyUnicode_AsUTF8(PyObject_Str(python_wrapper_last_result()))); } long python_wrapper_get_attribute_long(PyObject* instance, const char* attr_name, long def) { TRACE_CALL(__func__); assert(instance); assert(attr_name); PyObject* attr = PyObject_GetAttrString(instance, attr_name); if (attr && PyLong_Check(attr)) { return PyLong_AsLong(attr); } return def; } gboolean python_wrapper_check_attribute(PyObject* instance, const char* attr_name) { TRACE_CALL(__func__); assert(instance); assert(attr_name); if (PyObject_HasAttrString(instance, attr_name)) { return TRUE; } g_printerr("Python plugin instance is missing member: %s\n", attr_name); return FALSE; } void* python_wrapper_malloc(int bytes) { TRACE_CALL(__func__); assert(bytes > 0); assert(bytes <= REASONABLE_LIMIT_FOR_MALLOC); void* result = malloc(bytes); if (!result) { g_printerr("Unable to allocate %d bytes in memory!\n", bytes); perror("malloc"); } return result; } char* python_wrapper_copy_string_from_python(PyObject* string, Py_ssize_t len) { TRACE_CALL(__func__); char* result = NULL; if (len <= 0 || string == NULL) { return NULL; } const char* py_str = PyUnicode_AsUTF8(string); if (py_str) { const int label_size = sizeof(char) * (len + 1); result = (char*)python_wrapper_malloc(label_size); result[len] = '\0'; memcpy(result, py_str, len); } return result; } void python_wrapper_set_service(RemminaPluginService* service) { remmina_plugin_service = service; } RemminaPluginService* python_wrapper_get_service(void) { return remmina_plugin_service; } void python_wrapper_add_plugin(PyPlugin* plugin) { TRACE_CALL(__func__); if (!plugin_map) { plugin_map = g_ptr_array_new(); } PyPlugin* test = python_wrapper_get_plugin(plugin->generic_plugin->name); if (test) { g_printerr("A plugin named '%s' has already been registered! Skipping...", plugin->generic_plugin->name); } else { g_ptr_array_add(plugin_map, plugin); } } RemminaTypeHint python_wrapper_to_generic(PyObject* field, gpointer* target) { TRACE_CALL(__func__); if (PyUnicode_Check(field)) { Py_ssize_t len = PyUnicode_GetLength(field); if (len > 0) { *target = python_wrapper_copy_string_from_python(field, len); } else { *target = ""; } return REMMINA_TYPEHINT_STRING; } else if (PyBool_Check(field)) { *target = python_wrapper_malloc(sizeof(long)); long* long_target = (long*)target; *long_target = PyLong_AsLong(field); return REMMINA_TYPEHINT_BOOLEAN; } else if (PyLong_Check(field)) { *target = python_wrapper_malloc(sizeof(long)); long* long_target = (long*)target; *long_target = PyLong_AsLong(field); return REMMINA_TYPEHINT_SIGNED; } else if (PyTuple_Check(field)) { Py_ssize_t len = PyTuple_Size(field); if (len) { gpointer* dest = (gpointer*)python_wrapper_malloc(sizeof(gpointer) * (len + 1)); memset(dest, 0, sizeof(gpointer) * (len + 1)); for (Py_ssize_t i = 0; i < len; ++i) { PyObject* item = PyTuple_GetItem(field, i); python_wrapper_to_generic(item, dest + i); } *target = dest; } return REMMINA_TYPEHINT_TUPLE; } *target = NULL; return REMMINA_TYPEHINT_UNDEFINED; } PyPlugin* python_wrapper_get_plugin(const gchar* name) { TRACE_CALL(__func__); assert(plugin_map); assert(name); for (gint i = 0; i < plugin_map->len; ++i) { PyPlugin* plugin = (PyPlugin*)g_ptr_array_index(plugin_map, i); if (plugin->generic_plugin && plugin->generic_plugin->name && g_str_equal(name, plugin->generic_plugin->name)) { return plugin; } } return NULL; } PyPlugin* python_wrapper_get_plugin_by_protocol_widget(RemminaProtocolWidget* gp) { TRACE_CALL(__func__); assert(plugin_map); assert(gp); const gchar* name = python_wrapper_get_service()->protocol_widget_get_name(gp); if (!name) { return NULL; } return python_wrapper_get_plugin(name); } void init_pygobject() { pygobject_init(-1, -1, -1); } GtkWidget* new_pywidget(GObject* obj) { return (GtkWidget*)pygobject_new(obj); } GtkWidget* get_pywidget(PyObject* obj) { return (GtkWidget*)pygobject_get(obj); }