summaryrefslogtreecommitdiffstats
path: root/plugins/python_wrapper
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/python_wrapper')
-rw-r--r--plugins/python_wrapper/CMakeLists.txt68
-rw-r--r--plugins/python_wrapper/pygobject.h669
-rw-r--r--plugins/python_wrapper/python_wrapper_common.c327
-rw-r--r--plugins/python_wrapper/python_wrapper_common.h295
-rw-r--r--plugins/python_wrapper/python_wrapper_entry.c100
-rw-r--r--plugins/python_wrapper/python_wrapper_entry.h63
-rw-r--r--plugins/python_wrapper/python_wrapper_file.c160
-rw-r--r--plugins/python_wrapper/python_wrapper_file.h63
-rw-r--r--plugins/python_wrapper/python_wrapper_plugin.c220
-rw-r--r--plugins/python_wrapper/python_wrapper_plugin.h80
-rw-r--r--plugins/python_wrapper/python_wrapper_pref.c104
-rw-r--r--plugins/python_wrapper/python_wrapper_pref.h63
-rw-r--r--plugins/python_wrapper/python_wrapper_protocol.c315
-rw-r--r--plugins/python_wrapper/python_wrapper_protocol.h109
-rw-r--r--plugins/python_wrapper/python_wrapper_protocol_widget.c845
-rw-r--r--plugins/python_wrapper/python_wrapper_protocol_widget.h65
-rw-r--r--plugins/python_wrapper/python_wrapper_remmina.c1241
-rw-r--r--plugins/python_wrapper/python_wrapper_remmina.h93
-rw-r--r--plugins/python_wrapper/python_wrapper_remmina_file.c205
-rw-r--r--plugins/python_wrapper/python_wrapper_remmina_file.h63
-rw-r--r--plugins/python_wrapper/python_wrapper_secret.c138
-rw-r--r--plugins/python_wrapper/python_wrapper_secret.h68
-rw-r--r--plugins/python_wrapper/python_wrapper_tool.c84
-rw-r--r--plugins/python_wrapper/python_wrapper_tool.h69
24 files changed, 5507 insertions, 0 deletions
diff --git a/plugins/python_wrapper/CMakeLists.txt b/plugins/python_wrapper/CMakeLists.txt
new file mode 100644
index 0000000..d77eac5
--- /dev/null
+++ b/plugins/python_wrapper/CMakeLists.txt
@@ -0,0 +1,68 @@
+# Remmina - The GTK+ Remote Desktop Client
+#
+# Copyright (C) 2016-2018 Denis Ollier
+#
+# 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.
+
+set(REMMINA_PLUGIN_PYTHON_WRAPPER_SRCS
+ python_wrapper_common.c
+ python_wrapper_common.h
+ python_wrapper_entry.c
+ python_wrapper_entry.h
+ python_wrapper_file.c
+ python_wrapper_file.h
+ python_wrapper_plugin.c
+ python_wrapper_plugin.h
+ python_wrapper_pref.c
+ python_wrapper_pref.h
+ python_wrapper_protocol.c
+ python_wrapper_protocol.h
+ python_wrapper_protocol_widget.c
+ python_wrapper_protocol_widget.h
+ python_wrapper_remmina.c
+ python_wrapper_remmina.h
+ python_wrapper_remmina_file.c
+ python_wrapper_remmina_file.h
+ python_wrapper_secret.c
+ python_wrapper_secret.h
+ python_wrapper_tool.c
+ python_wrapper_tool.h
+ )
+
+add_library(remmina-plugin-python_wrapper MODULE ${REMMINA_PLUGIN_PYTHON_WRAPPER_SRCS})
+set_target_properties(remmina-plugin-python_wrapper PROPERTIES PREFIX "")
+set_target_properties(remmina-plugin-python_wrapper PROPERTIES NO_SONAME 1)
+
+include_directories(${REMMINA_COMMON_INCLUDE_DIRS} ${PYTHON_WRAPPER_INCLUDE_DIRS})
+target_link_libraries(remmina-plugin-python_wrapper ${REMMINA_COMMON_LIBRARIES} ${PYTHON_WRAPPER_LIBRARIES})
+
+install(TARGETS remmina-plugin-python_wrapper DESTINATION ${REMMINA_PLUGINDIR})
+
+target_include_directories(remmina-plugin-python_wrapper PRIVATE ${PYTHON_INCLUDE_DIRS})
+target_link_libraries(remmina-plugin-python_wrapper ${PYTHON_LIBRARIES})
diff --git a/plugins/python_wrapper/pygobject.h b/plugins/python_wrapper/pygobject.h
new file mode 100644
index 0000000..145d1c6
--- /dev/null
+++ b/plugins/python_wrapper/pygobject.h
@@ -0,0 +1,669 @@
+/* -*- Mode: C; c-basic-offset: 4 -*- */
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * 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.
+ *
+ */
+
+#ifndef _PYGOBJECT_H_
+#define _PYGOBJECT_H_
+
+#include <Python.h>
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+/* PyGClosure is a _private_ structure */
+typedef void (* PyClosureExceptionHandler) (GValue *ret, guint n_param_values, const GValue *params);
+typedef struct _PyGClosure PyGClosure;
+typedef struct _PyGObjectData PyGObjectData;
+
+struct _PyGClosure {
+ GClosure closure;
+ PyObject *callback;
+ PyObject *extra_args; /* tuple of extra args to pass to callback */
+ PyObject *swap_data; /* other object for gtk_signal_connect__object */
+ PyClosureExceptionHandler exception_handler;
+};
+
+typedef enum {
+ PYGOBJECT_USING_TOGGLE_REF = 1 << 0,
+ PYGOBJECT_IS_FLOATING_REF = 1 << 1
+} PyGObjectFlags;
+
+ /* closures is just an alias for what is found in the
+ * PyGObjectData */
+typedef struct {
+ PyObject_HEAD
+ GObject *obj;
+ PyObject *inst_dict; /* the instance dictionary -- must be last */
+ PyObject *weakreflist; /* list of weak references */
+
+ /*< private >*/
+ /* using union to preserve ABI compatibility (structure size
+ * must not change) */
+ union {
+ GSList *closures; /* stale field; no longer updated DO-NOT-USE! */
+ PyGObjectFlags flags;
+ } private_flags;
+
+} PyGObject;
+
+#define pygobject_get(v) (((PyGObject *)(v))->obj)
+#define pygobject_check(v,base) (PyObject_TypeCheck(v,base))
+
+typedef struct {
+ PyObject_HEAD
+ gpointer boxed;
+ GType gtype;
+ gboolean free_on_dealloc;
+} PyGBoxed;
+
+#define pyg_boxed_get(v,t) ((t *)((PyGBoxed *)(v))->boxed)
+#define pyg_boxed_check(v,typecode) (PyObject_TypeCheck(v, &PyGBoxed_Type) && ((PyGBoxed *)(v))->gtype == typecode)
+
+typedef struct {
+ PyObject_HEAD
+ gpointer pointer;
+ GType gtype;
+} PyGPointer;
+
+#define pyg_pointer_get(v,t) ((t *)((PyGPointer *)(v))->pointer)
+#define pyg_pointer_check(v,typecode) (PyObject_TypeCheck(v, &PyGPointer_Type) && ((PyGPointer *)(v))->gtype == typecode)
+
+typedef void (*PyGFatalExceptionFunc) (void);
+typedef void (*PyGThreadBlockFunc) (void);
+
+typedef struct {
+ PyObject_HEAD
+ GParamSpec *pspec;
+} PyGParamSpec;
+
+#define PyGParamSpec_Get(v) (((PyGParamSpec *)v)->pspec)
+#define PyGParamSpec_Check(v) (PyObject_TypeCheck(v, &PyGParamSpec_Type))
+
+typedef int (*PyGClassInitFunc) (gpointer gclass, PyTypeObject *pyclass);
+typedef PyTypeObject * (*PyGTypeRegistrationFunction) (const gchar *name,
+ gpointer data);
+
+struct _PyGObject_Functions {
+ /*
+ * All field names in here are considered private,
+ * use the macros below instead, which provides stability
+ */
+ void (* register_class)(PyObject *dict, const gchar *class_name,
+ GType gtype, PyTypeObject *type, PyObject *bases);
+ void (* register_wrapper)(PyObject *self);
+ PyTypeObject *(* lookup_class)(GType type);
+ PyObject *(* newgobj)(GObject *obj);
+
+ GClosure *(* closure_new)(PyObject *callback, PyObject *extra_args,
+ PyObject *swap_data);
+ void (* object_watch_closure)(PyObject *self, GClosure *closure);
+ GDestroyNotify destroy_notify;
+
+ GType (* type_from_object)(PyObject *obj);
+ PyObject *(* type_wrapper_new)(GType type);
+
+ gint (* enum_get_value)(GType enum_type, PyObject *obj, gint *val);
+ gint (* flags_get_value)(GType flag_type, PyObject *obj, gint *val);
+ void (* register_gtype_custom)(GType gtype,
+ PyObject *(* from_func)(const GValue *value),
+ int (* to_func)(GValue *value, PyObject *obj));
+ int (* value_from_pyobject)(GValue *value, PyObject *obj);
+ PyObject *(* value_as_pyobject)(const GValue *value, gboolean copy_boxed);
+
+ void (* register_interface)(PyObject *dict, const gchar *class_name,
+ GType gtype, PyTypeObject *type);
+
+ PyTypeObject *boxed_type;
+ void (* register_boxed)(PyObject *dict, const gchar *class_name,
+ GType boxed_type, PyTypeObject *type);
+ PyObject *(* boxed_new)(GType boxed_type, gpointer boxed,
+ gboolean copy_boxed, gboolean own_ref);
+
+ PyTypeObject *pointer_type;
+ void (* register_pointer)(PyObject *dict, const gchar *class_name,
+ GType pointer_type, PyTypeObject *type);
+ PyObject *(* pointer_new)(GType boxed_type, gpointer pointer);
+
+ void (* enum_add_constants)(PyObject *module, GType enum_type,
+ const gchar *strip_prefix);
+ void (* flags_add_constants)(PyObject *module, GType flags_type,
+ const gchar *strip_prefix);
+
+ const gchar *(* constant_strip_prefix)(const gchar *name,
+ const gchar *strip_prefix);
+
+ gboolean (* error_check)(GError **error);
+
+ /* hooks to register handlers for getting GDK threads to cooperate
+ * with python threading */
+ void (* set_thread_block_funcs) (PyGThreadBlockFunc block_threads_func,
+ PyGThreadBlockFunc unblock_threads_func);
+ PyGThreadBlockFunc block_threads;
+ PyGThreadBlockFunc unblock_threads;
+ PyTypeObject *paramspec_type;
+ PyObject *(* paramspec_new)(GParamSpec *spec);
+ GParamSpec *(*paramspec_get)(PyObject *tuple);
+ int (*pyobj_to_unichar_conv)(PyObject *pyobj, void* ptr);
+ gboolean (*parse_constructor_args)(GType obj_type,
+ char **arg_names,
+ char **prop_names,
+ GParameter *params,
+ guint *nparams,
+ PyObject **py_args);
+ PyObject *(* param_gvalue_as_pyobject) (const GValue* gvalue,
+ gboolean copy_boxed,
+ const GParamSpec* pspec);
+ int (* gvalue_from_param_pyobject) (GValue* value,
+ PyObject* py_obj,
+ const GParamSpec* pspec);
+ PyTypeObject *enum_type;
+ PyObject *(*enum_add)(PyObject *module,
+ const char *type_name_,
+ const char *strip_prefix,
+ GType gtype);
+ PyObject* (*enum_from_gtype)(GType gtype, int value);
+
+ PyTypeObject *flags_type;
+ PyObject *(*flags_add)(PyObject *module,
+ const char *type_name_,
+ const char *strip_prefix,
+ GType gtype);
+ PyObject* (*flags_from_gtype)(GType gtype, int value);
+
+ gboolean threads_enabled;
+ int (*enable_threads) (void);
+
+ int (*gil_state_ensure) (void);
+ void (*gil_state_release) (int flag);
+
+ void (*register_class_init) (GType gtype, PyGClassInitFunc class_init);
+ void (*register_interface_info) (GType gtype, const GInterfaceInfo *info);
+ void (*closure_set_exception_handler) (GClosure *closure, PyClosureExceptionHandler handler);
+
+ void (*add_warning_redirection) (const char *domain,
+ PyObject *warning);
+ void (*disable_warning_redirections) (void);
+ void (*type_register_custom)(const gchar *type_name,
+ PyGTypeRegistrationFunction callback,
+ gpointer data);
+ gboolean (*gerror_exception_check) (GError **error);
+ PyObject* (*option_group_new) (GOptionGroup *group);
+ GType (* type_from_object_strict) (PyObject *obj, gboolean strict);
+};
+
+#ifndef _INSIDE_PYGOBJECT_
+
+#if defined(NO_IMPORT) || defined(NO_IMPORT_PYGOBJECT)
+extern struct _PyGObject_Functions *_PyGObject_API;
+#else
+struct _PyGObject_Functions *_PyGObject_API;
+#endif
+
+#define pygobject_register_class (_PyGObject_API->register_class)
+#define pygobject_register_wrapper (_PyGObject_API->register_wrapper)
+#define pygobject_lookup_class (_PyGObject_API->lookup_class)
+#define pygobject_new (_PyGObject_API->newgobj)
+#define pyg_closure_new (_PyGObject_API->closure_new)
+#define pygobject_watch_closure (_PyGObject_API->object_watch_closure)
+#define pyg_closure_set_exception_handler (_PyGObject_API->closure_set_exception_handler)
+#define pyg_destroy_notify (_PyGObject_API->destroy_notify)
+#define pyg_type_from_object_strict (_PyGObject_API->type_from_object_strict)
+#define pyg_type_from_object (_PyGObject_API->type_from_object)
+#define pyg_type_wrapper_new (_PyGObject_API->type_wrapper_new)
+#define pyg_enum_get_value (_PyGObject_API->enum_get_value)
+#define pyg_flags_get_value (_PyGObject_API->flags_get_value)
+#define pyg_register_gtype_custom (_PyGObject_API->register_gtype_custom)
+#define pyg_value_from_pyobject (_PyGObject_API->value_from_pyobject)
+#define pyg_value_as_pyobject (_PyGObject_API->value_as_pyobject)
+#define pyg_register_interface (_PyGObject_API->register_interface)
+#define PyGBoxed_Type (*_PyGObject_API->boxed_type)
+#define pyg_register_boxed (_PyGObject_API->register_boxed)
+#define pyg_boxed_new (_PyGObject_API->boxed_new)
+#define PyGPointer_Type (*_PyGObject_API->pointer_type)
+#define pyg_register_pointer (_PyGObject_API->register_pointer)
+#define pyg_pointer_new (_PyGObject_API->pointer_new)
+#define pyg_enum_add_constants (_PyGObject_API->enum_add_constants)
+#define pyg_flags_add_constants (_PyGObject_API->flags_add_constants)
+#define pyg_constant_strip_prefix (_PyGObject_API->constant_strip_prefix)
+#define pyg_error_check (_PyGObject_API->error_check)
+#define pyg_set_thread_block_funcs (_PyGObject_API->set_thread_block_funcs)
+#define PyGParamSpec_Type (*_PyGObject_API->paramspec_type)
+#define pyg_param_spec_new (_PyGObject_API->paramspec_new)
+#define pyg_param_spec_from_object (_PyGObject_API->paramspec_get)
+#define pyg_pyobj_to_unichar_conv (_PyGObject_API->pyobj_to_unichar_conv)
+#define pyg_parse_constructor_args (_PyGObject_API->parse_constructor_args)
+#define pyg_param_gvalue_as_pyobject (_PyGObject_API->value_as_pyobject)
+#define pyg_param_gvalue_from_pyobject (_PyGObject_API->gvalue_from_param_pyobject)
+#define PyGEnum_Type (*_PyGObject_API->enum_type)
+#define pyg_enum_add (_PyGObject_API->enum_add)
+#define pyg_enum_from_gtype (_PyGObject_API->enum_from_gtype)
+#define PyGFlags_Type (*_PyGObject_API->flags_type)
+#define pyg_flags_add (_PyGObject_API->flags_add)
+#define pyg_flags_from_gtype (_PyGObject_API->flags_from_gtype)
+#define pyg_enable_threads (_PyGObject_API->enable_threads)
+#define pyg_gil_state_ensure (_PyGObject_API->gil_state_ensure)
+#define pyg_gil_state_release (_PyGObject_API->gil_state_release)
+#define pyg_register_class_init (_PyGObject_API->register_class_init)
+#define pyg_register_interface_info (_PyGObject_API->register_interface_info)
+#define pyg_add_warning_redirection (_PyGObject_API->add_warning_redirection)
+#define pyg_disable_warning_redirections (_PyGObject_API->disable_warning_redirections)
+#define pyg_type_register_custom_callback (_PyGObject_API->type_register_custom)
+#define pyg_gerror_exception_check (_PyGObject_API->gerror_exception_check)
+#define pyg_option_group_new (_PyGObject_API->option_group_new)
+
+#define pyg_block_threads() G_STMT_START { \
+ if (_PyGObject_API->block_threads != NULL) \
+ (* _PyGObject_API->block_threads)(); \
+ } G_STMT_END
+#define pyg_unblock_threads() G_STMT_START { \
+ if (_PyGObject_API->unblock_threads != NULL) \
+ (* _PyGObject_API->unblock_threads)(); \
+ } G_STMT_END
+
+#define pyg_threads_enabled (_PyGObject_API->threads_enabled)
+
+#define pyg_begin_allow_threads \
+ G_STMT_START { \
+ PyThreadState *_save = NULL; \
+ if (_PyGObject_API->threads_enabled) \
+ _save = PyEval_SaveThread();
+#define pyg_end_allow_threads \
+ if (_PyGObject_API->threads_enabled) \
+ PyEval_RestoreThread(_save); \
+ } G_STMT_END
+
+
+/**
+ * pygobject_init:
+ * @req_major: minimum version major number, or -1
+ * @req_minor: minimum version minor number, or -1
+ * @req_micro: minimum version micro number, or -1
+ *
+ * Imports and initializes the 'gobject' python module. Can
+ * optionally check for a required minimum version if @req_major,
+ * @req_minor, and @req_micro are all different from -1.
+ *
+ * Returns: a new reference to the gobject module on success, NULL in
+ * case of failure (and raises ImportError).
+ **/
+static inline PyObject *
+pygobject_init(int req_major, int req_minor, int req_micro)
+{
+ PyObject *gobject, *cobject;
+
+ gobject = PyImport_ImportModule("gi._gobject");
+ if (!gobject) {
+ if (PyErr_Occurred())
+ {
+ PyObject *type, *value, *traceback;
+ PyObject *py_orig_exc;
+ PyErr_Fetch(&type, &value, &traceback);
+ py_orig_exc = PyObject_Repr(value);
+ Py_XDECREF(type);
+ Py_XDECREF(value);
+ Py_XDECREF(traceback);
+
+
+#if PY_VERSION_HEX < 0x03000000
+ PyErr_Format(PyExc_ImportError,
+ "could not import gobject (error was: %s)",
+ PyString_AsString(py_orig_exc));
+#else
+ {
+ /* Can not use PyErr_Format because it doesn't have
+ * a format string for dealing with PyUnicode objects
+ * like PyUnicode_FromFormat has
+ */
+ PyObject *errmsg = PyUnicode_FromFormat("could not import gobject (error was: %U)",
+ py_orig_exc);
+
+ if (errmsg) {
+ PyErr_SetObject(PyExc_ImportError,
+ errmsg);
+ Py_DECREF(errmsg);
+ }
+ /* if errmsg is NULL then we might have OOM
+ * PyErr should already be set and trying to
+ * return our own error would be futile
+ */
+ }
+#endif
+ Py_DECREF(py_orig_exc);
+ } else {
+ PyErr_SetString(PyExc_ImportError,
+ "could not import gobject (no error given)");
+ }
+ return NULL;
+ }
+
+ cobject = PyObject_GetAttrString(gobject, "_PyGObject_API");
+#if PY_VERSION_HEX >= 0x03000000
+ if (cobject && PyCapsule_CheckExact(cobject))
+ _PyGObject_API = (struct _PyGObject_Functions *) PyCapsule_GetPointer(cobject, "gobject._PyGObject_API");
+
+#else
+ if (cobject && PyCObject_Check(cobject))
+ _PyGObject_API = (struct _PyGObject_Functions *) PyCObject_AsVoidPtr(cobject);
+#endif
+ else {
+ PyErr_SetString(PyExc_ImportError,
+ "could not import gobject (could not find _PyGObject_API object)");
+ Py_DECREF(gobject);
+ return NULL;
+ }
+
+ if (req_major != -1)
+ {
+ int found_major, found_minor, found_micro;
+ PyObject *version;
+
+ version = PyObject_GetAttrString(gobject, "pygobject_version");
+ if (!version) {
+ PyErr_SetString(PyExc_ImportError,
+ "could not import gobject (version too old)");
+ Py_DECREF(gobject);
+ return NULL;
+ }
+ if (!PyArg_ParseTuple(version, "iii",
+ &found_major, &found_minor, &found_micro)) {
+ PyErr_SetString(PyExc_ImportError,
+ "could not import gobject (version has invalid format)");
+ Py_DECREF(version);
+ Py_DECREF(gobject);
+ return NULL;
+ }
+ Py_DECREF(version);
+ if (req_major != found_major ||
+ req_minor > found_minor ||
+ (req_minor == found_minor && req_micro > found_micro)) {
+ PyErr_Format(PyExc_ImportError,
+ "could not import gobject (version mismatch, %d.%d.%d is required, "
+ "found %d.%d.%d)", req_major, req_minor, req_micro,
+ found_major, found_minor, found_micro);
+ Py_DECREF(gobject);
+ return NULL;
+ }
+ }
+ return gobject;
+}
+
+/**
+ * PYLIST_FROMGLIBLIST:
+ * @type: the type of the GLib list e.g. #GList or #GSList
+ * @prefix: the prefix of functions that manipulate a list of the type
+ * given by type.
+ *
+ * A macro that creates a type specific code block which converts a GLib
+ * list (#GSList or #GList) to a Python list. The first two args of the macro
+ * are used to specify the type and list function prefix so that the type
+ * specific macros can be generated.
+ *
+ * The rest of the args are for the standard args for the type specific
+ * macro(s) created from this macro.
+ */
+ #define PYLIST_FROMGLIBLIST(type,prefix,py_list,list,item_convert_func,\
+ list_free,list_item_free) \
+G_STMT_START \
+{ \
+ gint i, len; \
+ PyObject *item; \
+ void (*glib_list_free)(type*) = list_free; \
+ GFunc glib_list_item_free = (GFunc)list_item_free; \
+ \
+ len = prefix##_length(list); \
+ py_list = PyList_New(len); \
+ for (i = 0; i < len; i++) { \
+ gpointer list_item = prefix##_nth_data(list, i); \
+ \
+ item = item_convert_func; \
+ PyList_SetItem(py_list, i, item); \
+ } \
+ if (glib_list_item_free != NULL) \
+ prefix##_foreach(list, glib_list_item_free, NULL); \
+ if (glib_list_free != NULL) \
+ glib_list_free(list); \
+} G_STMT_END
+
+/**
+ * PYLIST_FROMGLIST:
+ * @py_list: the name of the Python list
+ *
+ * @list: the #GList to be converted to a Python list
+ *
+ * @item_convert_func: the function that converts a list item to a Python
+ * object. The function must refer to the list item using "@list_item" and
+ * must return a #PyObject* object. An example conversion function is:
+ * [[
+ * PyString_FromString(list_item)
+ * ]]
+ * A more elaborate function is:
+ * [[
+ * pyg_boxed_new(GTK_TYPE_RECENT_INFO, list_item, TRUE, TRUE)
+ * ]]
+ * @list_free: the name of a function that takes a single arg (the list) and
+ * frees its memory. Can be NULL if the list should not be freed. An example
+ * is:
+ * [[
+ * g_list_free
+ * ]]
+ * @list_item_free: the name of a #GFunc function that frees the memory used
+ * by the items in the list or %NULL if the list items do not have to be
+ * freed. A simple example is:
+ * [[
+ * g_free
+ * ]]
+ *
+ * A macro that adds code that converts a #GList to a Python list.
+ *
+ */
+#define PYLIST_FROMGLIST(py_list,list,item_convert_func,list_free,\
+ list_item_free) \
+ PYLIST_FROMGLIBLIST(GList,g_list,py_list,list,item_convert_func,\
+ list_free,list_item_free)
+
+/**
+ * PYLIST_FROMGSLIST:
+ * @py_list: the name of the Python list
+ *
+ * @list: the #GSList to be converted to a Python list
+ *
+ * @item_convert_func: the function that converts a list item to a Python
+ * object. The function must refer to the list item using "@list_item" and
+ * must return a #PyObject* object. An example conversion function is:
+ * [[
+ * PyString_FromString(list_item)
+ * ]]
+ * A more elaborate function is:
+ * [[
+ * pyg_boxed_new(GTK_TYPE_RECENT_INFO, list_item, TRUE, TRUE)
+ * ]]
+ * @list_free: the name of a function that takes a single arg (the list) and
+ * frees its memory. Can be %NULL if the list should not be freed. An example
+ * is:
+ * [[
+ * g_list_free
+ * ]]
+ * @list_item_free: the name of a #GFunc function that frees the memory used
+ * by the items in the list or %NULL if the list items do not have to be
+ * freed. A simple example is:
+ * [[
+ * g_free
+ * ]]
+ *
+ * A macro that adds code that converts a #GSList to a Python list.
+ *
+ */
+#define PYLIST_FROMGSLIST(py_list,list,item_convert_func,list_free,\
+ list_item_free) \
+ PYLIST_FROMGLIBLIST(GSList,g_slist,py_list,list,item_convert_func,\
+ list_free,list_item_free)
+
+/**
+ * PYLIST_ASGLIBLIST
+ * @type: the type of the GLib list e.g. GList or GSList
+ * @prefix: the prefix of functions that manipulate a list of the type
+ * given by type e.g. g_list or g_slist
+ *
+ * A macro that creates a type specific code block to be used to convert a
+ * Python list to a GLib list (GList or GSList). The first two args of the
+ * macro are used to specify the type and list function prefix so that the
+ * type specific macros can be generated.
+ *
+ * The rest of the args are for the standard args for the type specific
+ * macro(s) created from this macro.
+ */
+#define PYLIST_ASGLIBLIST(type,prefix,py_list,list,check_func,\
+ convert_func,child_free_func,errormsg,errorreturn) \
+G_STMT_START \
+{ \
+ Py_ssize_t i, n_list; \
+ GFunc glib_child_free_func = (GFunc)child_free_func; \
+ \
+ if (!(py_list = PySequence_Fast(py_list, ""))) { \
+ errormsg; \
+ return errorreturn; \
+ } \
+ n_list = PySequence_Fast_GET_SIZE(py_list); \
+ for (i = 0; i < n_list; i++) { \
+ PyObject *py_item = PySequence_Fast_GET_ITEM(py_list, i); \
+ \
+ if (!check_func) { \
+ if (glib_child_free_func) \
+ prefix##_foreach(list, glib_child_free_func, NULL); \
+ prefix##_free(list); \
+ Py_DECREF(py_list); \
+ errormsg; \
+ return errorreturn; \
+ } \
+ list = prefix##_prepend(list, convert_func); \
+ }; \
+ Py_DECREF(py_list); \
+ list = prefix##_reverse(list); \
+} \
+G_STMT_END
+/**
+ * PYLIST_ASGLIST
+ * @py_list: the Python list to be converted
+ * @list: the #GList list to be converted
+ * @check_func: the expression that takes a #PyObject* arg (must be named
+ * @py_item) and returns an int value indicating if the Python object matches
+ * the required list item type (0 - %False or 1 - %True). An example is:
+ * [[
+ * (PyString_Check(py_item)||PyUnicode_Check(py_item))
+ * ]]
+ * @convert_func: the function that takes a #PyObject* arg (must be named
+ * py_item) and returns a pointer to the converted list object. An example
+ * is:
+ * [[
+ * pygobject_get(py_item)
+ * ]]
+ * @child_free_func: the name of a #GFunc function that frees a GLib list
+ * item or %NULL if the list item does not have to be freed. This function is
+ * used to help free the items in a partially created list if there is an
+ * error. An example is:
+ * [[
+ * g_free
+ * ]]
+ * @errormsg: a function that sets up a Python error message. An example is:
+ * [[
+ * PyErr_SetString(PyExc_TypeError, "strings must be a sequence of" "strings
+ * or unicode objects")
+ * ]]
+ * @errorreturn: the value to return if an error occurs, e.g.:
+ * [[
+ * %NULL
+ * ]]
+ *
+ * A macro that creates code that converts a Python list to a #GList. The
+ * returned list must be freed using the appropriate list free function when
+ * it's no longer needed. If an error occurs the child_free_func is used to
+ * release the memory used by the list items and then the list memory is
+ * freed.
+ */
+#define PYLIST_ASGLIST(py_list,list,check_func,convert_func,child_free_func,\
+ errormsg,errorreturn) \
+ PYLIST_ASGLIBLIST(GList,g_list,py_list,list,check_func,convert_func,\
+ child_free_func,errormsg,errorreturn)
+
+/**
+ * PYLIST_ASGSLIST
+ * @py_list: the Python list to be converted
+ * @list: the #GSList list to be converted
+ * @check_func: the expression that takes a #PyObject* arg (must be named
+ * @py_item) and returns an int value indicating if the Python object matches
+ * the required list item type (0 - %False or 1 - %True). An example is:
+ * [[
+ * (PyString_Check(py_item)||PyUnicode_Check(py_item))
+ * ]]
+ * @convert_func: the function that takes a #PyObject* arg (must be named
+ * py_item) and returns a pointer to the converted list object. An example
+ * is:
+ * [[
+ * pygobject_get(py_item)
+ * ]]
+ * @child_free_func: the name of a #GFunc function that frees a GLib list
+ * item or %NULL if the list item does not have to be freed. This function is
+ * used to help free the items in a partially created list if there is an
+ * error. An example is:
+ * [[
+ * g_free
+ * ]]
+ * @errormsg: a function that sets up a Python error message. An example is:
+ * [[
+ * PyErr_SetString(PyExc_TypeError, "strings must be a sequence of" "strings
+ * or unicode objects")
+ * ]]
+ * @errorreturn: the value to return if an error occurs, e.g.:
+ * [[
+ * %NULL
+ * ]]
+ *
+ * A macro that creates code that converts a Python list to a #GSList. The
+ * returned list must be freed using the appropriate list free function when
+ * it's no longer needed. If an error occurs the child_free_func is used to
+ * release the memory used by the list items and then the list memory is
+ * freed.
+ */
+#define PYLIST_ASGSLIST(py_list,list,check_func,convert_func,child_free_func,\
+ errormsg,errorreturn) \
+ PYLIST_ASGLIBLIST(GSList,g_slist,py_list,list,check_func,convert_func,\
+ child_free_func,errormsg,errorreturn)
+
+#endif /* !_INSIDE_PYGOBJECT_ */
+
+G_END_DECLS
+
+#endif /* !_PYGOBJECT_H_ */
diff --git a/plugins/python_wrapper/python_wrapper_common.c b/plugins/python_wrapper/python_wrapper_common.c
new file mode 100644
index 0000000..e6a2d05
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_common.c
@@ -0,0 +1,327 @@
+/*
+ * 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 <assert.h>
+#include <stdio.h>
+
+#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);
+}
diff --git a/plugins/python_wrapper/python_wrapper_common.h b/plugins/python_wrapper/python_wrapper_common.h
new file mode 100644
index 0000000..8c7a167
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_common.h
@@ -0,0 +1,295 @@
+/*
+ * 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.
+ */
+
+/**
+ * @file python_wrapper_common.h
+ *
+ * @brief Contains functions and constants that are commonly used throughout the Python plugin implementation.
+ *
+ * @details These functions should not be used outside of the Python plugin implementation, since everything is intended
+ * to be used with the Python engine.
+ */
+
+#pragma once
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// I N C L U D E S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "common/remmina_plugin.h"
+
+#include <gtk/gtk.h>
+
+#include <Python.h>
+#include <glib.h>
+#include <Python.h>
+#include <structmember.h>
+
+#include "remmina/plugin.h"
+#include "config.h"
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// D E C L A R A T I O N S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+G_BEGIN_DECLS
+
+// - Attribute names
+
+extern const char* ATTR_NAME;
+extern const char* ATTR_ICON_NAME;
+extern const char* ATTR_DESCRIPTION;
+extern const char* ATTR_VERSION;
+extern const char* ATTR_ICON_NAME_SSH;
+extern const char* ATTR_FEATURES;
+extern const char* ATTR_BASIC_SETTINGS;
+extern const char* ATTR_ADVANCED_SETTINGS;
+extern const char* ATTR_SSH_SETTING;
+extern const char* ATTR_EXPORT_HINTS;
+extern const char* ATTR_PREF_LABEL;
+extern const char* ATTR_INIT_ORDER;
+
+// You can enable this for debuggin purposes or specify it in the build.
+// #define WITH_PYTHON_TRACE_CALLS
+
+/**
+ * If WITH_PYTHON_TRACE_CALLS is defined, it logs the calls to the Python code and errors in case.
+ */
+#ifdef WITH_PYTHON_TRACE_CALLS
+#define CallPythonMethod(instance, name, params, ...) \
+ python_wrapper_last_result_set(PyObject_CallMethod(instance, name, params, ##__VA_ARGS__)); \
+ python_wrapper_log_method_call(instance, name); \
+ python_wrapper_check_error()
+#else
+/**
+ * If WITH_TRACE_CALL is not defined, it still logs errors but doesn't print the call anymore.
+ */
+#define CallPythonMethod(instance, name, params, ...) \
+ PyObject_CallMethod(instance, name, params, ##__VA_ARGS__); \
+ python_wrapper_check_error()
+#endif // WITH_PYTHON_TRACE_CALLS
+
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// T Y P E S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * @brief The Python abstraction of the protocol widget struct.
+ *
+ * @details This struct is responsible to provide the same accessibility to the protocol widget for Python as for
+ * native plugins.
+ */
+typedef struct
+{
+ PyObject_HEAD
+ RemminaProtocolWidget* gp;
+} PyRemminaProtocolWidget;
+
+/**
+ * @brief Maps an instance of a Python plugin to a Remmina one.
+ *
+ * @details This is used to map a Python plugin instance to the Remmina plugin one. Also instance specific data as the
+ * protocol widget are stored in this struct.
+ */
+typedef struct
+{
+ RemminaProtocolPlugin* protocol_plugin;
+ RemminaFilePlugin* file_plugin;
+ RemminaSecretPlugin* secret_plugin;
+ RemminaToolPlugin* tool_plugin;
+ RemminaEntryPlugin* entry_plugin;
+ RemminaPrefPlugin* pref_plugin;
+ RemminaPlugin* generic_plugin;
+ PyRemminaProtocolWidget* gp;
+ PyObject* instance;
+} PyPlugin;
+
+/**
+ * A struct used to communicate data between Python and C without strict data type.
+ */
+typedef struct
+{
+ PyObject_HEAD;
+ RemminaTypeHint type_hint;
+ gpointer raw;
+} PyGeneric;
+
+/**
+ * Checks if self is set and returns NULL if not.
+ */
+#define SELF_CHECK() if (!self) { \
+ g_printerr("[%s:%d]: self is null!\n", __FILE__, __LINE__); \
+ PyErr_SetString(PyExc_RuntimeError, "Method is not called from an instance (self is null)!"); \
+ return NULL; \
+ }
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Creates a new instance of PyGeneric.
+ */
+PyGeneric* python_wrapper_generic_new(void);
+
+/**
+ * Registers the given plugin if no other plugin with the same name has been already registered.
+ */
+void python_wrapper_add_plugin(PyPlugin* plugin);
+
+/**
+ * Sets the pointer to the plugin service of Remmina.
+ */
+void python_wrapper_set_service(RemminaPluginService* service);
+
+/**
+ * Gets the pointer to the plugin service of Remmina.
+ */
+RemminaPluginService* python_wrapper_get_service(void);
+
+/**
+ * Extracts data from a PyObject instance to a generic pointer and returns a type hint if it could be determined.
+ */
+RemminaTypeHint python_wrapper_to_generic(PyObject* field, gpointer* target);
+
+/**
+ * Gets the result of the last python method call.
+ */
+PyObject* python_wrapper_last_result(void);
+
+/**
+ * @brief Sets the result of the last python method call.
+ *
+ * @return Returns the passed result (it's done to be compatible with the CallPythonMethod macro).
+ */
+PyObject* python_wrapper_last_result_set(PyObject* result);
+
+/**
+ * @brief Prints a log message to inform the user a python message has been called.
+ *
+ * @detail This method is called from the CALL_PYTHON macro if WITH_PYTHON_TRACE_CALLS is defined.
+ *
+ * @param instance The instance that contains the called method.
+ * @param method The name of the method called.
+ */
+void python_wrapper_log_method_call(PyObject* instance, const char* method);
+
+/**
+ * @brief Checks if an error has occurred and prints it.
+ *
+ * @return Returns TRUE if an error has occurred.
+ */
+gboolean python_wrapper_check_error(void);
+
+/**
+ * @brief Gets the attribute as long value.
+ *
+ * @param instance The instance of the object to get the attribute.
+ * @param constant_name The name of the attribute to get.
+ * @param def The value to return if the attribute doesn't exist or is not set.
+ *
+ * @return The value attribute as long.
+ */
+long python_wrapper_get_attribute_long(PyObject* instance, const char* attr_name, long def);
+
+/**
+ * @brief Checks if a given attribute exists.
+ *
+ * @param instance The object to check for the attribute.
+ * @param attr_name The name of the attribute to check.
+ *
+ * @return Returns TRUE if the attribute exists.
+ */
+gboolean python_wrapper_check_attribute(PyObject* instance, const char* attr_name);
+
+/**
+ * @brief Allocates memory and checks for errors before returning.
+ *
+ * @param bytes Amount of bytes to allocate.
+ *
+ * @return Address to the allocated memory.
+ */
+void* python_wrapper_malloc(int bytes);
+
+/**
+ * @biref Copies a string from a Python object to a new point in memory.
+ *
+ * @param string The python object, containing the string to copy.
+ * @param len The length of the string to copy.
+ *
+ * @return A char pointer to the new copy of the string.
+ */
+char* python_wrapper_copy_string_from_python(PyObject* string, Py_ssize_t len);
+
+/**
+ * @brief Tries to find the Python plugin matching to the given instance of RemminaPlugin.
+ *
+ * @param plugin_map An array of PyPlugin pointers to search.
+ * @param instance The RemminaPlugin instance to find the correct PyPlugin instance for.
+ *
+ * @return A pointer to a PyPlugin instance if successful. Otherwise NULL is returned.
+ */
+PyPlugin* python_wrapper_get_plugin(const gchar* name);
+
+/**
+ * @brief Tries to find the Python plugin matching to the given instance of RemminaPlugin.
+ *
+ * @param plugin_map An array of PyPlugin pointers to search.
+ * @param instance The RemminaPlugin instance to find the correct PyPlugin instance for.
+ *
+ * @return A pointer to a PyPlugin instance if successful. Otherwise NULL is returned.
+ */
+PyPlugin* python_wrapper_get_plugin_by_protocol_widget(RemminaProtocolWidget* gp);
+
+/**
+ * Creates a new GtkWidget
+ * @param obj
+ * @return
+ */
+GtkWidget* new_pywidget(GObject* obj);
+
+/**
+ * Extracts a GtkWidget from a PyObject instance.
+ * @param obj
+ * @return
+ */
+GtkWidget* get_pywidget(PyObject* obj);
+
+/**
+ * Initializes the pygobject library. This needs to be called before any Python plugin is being initialized.
+ */
+void init_pygobject(void);
+
+G_END_DECLS
diff --git a/plugins/python_wrapper/python_wrapper_entry.c b/plugins/python_wrapper/python_wrapper_entry.c
new file mode 100644
index 0000000..8865863
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_entry.c
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+
+/**
+ * @file python_wrapper_entry.c
+ * @brief Contains the wiring of a Python pluing based on RemminaPluginProtocol.
+ * @author Mathias Winterhalter
+ * @date 07.04.2021
+ */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// I N C L U D E S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "python_wrapper_entry.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// D E C L A R A T I O N S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+void python_wrapper_entry_init(void)
+{
+ TRACE_CALL(__func__);
+}
+
+void python_wrapper_entry_entry_func_wrapper(RemminaEntryPlugin* instance)
+{
+ TRACE_CALL(__func__);
+
+ PyPlugin* plugin = python_wrapper_get_plugin(instance->name);
+ if (plugin)
+ {
+ CallPythonMethod(plugin->instance, "entry_func", NULL);
+ }
+}
+
+RemminaPlugin* python_wrapper_create_entry_plugin(PyPlugin* plugin)
+{
+ TRACE_CALL(__func__);
+
+ PyObject* instance = plugin->instance;
+
+ if (!python_wrapper_check_attribute(instance, ATTR_NAME)
+ || !python_wrapper_check_attribute(instance, ATTR_VERSION)
+ || !python_wrapper_check_attribute(instance, ATTR_DESCRIPTION))
+ {
+ g_printerr("Unable to create entry plugin. Aborting!\n");
+ return NULL;
+ }
+
+ RemminaEntryPlugin* remmina_plugin = (RemminaEntryPlugin*)python_wrapper_malloc(sizeof(RemminaEntryPlugin));
+
+ remmina_plugin->type = REMMINA_PLUGIN_TYPE_ENTRY;
+ remmina_plugin->domain = GETTEXT_PACKAGE;
+ remmina_plugin->name = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_NAME));
+ remmina_plugin->version = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_VERSION));
+ remmina_plugin->description = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_DESCRIPTION));
+ remmina_plugin->entry_func = python_wrapper_entry_entry_func_wrapper;
+
+ plugin->entry_plugin = remmina_plugin;
+ plugin->generic_plugin = (RemminaPlugin*)remmina_plugin;
+
+ python_wrapper_add_plugin(plugin);
+
+ return (RemminaPlugin*)remmina_plugin;
+}
diff --git a/plugins/python_wrapper/python_wrapper_entry.h b/plugins/python_wrapper/python_wrapper_entry.h
new file mode 100644
index 0000000..396f7be
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_entry.h
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+/**
+ * @file python_wrapper_entry.h
+ *
+ * @brief Contains the specialisation of RemminaPluginEntry plugins in Python.
+ */
+
+#pragma once
+
+#include "python_wrapper_common.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+G_BEGIN_DECLS
+
+/**
+ * Initializes the Python plugin specialisation for entry plugins.
+ */
+void python_wrapper_entry_init(void);
+
+/**
+ * @brief Creates a new instance of the RemminaPluginEntry, initializes its members and references the wrapper
+ * functions.
+ * @param instance The instance of the Python plugin.
+ * @return Returns a new instance of the RemminaPlugin (must be freed!).
+ */
+RemminaPlugin* python_wrapper_create_entry_plugin(PyPlugin* instance);
+
+G_END_DECLS
diff --git a/plugins/python_wrapper/python_wrapper_file.c b/plugins/python_wrapper/python_wrapper_file.c
new file mode 100644
index 0000000..e69c9b0
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_file.c
@@ -0,0 +1,160 @@
+/*
+ * 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 "python_wrapper_file.h"
+#include "python_wrapper_remmina_file.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// D E C L A R A T I O N S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+void python_wrapper_file_init(void)
+{
+ TRACE_CALL(__func__);
+}
+
+gboolean python_wrapper_file_import_test_func_wrapper(RemminaFilePlugin* instance, const gchar* from_file)
+{
+ TRACE_CALL(__func__);
+
+ PyObject* result = NULL;
+
+ PyPlugin* plugin = python_wrapper_get_plugin(instance->name);
+
+ if (plugin)
+ {
+ result = CallPythonMethod(plugin->instance, "import_test_func", "s", from_file);
+ }
+
+ return result == Py_None || result != Py_False;
+}
+
+RemminaFile* python_wrapper_file_import_func_wrapper(RemminaFilePlugin* instance, const gchar* from_file)
+{
+ TRACE_CALL(__func__);
+
+ PyObject* result = NULL;
+
+ PyPlugin* plugin = python_wrapper_get_plugin(instance->name);
+ if (!plugin)
+ {
+ return NULL;
+ }
+
+ result = CallPythonMethod(plugin->instance, "import_func", "s", from_file);
+
+ if (result == Py_None || result == Py_False)
+ {
+ return NULL;
+ }
+
+ return ((PyRemminaFile*)result)->file;
+}
+
+gboolean python_wrapper_file_export_test_func_wrapper(RemminaFilePlugin* instance, RemminaFile* file)
+{
+ TRACE_CALL(__func__);
+
+ PyObject* result = NULL;
+
+ PyPlugin* plugin = python_wrapper_get_plugin(instance->name);
+ if (plugin)
+ {
+ result = CallPythonMethod(plugin->instance,
+ "export_test_func",
+ "O",
+ python_wrapper_remmina_file_to_python(file));
+ }
+
+ return result == Py_None || result != Py_False;
+}
+
+gboolean
+python_wrapper_file_export_func_wrapper(RemminaFilePlugin* instance, RemminaFile* file, const gchar* to_file)
+{
+ TRACE_CALL(__func__);
+
+ PyObject* result = NULL;
+
+ PyPlugin* plugin = python_wrapper_get_plugin(instance->name);
+ if (plugin)
+ {
+ result = CallPythonMethod(plugin->instance, "export_func", "s", to_file);
+ }
+
+ return result == Py_None || result != Py_False;
+}
+
+RemminaPlugin* python_wrapper_create_file_plugin(PyPlugin* plugin)
+{
+ TRACE_CALL(__func__);
+
+ PyObject* instance = plugin->instance;
+ Py_IncRef(instance);
+
+ if (!python_wrapper_check_attribute(instance, ATTR_NAME))
+ {
+ g_printerr("Unable to create file plugin. Aborting!\n");
+ return NULL;
+ }
+
+ RemminaFilePlugin* remmina_plugin = (RemminaFilePlugin*)python_wrapper_malloc(sizeof(RemminaFilePlugin));
+
+ remmina_plugin->type = REMMINA_PLUGIN_TYPE_FILE;
+ remmina_plugin->domain = GETTEXT_PACKAGE;
+ remmina_plugin->name = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_NAME));
+ remmina_plugin->version = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_VERSION));
+ remmina_plugin->description = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_DESCRIPTION));
+ remmina_plugin->export_hints = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_EXPORT_HINTS));
+
+ remmina_plugin->import_test_func = python_wrapper_file_import_test_func_wrapper;
+ remmina_plugin->import_func = python_wrapper_file_import_func_wrapper;
+ remmina_plugin->export_test_func = python_wrapper_file_export_test_func_wrapper;
+ remmina_plugin->export_func = python_wrapper_file_export_func_wrapper;
+
+ plugin->file_plugin = remmina_plugin;
+ plugin->generic_plugin = (RemminaPlugin*)remmina_plugin;
+
+ python_wrapper_add_plugin(plugin);
+
+ return (RemminaPlugin*)remmina_plugin;
+}
diff --git a/plugins/python_wrapper/python_wrapper_file.h b/plugins/python_wrapper/python_wrapper_file.h
new file mode 100644
index 0000000..035712e
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_file.h
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+/**
+ * @file python_wrapper_file.h
+ *
+ * @brief Contains the specialisation of RemminaPluginFile plugins in Python.
+ */
+
+#pragma once
+
+#include "python_wrapper_common.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+G_BEGIN_DECLS
+
+/**
+ * Initializes the Python plugin specialisation for file plugins.
+ */
+void python_wrapper_file_init(void);
+
+/**
+ * @brief Creates a new instance of the RemminaPluginFile, initializes its members and references the wrapper
+ * functions.
+ * @param instance The instance of the Python plugin.
+ * @return Returns a new instance of the RemminaPlugin (must be freed!).
+ */
+RemminaPlugin* python_wrapper_create_file_plugin(PyPlugin* instance);
+
+G_END_DECLS
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;
+}
diff --git a/plugins/python_wrapper/python_wrapper_plugin.h b/plugins/python_wrapper/python_wrapper_plugin.h
new file mode 100644
index 0000000..5678088
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_plugin.h
@@ -0,0 +1,80 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2014-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * 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.
+ */
+
+/**
+ * @file python_wrapper.h
+ *
+ * @brief Declares the interface between the Python plugin implementation and Remmina covering the initialization of
+ * the implementation and the load function, that allows Remmina to load plugins into the application.
+ *
+ * @details When Remmina discovers Python scripts in the plugin root folder the plugin manager passes the path to the
+ * Python plugin loader. There it gets executed and the plugin classes get mapped to "real" Remmina plugin
+ * instances.
+ *
+ * For the communication between Remmina and Python the python module called 'remmina' is initialized and
+ * loaded into the environment of the plugin script (@see python_wrapper_module.c).
+ *
+ * @see http://www.remmina.org/wp for more information.
+ */
+
+#pragma once
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// I N C L U D E
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "remmina/plugin.h"
+
+G_BEGIN_DECLS
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * @brief Initializes the Python plugin loaders.
+ * @details This does not load any plugins but initializes the implementation (e.g. globals and the Python engine).
+ */
+gboolean python_wrapper_init(RemminaLanguageWrapperPlugin* plugin);
+
+/**
+ * @brief Loads a plugin from the Remmina plugin folder with the given name.
+ *
+ * @param service The instance of the service providing an API between Remmina and its plugins.
+ * @param filename The filename of the plugin to load.
+ *
+ * @return TRUE on success, FALSE otherwise.
+ */
+gboolean python_wrapper_load(RemminaLanguageWrapperPlugin* plugin, const gchar* filename);
+
+G_END_DECLS
diff --git a/plugins/python_wrapper/python_wrapper_pref.c b/plugins/python_wrapper/python_wrapper_pref.c
new file mode 100644
index 0000000..84a76db
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_pref.c
@@ -0,0 +1,104 @@
+/*
+ * 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 "python_wrapper_pref.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// D E C L A R A T I O N S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+void python_wrapper_pref_init(void)
+{
+ TRACE_CALL(__func__);
+
+}
+
+/**
+ * @brief
+ */
+GtkWidget* python_wrapper_pref_get_pref_body_wrapper(RemminaPrefPlugin* instance)
+{
+ TRACE_CALL(__func__);
+
+ PyPlugin* plugin = python_wrapper_get_plugin(instance->name);
+
+ PyObject* result = CallPythonMethod(plugin->instance, "get_pref_body", NULL, NULL);
+ if (result == Py_None || result == NULL)
+ {
+ return NULL;
+ }
+
+ return get_pywidget(result);
+}
+
+RemminaPlugin* python_wrapper_create_pref_plugin(PyPlugin* plugin)
+{
+ TRACE_CALL(__func__);
+
+ PyObject* instance = plugin->instance;
+
+ if (!python_wrapper_check_attribute(instance, ATTR_NAME)
+ || !python_wrapper_check_attribute(instance, ATTR_VERSION)
+ || !python_wrapper_check_attribute(instance, ATTR_DESCRIPTION)
+ || !python_wrapper_check_attribute(instance, ATTR_PREF_LABEL))
+ {
+ g_printerr("Unable to create pref plugin. Aborting!\n");
+ return NULL;
+ }
+
+ RemminaPrefPlugin* remmina_plugin = (RemminaPrefPlugin*)python_wrapper_malloc(sizeof(RemminaPrefPlugin));
+
+ remmina_plugin->type = REMMINA_PLUGIN_TYPE_PREF;
+ remmina_plugin->domain = GETTEXT_PACKAGE;
+ remmina_plugin->name = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_NAME));
+ remmina_plugin->version = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_VERSION));
+ remmina_plugin->description = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_DESCRIPTION));
+ remmina_plugin->pref_label = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_PREF_LABEL));
+ remmina_plugin->get_pref_body = python_wrapper_pref_get_pref_body_wrapper;
+
+ plugin->pref_plugin = remmina_plugin;
+ plugin->generic_plugin = (RemminaPlugin*)remmina_plugin;
+
+ python_wrapper_add_plugin(plugin);
+
+ return (RemminaPlugin*)remmina_plugin;
+}
diff --git a/plugins/python_wrapper/python_wrapper_pref.h b/plugins/python_wrapper/python_wrapper_pref.h
new file mode 100644
index 0000000..9dadb96
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_pref.h
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ */
+
+/**
+ * @file python_wrapper_pref.h
+ *
+ * @brief Contains the specialisation of RemminaPluginFile plugins in Python.
+ */
+
+#pragma once
+
+#include "python_wrapper_common.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+G_BEGIN_DECLS
+
+/**
+ * Initializes the Python plugin specialisation for preferences plugins.
+ */
+void python_wrapper_pref_init(void);
+
+/**
+ * @brief Creates a new instance of the RemminaPluginPref, initializes its members and references the wrapper
+ * functions.
+ * @param instance The instance of the Python plugin.
+ * @return Returns a new instance of the RemminaPlugin (must be freed!).
+ */
+RemminaPlugin* python_wrapper_create_pref_plugin(PyPlugin* instance);
+
+G_END_DECLS
diff --git a/plugins/python_wrapper/python_wrapper_protocol.c b/plugins/python_wrapper/python_wrapper_protocol.c
new file mode 100644
index 0000000..64225aa
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_protocol.c
@@ -0,0 +1,315 @@
+/*
+ * 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.
+ */
+
+/**
+ * @file python_wrapper_common.c
+ * @brief
+ * @author Mathias Winterhalter
+ * @date 07.04.2021
+ */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// I N C L U D E S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "python_wrapper_common.h"
+#include "python_wrapper_protocol.h"
+#include "python_wrapper_remmina.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// D E C L A R A T I O N S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+void python_wrapper_protocol_init(void)
+{
+ TRACE_CALL(__func__);
+}
+
+void remmina_protocol_init_wrapper(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ PyPlugin* py_plugin = python_wrapper_get_plugin_by_protocol_widget(gp);
+ py_plugin->gp->gp = gp;
+ CallPythonMethod(py_plugin->instance, "init", "O", py_plugin->gp);
+}
+
+gboolean remmina_protocol_open_connection_wrapper(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ PyPlugin* py_plugin = python_wrapper_get_plugin_by_protocol_widget(gp);
+ if (py_plugin)
+ {
+ PyObject* result = CallPythonMethod(py_plugin->instance, "open_connection", "O", py_plugin->gp);
+ return result == Py_True;
+ }
+ else
+ {
+ return gtk_false();
+ }
+}
+
+gboolean remmina_protocol_close_connection_wrapper(RemminaProtocolWidget* gp)
+{
+ TRACE_CALL(__func__);
+ PyPlugin* py_plugin = python_wrapper_get_plugin_by_protocol_widget(gp);
+ PyObject* result = CallPythonMethod(py_plugin->instance, "close_connection", "O", py_plugin->gp);
+ return result == Py_True;
+}
+
+gboolean remmina_protocol_query_feature_wrapper(RemminaProtocolWidget* gp,
+ const RemminaProtocolFeature* feature)
+{
+ TRACE_CALL(__func__);
+ PyPlugin* py_plugin = python_wrapper_get_plugin_by_protocol_widget(gp);
+ PyRemminaProtocolFeature* pyFeature = python_wrapper_protocol_feature_new();
+ pyFeature->type = (gint)feature->type;
+ pyFeature->id = feature->id;
+ pyFeature->opt1 = python_wrapper_generic_new();
+ pyFeature->opt1->raw = feature->opt1;
+ pyFeature->opt2 = python_wrapper_generic_new();
+ pyFeature->opt2->raw = feature->opt2;
+ pyFeature->opt3 = python_wrapper_generic_new();
+ pyFeature->opt3->raw = feature->opt3;
+
+ PyObject* result = CallPythonMethod(py_plugin->instance, "query_feature", "OO", py_plugin->gp, pyFeature);
+ Py_DecRef((PyObject*)pyFeature);
+ Py_DecRef((PyObject*)pyFeature->opt1);
+ Py_DecRef((PyObject*)pyFeature->opt2);
+ Py_DecRef((PyObject*)pyFeature->opt3);
+ return result == Py_True;
+}
+
+void remmina_protocol_call_feature_wrapper(RemminaProtocolWidget* gp, const RemminaProtocolFeature* feature)
+{
+ TRACE_CALL(__func__);
+ PyPlugin* py_plugin = python_wrapper_get_plugin_by_protocol_widget(gp);
+ PyRemminaProtocolFeature* pyFeature = python_wrapper_protocol_feature_new();
+ pyFeature->type = (gint)feature->type;
+ pyFeature->id = feature->id;
+ pyFeature->opt1 = python_wrapper_generic_new();
+ pyFeature->opt1->raw = feature->opt1;
+ pyFeature->opt1->type_hint = feature->opt1_type_hint;
+ pyFeature->opt2 = python_wrapper_generic_new();
+ pyFeature->opt2->raw = feature->opt2;
+ pyFeature->opt2->type_hint = feature->opt2_type_hint;
+ pyFeature->opt3 = python_wrapper_generic_new();
+ pyFeature->opt3->raw = feature->opt3;
+ pyFeature->opt3->type_hint = feature->opt3_type_hint;
+
+ CallPythonMethod(py_plugin->instance, "call_feature", "OO", py_plugin->gp, pyFeature);
+ Py_DecRef((PyObject*)pyFeature);
+ Py_DecRef((PyObject*)pyFeature->opt1);
+ Py_DecRef((PyObject*)pyFeature->opt2);
+ Py_DecRef((PyObject*)pyFeature->opt3);
+}
+
+void remmina_protocol_send_keytrokes_wrapper(RemminaProtocolWidget* gp,
+ const guint keystrokes[],
+ const gint keylen)
+{
+ TRACE_CALL(__func__);
+ PyPlugin* py_plugin = python_wrapper_get_plugin_by_protocol_widget(gp);
+ PyObject* obj = PyList_New(keylen);
+ Py_IncRef(obj);
+ for (int i = 0; i < keylen; ++i)
+ {
+ PyList_SetItem(obj, i, PyLong_FromLong(keystrokes[i]));
+ }
+ CallPythonMethod(py_plugin->instance, "send_keystrokes", "OO", py_plugin->gp, obj);
+ Py_DecRef(obj);
+}
+
+gboolean remmina_protocol_get_plugin_screenshot_wrapper(RemminaProtocolWidget* gp,
+ RemminaPluginScreenshotData* rpsd)
+{
+ TRACE_CALL(__func__);
+
+ PyPlugin* py_plugin = python_wrapper_get_plugin_by_protocol_widget(gp);
+ PyRemminaPluginScreenshotData* data = python_wrapper_screenshot_data_new();
+ Py_IncRef((PyObject*)data);
+ PyObject* result = CallPythonMethod(py_plugin->instance, "get_plugin_screenshot", "OO", py_plugin->gp, data);
+ if (result == Py_True)
+ {
+ if (!PyByteArray_Check((PyObject*)data->buffer))
+ {
+ g_printerr("Unable to parse screenshot data. 'buffer' needs to be an byte array!");
+ return 0;
+ }
+ Py_ssize_t buffer_len = PyByteArray_Size((PyObject*)data->buffer);
+
+ // Is being freed by Remmina!
+ rpsd->buffer = (unsigned char*)python_wrapper_malloc(sizeof(unsigned char) * buffer_len);
+ if (!rpsd->buffer)
+ {
+ return 0;
+ }
+ memcpy(rpsd->buffer, PyByteArray_AsString((PyObject*)data->buffer), sizeof(unsigned char) * buffer_len);
+ rpsd->bytesPerPixel = data->bytesPerPixel;
+ rpsd->bitsPerPixel = data->bitsPerPixel;
+ rpsd->height = data->height;
+ rpsd->width = data->width;
+ }
+ Py_DecRef((PyObject*)data->buffer);
+ Py_DecRef((PyObject*)data);
+ return result == Py_True;
+}
+
+gboolean remmina_protocol_map_event_wrapper(RemminaProtocolWidget* gp)
+{
+ PyPlugin* plugin = python_wrapper_get_plugin_by_protocol_widget(gp);
+ PyObject* result = CallPythonMethod(plugin->instance, "map_event", "O", plugin->gp);
+ return PyBool_Check(result) && result == Py_True;
+}
+
+gboolean remmina_protocol_unmap_event_wrapper(RemminaProtocolWidget* gp)
+{
+ PyPlugin* plugin = python_wrapper_get_plugin_by_protocol_widget(gp);
+ PyObject* result = CallPythonMethod(plugin->instance, "unmap_event", "O", plugin->gp);
+ return PyBool_Check(result) && result == Py_True;
+}
+
+RemminaPlugin* python_wrapper_create_protocol_plugin(PyPlugin* plugin)
+{
+ PyObject* instance = plugin->instance;
+
+ if (!python_wrapper_check_attribute(instance, ATTR_ICON_NAME_SSH)
+ || !python_wrapper_check_attribute(instance, ATTR_ICON_NAME)
+ || !python_wrapper_check_attribute(instance, ATTR_FEATURES)
+ || !python_wrapper_check_attribute(instance, ATTR_BASIC_SETTINGS)
+ || !python_wrapper_check_attribute(instance, ATTR_ADVANCED_SETTINGS)
+ || !python_wrapper_check_attribute(instance, ATTR_SSH_SETTING))
+ {
+ g_printerr("Unable to create protocol plugin. Aborting!\n");
+ return NULL;
+ }
+
+ RemminaProtocolPlugin* remmina_plugin = (RemminaProtocolPlugin*)python_wrapper_malloc(sizeof(RemminaProtocolPlugin));
+
+ remmina_plugin->type = REMMINA_PLUGIN_TYPE_PROTOCOL;
+ remmina_plugin->domain = GETTEXT_PACKAGE;
+ remmina_plugin->basic_settings = NULL;
+ remmina_plugin->advanced_settings = NULL;
+ remmina_plugin->features = NULL;
+
+ remmina_plugin->name = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_NAME));
+ remmina_plugin->description = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_DESCRIPTION));
+ remmina_plugin->version = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_VERSION));
+ remmina_plugin->icon_name = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_ICON_NAME));
+ remmina_plugin->icon_name_ssh = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_ICON_NAME_SSH));
+
+ PyObject* list = PyObject_GetAttrString(instance, "basic_settings");
+ Py_ssize_t len = PyList_Size(list);
+ if (len)
+ {
+ RemminaProtocolSetting* basic_settings = (RemminaProtocolSetting*)python_wrapper_malloc(
+ sizeof(RemminaProtocolSetting) * (len + 1));
+ memset(basic_settings, 0, sizeof(RemminaProtocolSetting) * (len + 1));
+
+ for (Py_ssize_t i = 0; i < len; ++i)
+ {
+ RemminaProtocolSetting* dest = basic_settings + i;
+ python_wrapper_to_protocol_setting(dest, PyList_GetItem(list, i));
+ }
+ RemminaProtocolSetting* dest = basic_settings + len;
+ dest->type = REMMINA_PROTOCOL_SETTING_TYPE_END;
+ remmina_plugin->basic_settings = basic_settings;
+ }
+
+ list = PyObject_GetAttrString(instance, "advanced_settings");
+ len = PyList_Size(list);
+ if (len)
+ {
+ RemminaProtocolSetting* advanced_settings = (RemminaProtocolSetting*)python_wrapper_malloc(
+ sizeof(RemminaProtocolSetting) * (len + 1));
+ memset(advanced_settings, 0, sizeof(RemminaProtocolSetting) * (len + 1));
+
+ for (Py_ssize_t i = 0; i < len; ++i)
+ {
+ RemminaProtocolSetting* dest = advanced_settings + i;
+ python_wrapper_to_protocol_setting(dest, PyList_GetItem(list, i));
+ }
+
+ RemminaProtocolSetting* dest = advanced_settings + len;
+ dest->type = REMMINA_PROTOCOL_SETTING_TYPE_END;
+
+ remmina_plugin->advanced_settings = advanced_settings;
+ }
+
+ list = PyObject_GetAttrString(instance, "features");
+ len = PyList_Size(list);
+ if (len)
+ {
+ RemminaProtocolFeature* features = (RemminaProtocolFeature*)python_wrapper_malloc(
+ sizeof(RemminaProtocolFeature) * (len + 1));
+ memset(features, 0, sizeof(RemminaProtocolFeature) * (len + 1));
+
+ for (Py_ssize_t i = 0; i < len; ++i)
+ {
+ RemminaProtocolFeature* dest = features + i;
+ python_wrapper_to_protocol_feature(dest, PyList_GetItem(list, i));
+ }
+
+ RemminaProtocolFeature* dest = features + len;
+ dest->type = REMMINA_PROTOCOL_FEATURE_TYPE_END;
+
+ remmina_plugin->features = features;
+ }
+
+ remmina_plugin->ssh_setting = (RemminaProtocolSSHSetting)python_wrapper_get_attribute_long(instance,
+ ATTR_SSH_SETTING,
+ REMMINA_PROTOCOL_SSH_SETTING_NONE);
+
+ remmina_plugin->init = remmina_protocol_init_wrapper; // Plugin initialization
+ remmina_plugin->open_connection = remmina_protocol_open_connection_wrapper; // Plugin open connection
+ remmina_plugin->close_connection = remmina_protocol_close_connection_wrapper; // Plugin close connection
+ remmina_plugin->query_feature = remmina_protocol_query_feature_wrapper; // Query for available features
+ remmina_plugin->call_feature = remmina_protocol_call_feature_wrapper; // Call a feature
+ remmina_plugin->send_keystrokes =
+ remmina_protocol_send_keytrokes_wrapper; // Send a keystroke
+ remmina_plugin->get_plugin_screenshot =
+ remmina_protocol_get_plugin_screenshot_wrapper; // Screenshot support unavailable
+
+ remmina_plugin->map_event = remmina_protocol_map_event_wrapper;
+ remmina_plugin->unmap_event = remmina_protocol_unmap_event_wrapper;
+
+ plugin->protocol_plugin = remmina_plugin;
+ plugin->generic_plugin = (RemminaPlugin*)remmina_plugin;
+
+ python_wrapper_add_plugin(plugin);
+
+ return (RemminaPlugin*)remmina_plugin;
+} \ No newline at end of file
diff --git a/plugins/python_wrapper/python_wrapper_protocol.h b/plugins/python_wrapper/python_wrapper_protocol.h
new file mode 100644
index 0000000..7c4c9da
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_protocol.h
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+/**
+ * @file python_wrapper_protocol.h
+ *
+ * @brief Contains the specialisation of RemminaPluginFile plugins in Python.
+ */
+
+#pragma once
+
+#include "python_wrapper_common.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// I N C L U D E S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "remmina/plugin.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+G_BEGIN_DECLS
+
+/**
+ * Wrapper for a Python object that contains a pointer to an instance of RemminaProtocolFeature.
+ */
+typedef struct
+{
+ PyObject_HEAD
+ RemminaProtocolFeatureType type;
+ gint id;
+ PyGeneric* opt1;
+ PyGeneric* opt2;
+ PyGeneric* opt3;
+} PyRemminaProtocolFeature;
+
+/**
+ *
+ */
+typedef struct
+{
+ PyObject_HEAD
+ PyByteArrayObject* buffer;
+ int bitsPerPixel;
+ int bytesPerPixel;
+ int width;
+ int height;
+} PyRemminaPluginScreenshotData;
+
+/**
+ * Initializes the Python plugin specialisation for protocol plugins.
+ */
+void python_wrapper_protocol_init(void);
+
+/**
+ * @brief Creates a new instance of the RemminaPluginProtocol, initializes its members and references the wrapper
+ * functions.
+ *
+ * @param instance The instance of the Python plugin.
+ *
+ * @return Returns a new instance of the RemminaPlugin (must be freed!).
+ */
+RemminaPlugin* python_wrapper_create_protocol_plugin(PyPlugin* plugin);
+
+/**
+ *
+ * @return
+ */
+PyRemminaProtocolFeature* python_wrapper_protocol_feature_new(void);
+
+/**
+ *
+ * @return
+ */
+PyRemminaPluginScreenshotData* python_wrapper_screenshot_data_new(void);
+
+G_END_DECLS
diff --git a/plugins/python_wrapper/python_wrapper_protocol_widget.c b/plugins/python_wrapper/python_wrapper_protocol_widget.c
new file mode 100644
index 0000000..3f8ee7a
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_protocol_widget.c
@@ -0,0 +1,845 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2014-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * 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.
+ */
+
+/**
+ * @file python_wrapper_protocol_widget.c
+ * @brief Implementation of the Protocol Widget API.
+ * @author Mathias Winterhalter
+ * @date 19.11.2020
+ *
+ * The RemminaPluginService provides an API for plugins to interact with Remmina. The
+ * module called 'remmina' forwards this interface to make it accessible for Python
+ * scripts.
+ *
+ * This is an example of a minimal protocol plugin:
+ *
+ * @code
+ * import remmina
+ *
+ * class MyProtocol:
+ * def __init__(self):
+ * self.name = "MyProtocol"
+ * self.description = "Example protocol plugin to explain how Python plugins work."
+ * self.version = "0.1"
+ * self.icon_name = ""
+ * self.icon_name_ssh = ""
+ *
+ * def init(self, handle):
+ * print("This is getting logged to the standard output of Remmina.")
+ * remmina.log_print("For debugging purposes it would be better to log the output to the %s window %s!" % ("debug", ":)"))
+ * self.init_your_stuff(handle)
+ *
+ * def open_connection(self, handle):
+ * if not self.connect():
+ * remmina.log_print("Error! Can not connect...")
+ * return False
+ *
+ * remmina.remmina_signal_connected(handle)
+ * remmina.log_print("Connection established!")
+ * return True
+ *
+ *
+ * def close_connection(self, handle):
+ * self.disconnect()
+ * return True
+ *
+ * plugin = MyProtocol()
+ * remmina.register_plugin(plugin)
+ * @endcode
+ *
+ *
+ *
+ * @see http://www.remmina.org/wp for more information.
+ */
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// I N L U C E S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "python_wrapper_common.h"
+#include "remmina/plugin.h"
+#include "remmina/types.h"
+#include "python_wrapper_remmina_file.h"
+#include "python_wrapper_protocol_widget.h"
+#include "python_wrapper_protocol.h"
+
+// -- Python Type -> RemminaWidget
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// D E C L A R A T I O N S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static PyObject* protocol_widget_get_viewport(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_get_width(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_set_width(PyRemminaProtocolWidget* self, PyObject* var_width);
+static PyObject* protocol_widget_get_height(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_set_height(PyRemminaProtocolWidget* self, PyObject* var_height);
+static PyObject* protocol_widget_get_current_scale_mode(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_get_expand(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_set_expand(PyRemminaProtocolWidget* self, PyObject* var_expand);
+static PyObject* protocol_widget_has_error(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_set_error(PyRemminaProtocolWidget* self, PyObject* var_msg);
+static PyObject* protocol_widget_is_closed(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_get_file(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_emit_signal(PyRemminaProtocolWidget* self, PyObject* var_signal);
+static PyObject* protocol_widget_register_hostkey(PyRemminaProtocolWidget* self, PyObject* var_widget);
+static PyObject* protocol_widget_start_direct_tunnel(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_start_reverse_tunnel(PyRemminaProtocolWidget* self, PyObject* var_local_port);
+static PyObject* protocol_widget_start_xport_tunnel(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_set_display(PyRemminaProtocolWidget* self, PyObject* var_display);
+static PyObject* protocol_widget_signal_connection_closed(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_signal_connection_opened(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_update_align(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_unlock_dynres(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_desktop_resize(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_panel_new_certificate(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_panel_changed_certificate(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_get_username(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_get_password(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_get_domain(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_get_savepassword(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_panel_authx509(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_get_cacert(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_get_cacrl(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_get_clientcert(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_get_clientkey(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_save_cred(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_panel_show_listen(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_panel_show_retry(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_panel_show(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_panel_hide(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_ssh_exec(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_chat_open(PyRemminaProtocolWidget* self, PyObject* var_name);
+static PyObject* protocol_widget_chat_close(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_chat_receive(PyRemminaProtocolWidget* self, PyObject* args);
+static PyObject* protocol_widget_send_keys_signals(PyRemminaProtocolWidget* self, PyObject* args);
+
+static struct PyMethodDef python_protocol_widget_type_methods[] =
+ {{ "get_viewport", (PyCFunction)protocol_widget_get_viewport, METH_NOARGS, "" },
+ { "get_width", (PyCFunction)protocol_widget_get_width, METH_NOARGS, "" },
+ { "set_width", (PyCFunction)protocol_widget_set_width, METH_VARARGS, "" },
+ { "get_height", (PyCFunction)protocol_widget_get_height, METH_VARARGS, "" },
+ { "set_height", (PyCFunction)protocol_widget_set_height, METH_VARARGS, "" },
+ { "get_current_scale_mode", (PyCFunction)protocol_widget_get_current_scale_mode, METH_VARARGS, "" },
+ { "get_expand", (PyCFunction)protocol_widget_get_expand, METH_VARARGS, "" },
+ { "set_expand", (PyCFunction)protocol_widget_set_expand, METH_VARARGS, "" },
+ { "has_error", (PyCFunction)protocol_widget_has_error, METH_VARARGS, "" },
+ { "set_error", (PyCFunction)protocol_widget_set_error, METH_VARARGS, "" },
+ { "is_closed", (PyCFunction)protocol_widget_is_closed, METH_VARARGS, "" },
+ { "get_file", (PyCFunction)protocol_widget_get_file, METH_NOARGS, "" },
+ { "emit_signal", (PyCFunction)protocol_widget_emit_signal, METH_VARARGS, "" },
+ { "register_hostkey", (PyCFunction)protocol_widget_register_hostkey, METH_VARARGS, "" },
+ { "start_direct_tunnel", (PyCFunction)protocol_widget_start_direct_tunnel, METH_VARARGS | METH_KEYWORDS, "" },
+ { "start_reverse_tunnel", (PyCFunction)protocol_widget_start_reverse_tunnel, METH_VARARGS, "" },
+ { "start_xport_tunnel", (PyCFunction)protocol_widget_start_xport_tunnel, METH_VARARGS, "" },
+ { "set_display", (PyCFunction)protocol_widget_set_display, METH_VARARGS, "" },
+ { "signal_connection_closed", (PyCFunction)protocol_widget_signal_connection_closed, METH_VARARGS, "" },
+ { "signal_connection_opened", (PyCFunction)protocol_widget_signal_connection_opened, METH_VARARGS, "" },
+ { "update_align", (PyCFunction)protocol_widget_update_align, METH_VARARGS, "" },
+ { "unlock_dynres", (PyCFunction)protocol_widget_unlock_dynres, METH_VARARGS, "" },
+ { "desktop_resize", (PyCFunction)protocol_widget_desktop_resize, METH_VARARGS, "" },
+ { "panel_new_certificate", (PyCFunction)protocol_widget_panel_new_certificate, METH_VARARGS | METH_KEYWORDS, "" },
+ { "panel_changed_certificate", (PyCFunction)protocol_widget_panel_changed_certificate,
+ METH_VARARGS | METH_KEYWORDS, "" },
+ { "get_username", (PyCFunction)protocol_widget_get_username, METH_VARARGS, "" },
+ { "get_password", (PyCFunction)protocol_widget_get_password, METH_VARARGS, "" },
+ { "get_domain", (PyCFunction)protocol_widget_get_domain, METH_VARARGS, "" },
+ { "get_savepassword", (PyCFunction)protocol_widget_get_savepassword, METH_VARARGS, "" },
+ { "panel_authx509", (PyCFunction)protocol_widget_panel_authx509, METH_VARARGS, "" },
+ { "get_cacert", (PyCFunction)protocol_widget_get_cacert, METH_VARARGS, "" },
+ { "get_cacrl", (PyCFunction)protocol_widget_get_cacrl, METH_VARARGS, "" },
+ { "get_clientcert", (PyCFunction)protocol_widget_get_clientcert, METH_VARARGS, "" },
+ { "get_clientkey", (PyCFunction)protocol_widget_get_clientkey, METH_VARARGS, "" },
+ { "save_cred", (PyCFunction)protocol_widget_save_cred, METH_VARARGS, "" },
+ { "panel_show_listen", (PyCFunction)protocol_widget_panel_show_listen, METH_VARARGS, "" },
+ { "panel_show_retry", (PyCFunction)protocol_widget_panel_show_retry, METH_VARARGS, "" },
+ { "panel_show", (PyCFunction)protocol_widget_panel_show, METH_VARARGS, "" },
+ { "panel_hide", (PyCFunction)protocol_widget_panel_hide, METH_VARARGS, "" },
+ { "ssh_exec", (PyCFunction)protocol_widget_ssh_exec, METH_VARARGS | METH_KEYWORDS, "" },
+ { "chat_open", (PyCFunction)protocol_widget_chat_open, METH_VARARGS, "" },
+ { "chat_close", (PyCFunction)protocol_widget_chat_close, METH_VARARGS, "" },
+ { "chat_receive", (PyCFunction)protocol_widget_chat_receive, METH_VARARGS, "" },
+ { "send_keys_signals", (PyCFunction)protocol_widget_send_keys_signals, METH_VARARGS | METH_KEYWORDS, "" },
+ { NULL }};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static PyObject* python_protocol_feature_new(PyTypeObject* type, PyObject* kws, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ PyRemminaProtocolWidget* self;
+ self = (PyRemminaProtocolWidget*)type->tp_alloc(type, 0);
+ if (!self)
+ {
+ return NULL;
+ }
+
+ return (PyObject*)self;
+}
+
+static int python_protocol_feature_init(PyObject* self, PyObject* args, PyObject* kwds)
+{
+ return 0;
+}
+
+static PyTypeObject python_protocol_widget_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "remmina.RemminaProtocolWidget",
+ .tp_doc = "RemminaProtocolWidget",
+ .tp_basicsize = sizeof(PyRemminaProtocolWidget),
+ .tp_itemsize = 0,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = python_protocol_feature_new,
+ .tp_init = python_protocol_feature_init,
+ .tp_methods = python_protocol_widget_type_methods
+};
+
+PyRemminaProtocolWidget* python_wrapper_protocol_widget_create(void)
+{
+ TRACE_CALL(__func__);
+
+ PyRemminaProtocolWidget* result = PyObject_NEW(PyRemminaProtocolWidget, &python_protocol_widget_type);
+ assert(result);
+
+ PyErr_Print();
+ Py_INCREF(result);
+ result->gp = NULL;
+ return result;
+}
+
+void python_wrapper_protocol_widget_init(void)
+{
+ init_pygobject();
+}
+
+void python_wrapper_protocol_widget_type_ready(void)
+{
+ TRACE_CALL(__func__);
+ if (PyType_Ready(&python_protocol_widget_type) < 0)
+ {
+ g_printerr("Error initializing remmina.RemminaWidget!\n");
+ PyErr_Print();
+ }
+}
+
+static PyObject* protocol_widget_get_viewport(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+ return (PyObject*)new_pywidget(G_OBJECT(python_wrapper_get_service()->protocol_widget_gtkviewport(self->gp)));
+}
+
+static PyObject* protocol_widget_get_width(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ return Py_BuildValue("i", python_wrapper_get_service()->protocol_widget_get_width(self->gp));
+}
+
+static PyObject* protocol_widget_set_width(PyRemminaProtocolWidget* self, PyObject* var_width)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ if (!var_width)
+ {
+ g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__);
+ return NULL;
+ }
+
+ if (PyLong_Check(var_width))
+ {
+ g_printerr("[%s:%d@%s]: Argument is not of type Long!\n", __FILE__, __LINE__, __func__);
+ return NULL;
+ }
+
+ gint width = (gint)PyLong_AsLong(var_width);
+ python_wrapper_get_service()->protocol_widget_set_height(self->gp, width);
+
+ return Py_None;
+}
+
+static PyObject* protocol_widget_get_height(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ return Py_BuildValue("i", python_wrapper_get_service()->protocol_widget_get_height(self->gp));
+}
+
+static PyObject* protocol_widget_set_height(PyRemminaProtocolWidget* self, PyObject* var_height)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ if (!var_height)
+ {
+ g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__);
+ return NULL;
+ }
+
+ if (PyLong_Check(var_height))
+ {
+ g_printerr("[%s:%d@%s]: Argument is not of type Long!\n", __FILE__, __LINE__, __func__);
+ return NULL;
+ }
+
+ gint height = (gint)PyLong_AsLong(var_height);
+ python_wrapper_get_service()->protocol_widget_set_height(self->gp, height);
+
+ return Py_None;
+}
+
+static PyObject* protocol_widget_get_current_scale_mode(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ return Py_BuildValue("i", python_wrapper_get_service()->protocol_widget_get_current_scale_mode(self->gp));
+}
+
+static PyObject* protocol_widget_get_expand(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ return Py_BuildValue("p", python_wrapper_get_service()->protocol_widget_get_expand(self->gp));
+}
+
+static PyObject* protocol_widget_set_expand(PyRemminaProtocolWidget* self, PyObject* var_expand)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ if (!var_expand)
+ {
+ g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__);
+ return NULL;
+ }
+
+ if (PyBool_Check(var_expand))
+ {
+ g_printerr("[%s:%d@%s]: Argument is not of type Boolean!\n", __FILE__, __LINE__, __func__);
+ return NULL;
+ }
+
+ python_wrapper_get_service()->protocol_widget_set_expand(self->gp, PyObject_IsTrue(var_expand));
+
+ return Py_None;
+}
+
+static PyObject* protocol_widget_has_error(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ return Py_BuildValue("p", python_wrapper_get_service()->protocol_widget_has_error(self->gp));
+}
+
+static PyObject* protocol_widget_set_error(PyRemminaProtocolWidget* self, PyObject* var_msg)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ if (!var_msg)
+ {
+ g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__);
+ return NULL;
+ }
+
+ if (PyUnicode_Check(var_msg))
+ {
+ g_printerr("[%s:%d@%s]: Argument is not of type String!\n", __FILE__, __LINE__, __func__);
+ return NULL;
+ }
+
+ const gchar* msg = PyUnicode_AsUTF8(var_msg);
+ python_wrapper_get_service()->protocol_widget_set_error(self->gp, msg);
+
+ return Py_None;
+}
+
+static PyObject* protocol_widget_is_closed(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ return Py_BuildValue("p", python_wrapper_get_service()->protocol_widget_is_closed(self->gp));
+}
+
+static PyObject* protocol_widget_get_file(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ RemminaFile* file = python_wrapper_get_service()->protocol_widget_get_file(self->gp);
+ return (PyObject*)python_wrapper_remmina_file_to_python(file);
+}
+
+static PyObject* protocol_widget_emit_signal(PyRemminaProtocolWidget* self, PyObject* var_signal)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ if (!var_signal)
+ {
+ g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__);
+ return NULL;
+ }
+
+ if (PyUnicode_Check(var_signal))
+ {
+ g_printerr("[%s:%d@%s]: Argument is not of type String!\n", __FILE__, __LINE__, __func__);
+ return NULL;
+ }
+
+ python_wrapper_get_service()->protocol_widget_set_error(self->gp, PyUnicode_AsUTF8(var_signal));
+
+ return Py_None;
+}
+
+static PyObject* protocol_widget_register_hostkey(PyRemminaProtocolWidget* self, PyObject* var_widget)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ if (!var_widget)
+ {
+ g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__);
+ return NULL;
+ }
+
+ python_wrapper_get_service()->protocol_widget_register_hostkey(self->gp, get_pywidget(var_widget));
+
+ return Py_None;
+}
+
+static PyObject* protocol_widget_start_direct_tunnel(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ gint default_port;
+ gboolean port_plus;
+
+ if (!args)
+ {
+ g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__);
+ }
+
+ if (PyArg_ParseTuple(args, "ii", &default_port, &port_plus))
+ {
+ return Py_BuildValue("s", python_wrapper_get_service()->protocol_widget_start_direct_tunnel(self->gp, default_port, port_plus));
+ }
+ else
+ {
+ PyErr_Print();
+ return NULL;
+ }
+ return Py_None;
+}
+
+static PyObject* protocol_widget_start_reverse_tunnel(PyRemminaProtocolWidget* self, PyObject* var_local_port)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ if (!PyLong_Check(var_local_port))
+ {
+ g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__);
+ return NULL;
+ }
+
+ if (!PyLong_Check(var_local_port))
+ {
+ g_printerr("[%s:%d@%s]: Argument is not of type Long!\n", __FILE__, __LINE__, __func__);
+ return NULL;
+ }
+
+ return Py_BuildValue("p", python_wrapper_get_service()->protocol_widget_start_reverse_tunnel(self->gp, (gint)PyLong_AsLong(var_local_port)));
+}
+
+static gboolean xport_tunnel_init(RemminaProtocolWidget* gp, gint remotedisplay, const gchar* server, gint port)
+{
+ TRACE_CALL(__func__);
+ PyPlugin* plugin = python_wrapper_get_plugin_by_protocol_widget(gp);
+ PyObject* result = PyObject_CallMethod(plugin->instance, "xport_tunnel_init", "Oisi", gp, remotedisplay, server, port);
+ return PyObject_IsTrue(result);
+}
+
+static PyObject* protocol_widget_start_xport_tunnel(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ return Py_BuildValue("p", python_wrapper_get_service()->protocol_widget_start_xport_tunnel(self->gp, xport_tunnel_init));
+}
+
+static PyObject* protocol_widget_set_display(PyRemminaProtocolWidget* self, PyObject* var_display)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ if (!var_display)
+ {
+ g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__);
+ return NULL;
+ }
+
+ if (!PyLong_Check(var_display))
+ {
+ g_printerr("[%s:%d@%s]: Argument is not of type Long!\n", __FILE__, __LINE__, __func__);
+ return NULL;
+ }
+
+ python_wrapper_get_service()->protocol_widget_set_display(self->gp, (gint)PyLong_AsLong(var_display));
+
+ return Py_None;
+}
+
+static PyObject* protocol_widget_signal_connection_closed(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ python_wrapper_get_service()->protocol_widget_signal_connection_closed(self->gp);
+ return Py_None;
+}
+
+static PyObject* protocol_widget_signal_connection_opened(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ python_wrapper_get_service()->protocol_widget_signal_connection_opened(self->gp);
+ return Py_None;
+}
+
+static PyObject* protocol_widget_update_align(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ python_wrapper_get_service()->protocol_widget_update_align(self->gp);
+ return Py_None;
+}
+
+static PyObject* protocol_widget_unlock_dynres(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ python_wrapper_get_service()->protocol_widget_unlock_dynres(self->gp);
+ return Py_None;
+}
+
+static PyObject* protocol_widget_desktop_resize(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ python_wrapper_get_service()->protocol_widget_desktop_resize(self->gp);
+ return Py_None;
+}
+
+static PyObject* protocol_widget_panel_new_certificate(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+ gchar* subject, * issuer, * fingerprint;
+
+ if (PyArg_ParseTuple(args, "sss", &subject, &issuer, &fingerprint))
+ {
+ python_wrapper_get_service()->protocol_widget_panel_new_certificate(self->gp, subject, issuer, fingerprint);
+ }
+ else
+ {
+ PyErr_Print();
+ return NULL;
+ }
+ return Py_None;
+}
+
+static PyObject* protocol_widget_panel_changed_certificate(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+ gchar* subject, * issuer, * new_fingerprint, * old_fingerprint;
+
+ if (PyArg_ParseTuple(args, "sss", &subject, &issuer, &new_fingerprint, &old_fingerprint))
+ {
+ python_wrapper_get_service()->protocol_widget_panel_changed_certificate(self->gp, subject, issuer, new_fingerprint, old_fingerprint);
+ }
+ else
+ {
+ PyErr_Print();
+ return NULL;
+ }
+ return Py_None;
+}
+
+static PyObject* protocol_widget_get_username(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ return Py_BuildValue("s", python_wrapper_get_service()->protocol_widget_get_username(self->gp));
+}
+
+static PyObject* protocol_widget_get_password(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ return Py_BuildValue("s", python_wrapper_get_service()->protocol_widget_get_password(self->gp));
+}
+
+static PyObject* protocol_widget_get_domain(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ return Py_BuildValue("s", python_wrapper_get_service()->protocol_widget_get_domain(self->gp));
+}
+
+static PyObject* protocol_widget_get_savepassword(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ return Py_BuildValue("p", python_wrapper_get_service()->protocol_widget_get_savepassword(self->gp));
+}
+
+static PyObject* protocol_widget_panel_authx509(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ return Py_BuildValue("i", python_wrapper_get_service()->protocol_widget_panel_authx509(self->gp));
+}
+
+static PyObject* protocol_widget_get_cacert(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ return Py_BuildValue("s", python_wrapper_get_service()->protocol_widget_get_cacert(self->gp));
+}
+
+static PyObject* protocol_widget_get_cacrl(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ return Py_BuildValue("s", python_wrapper_get_service()->protocol_widget_get_cacrl(self->gp));
+}
+
+static PyObject* protocol_widget_get_clientcert(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ return Py_BuildValue("s", python_wrapper_get_service()->protocol_widget_get_clientcert(self->gp));
+}
+
+static PyObject* protocol_widget_get_clientkey(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ return Py_BuildValue("s", python_wrapper_get_service()->protocol_widget_get_clientkey(self->gp));
+}
+
+static PyObject* protocol_widget_save_cred(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ python_wrapper_get_service()->protocol_widget_save_cred(self->gp);
+ return Py_None;
+}
+
+static PyObject* protocol_widget_panel_show_listen(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+ gint port = 0;
+
+ if (PyArg_ParseTuple(args, "i", &port))
+ {
+ python_wrapper_get_service()->protocol_widget_panel_show_listen(self->gp, port);
+ }
+ else
+ {
+ PyErr_Print();
+ return NULL;
+ }
+ return Py_None;
+}
+
+static PyObject* protocol_widget_panel_show_retry(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ python_wrapper_get_service()->protocol_widget_panel_show_retry(self->gp);
+ return Py_None;
+}
+
+static PyObject* protocol_widget_panel_show(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ python_wrapper_get_service()->protocol_widget_panel_show(self->gp);
+ return Py_None;
+}
+
+static PyObject* protocol_widget_panel_hide(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ python_wrapper_get_service()->protocol_widget_panel_hide(self->gp);
+ return Py_None;
+}
+
+static PyObject* protocol_widget_ssh_exec(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+ gboolean wait;
+ gchar* cmd;
+
+ if (PyArg_ParseTuple(args, "ps", &wait, &cmd))
+ {
+ python_wrapper_get_service()->protocol_widget_ssh_exec(self->gp, wait, cmd);
+ }
+ else
+ {
+ PyErr_Print();
+ return NULL;
+ }
+ return Py_None;
+}
+
+static void _on_send_callback_wrapper(RemminaProtocolWidget* gp, const gchar* text)
+{
+ PyPlugin* plugin = python_wrapper_get_plugin_by_protocol_widget(gp);
+ PyObject_CallMethod(plugin->instance, "on_send", "Os", gp, text);
+}
+
+static void _on_destroy_callback_wrapper(RemminaProtocolWidget* gp)
+{
+ PyPlugin* plugin = python_wrapper_get_plugin_by_protocol_widget(gp);
+ PyObject_CallMethod(plugin->instance, "on_destroy", "O", gp);
+}
+
+static PyObject* protocol_widget_chat_open(PyRemminaProtocolWidget* self, PyObject* var_name)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ if (!PyUnicode_Check(var_name))
+ {
+ g_printerr("[%s:%d@%s]: Argument is not of type String!\n", __FILE__, __LINE__, __func__);
+ }
+
+ python_wrapper_get_service()->protocol_widget_chat_open(self->gp,
+ PyUnicode_AsUTF8(var_name),
+ _on_send_callback_wrapper,
+ _on_destroy_callback_wrapper);
+
+ return Py_None;
+}
+
+static PyObject* protocol_widget_chat_close(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+
+ python_wrapper_get_service()->protocol_widget_panel_hide(self->gp);
+ return Py_None;
+}
+
+static PyObject* protocol_widget_chat_receive(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+ gchar* text;
+
+ if (PyArg_ParseTuple(args, "s", &text))
+ {
+ python_wrapper_get_service()->protocol_widget_chat_receive(self->gp, text);
+ }
+ else
+ {
+ PyErr_Print();
+ return NULL;
+ }
+
+ return Py_None;
+}
+
+static PyObject* protocol_widget_send_keys_signals(PyRemminaProtocolWidget* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+ SELF_CHECK();
+ guint* keyvals;
+ int length;
+ GdkEventType event_type;
+ PyObject* widget;
+
+ if (PyArg_ParseTuple(args, "Osii", &widget, &keyvals, &length, &event_type) && widget && keyvals)
+ {
+ if (event_type < GDK_NOTHING || event_type >= GDK_EVENT_LAST)
+ {
+ g_printerr("[%s:%d@%s]: %d is not a known value for GdkEventType!\n", __FILE__, __LINE__, __func__, event_type);
+ return NULL;
+ }
+ else
+ {
+ python_wrapper_get_service()->protocol_widget_send_keys_signals((GtkWidget*)widget, keyvals, length, event_type);
+ }
+ }
+ else
+ {
+ PyErr_Print();
+ return NULL;
+ }
+
+ return Py_None;
+}
diff --git a/plugins/python_wrapper/python_wrapper_protocol_widget.h b/plugins/python_wrapper/python_wrapper_protocol_widget.h
new file mode 100644
index 0000000..40e54ac
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_protocol_widget.h
@@ -0,0 +1,65 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * 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.
+ */
+
+/**
+ * @file python_wrapper_protocol_widget.h
+ *
+ * @brief Contains the implementation of the widget handling used from the protocol plugin.
+ */
+
+#pragma once
+
+#include "python_wrapper_common.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+G_BEGIN_DECLS
+
+/**
+ * Initializes the widget backend of the protocol plugin implementation.
+ */
+void python_wrapper_protocol_widget_init(void);
+
+/**
+ * Initializes Python types used for protocol widgets.
+ */
+void python_wrapper_protocol_widget_type_ready(void);
+
+/**
+ * Creates a new instance of PyRemminaProtocolWidget and initializes its fields.
+ */
+PyRemminaProtocolWidget* python_wrapper_protocol_widget_create();
+
+G_END_DECLS
diff --git a/plugins/python_wrapper/python_wrapper_remmina.c b/plugins/python_wrapper/python_wrapper_remmina.c
new file mode 100644
index 0000000..7a4a771
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_remmina.c
@@ -0,0 +1,1241 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * 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 "remmina/plugin.h"
+#include "remmina/types.h"
+
+#include "python_wrapper_remmina.h"
+#include "python_wrapper_protocol_widget.h"
+
+#include "python_wrapper_entry.h"
+#include "python_wrapper_file.h"
+#include "python_wrapper_protocol.h"
+#include "python_wrapper_tool.h"
+#include "python_wrapper_secret.h"
+#include "python_wrapper_pref.h"
+
+#include "python_wrapper_remmina_file.h"
+
+#include <string.h>
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// D E C L A R A T I O N S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+/**
+ * Util function to check if a specific member is define in a Python object.
+ */
+gboolean python_wrapper_check_mandatory_member(PyObject* instance, const gchar* member);
+
+static PyObject* python_wrapper_debug_wrapper(PyObject* self, PyObject* msg);
+static PyObject* remmina_register_plugin_wrapper(PyObject* self, PyObject* plugin);
+static PyObject* remmina_file_get_datadir_wrapper(PyObject* self, PyObject* plugin);
+static PyObject* remmina_file_new_wrapper(PyObject* self, PyObject* args, PyObject* kwargs);
+static PyObject* remmina_unlock_new_wrapper(PyObject* self, PyObject* args, PyObject* kwargs);
+static PyObject* remmina_pref_set_value_wrapper(PyObject* self, PyObject* args, PyObject* kwargs);
+static PyObject* remmina_pref_get_value_wrapper(PyObject* self, PyObject* args, PyObject* kwargs);
+static PyObject* remmina_pref_get_scale_quality_wrapper(PyObject* self, PyObject* plugin);
+static PyObject* remmina_pref_get_sshtunnel_port_wrapper(PyObject* self, PyObject* plugin);
+static PyObject* remmina_pref_get_ssh_loglevel_wrapper(PyObject* self, PyObject* plugin);
+static PyObject* remmina_pref_get_ssh_parseconfig_wrapper(PyObject* self, PyObject* plugin);
+static PyObject* remmina_pref_keymap_get_keyval_wrapper(PyObject* self, PyObject* args, PyObject* kwargs);
+static PyObject* python_wrapper_log_print_wrapper(PyObject* self, PyObject* arg);
+static PyObject* remmina_widget_pool_register_wrapper(PyObject* self, PyObject* args, PyObject* kwargs);
+static PyObject* rcw_open_from_file_full_wrapper(PyObject* self, PyObject* args, PyObject* kwargs);
+static PyObject* remmina_public_get_server_port_wrapper(PyObject* self, PyObject* args, PyObject* kwargs);
+static PyObject* remmina_masterthread_exec_is_main_thread_wrapper(PyObject* self, PyObject* plugin);
+static PyObject* remmina_gtksocket_available_wrapper(PyObject* self, PyObject* plugin);
+static PyObject*
+remmina_protocol_widget_get_profile_remote_height_wrapper(PyObject* self, PyObject* args, PyObject* kwargs);
+static PyObject*
+remmina_protocol_widget_get_profile_remote_width_wrapper(PyObject* self, PyObject* args, PyObject* kwargs);
+static PyObject* python_wrapper_show_dialog_wrapper(PyObject* self, PyObject* args, PyObject* kwargs);
+static PyObject* python_wrapper_get_mainwindow_wrapper(PyObject* self, PyObject* args);
+static PyObject* remmina_protocol_plugin_signal_connection_opened_wrapper(PyObject* self, PyObject* args);
+static PyObject* remmina_protocol_plugin_signal_connection_closed_wrapper(PyObject* self, PyObject* args);
+static PyObject* remmina_protocol_plugin_init_auth_wrapper(PyObject* self, PyObject* args, PyObject* kwargs);
+
+/**
+ * Declares functions for the Remmina module. These functions can be called from Python and are wired to one of the
+ * functions here in this file.
+ */
+static PyMethodDef remmina_python_module_type_methods[] = {
+ /**
+ * The first function that need to be called from the plugin code, since it registers the Python class acting as
+ * a Remmina plugin. Without this call the plugin will not be recognized.
+ */
+ { "register_plugin", remmina_register_plugin_wrapper, METH_O, NULL },
+
+ /**
+ * Prints a string into the Remmina log infrastructure.
+ */
+ { "log_print", python_wrapper_log_print_wrapper, METH_VARARGS, NULL },
+
+ /**
+ * Prints a debug message if enabled.
+ */
+ { "debug", python_wrapper_debug_wrapper, METH_VARARGS, NULL },
+
+ /**
+ * Shows a GTK+ dialog.
+ */
+ { "show_dialog", (PyCFunction)python_wrapper_show_dialog_wrapper, METH_VARARGS | METH_KEYWORDS,
+ NULL },
+
+ /**
+ * Returns the GTK+ object of the main window of Remmina.
+ */
+ { "get_main_window", python_wrapper_get_mainwindow_wrapper, METH_NOARGS, NULL },
+
+ /**
+ * Calls remmina_file_get_datadir and returns its result.
+ */
+ { "get_datadir", remmina_file_get_datadir_wrapper, METH_VARARGS, NULL },
+
+ /**
+ * Calls remmina_file_new and returns its result.
+ */
+ { "file_new", (PyCFunction)remmina_file_new_wrapper, METH_VARARGS | METH_KEYWORDS, NULL },
+
+ /**
+ * Calls remmina_unlock_new and returns its result.
+ */
+ { "unlock_new", (PyCFunction)remmina_unlock_new_wrapper, METH_VARARGS | METH_KEYWORDS, NULL },
+
+ /**
+ * Calls remmina_pref_set_value and returns its result.
+ */
+ { "pref_set_value", (PyCFunction)remmina_pref_set_value_wrapper, METH_VARARGS | METH_KEYWORDS, NULL },
+
+ /**
+ * Calls remmina_pref_get_value and returns its result.
+ */
+ { "pref_get_value", (PyCFunction)remmina_pref_get_value_wrapper, METH_VARARGS | METH_KEYWORDS, NULL },
+
+ /**
+ * Calls remmina_pref_get_scale_quality and returns its result.
+ */
+ { "pref_get_scale_quality", remmina_pref_get_scale_quality_wrapper, METH_VARARGS, NULL },
+
+ /**
+ * Calls remmina_pref_get_sshtunnel_port and returns its result.
+ */
+ { "pref_get_sshtunnel_port", remmina_pref_get_sshtunnel_port_wrapper, METH_VARARGS, NULL },
+
+ /**
+ * Calls remmina_pref_get_ssh_loglevel and returns its result.
+ */
+ { "pref_get_ssh_loglevel", remmina_pref_get_ssh_loglevel_wrapper, METH_VARARGS, NULL },
+
+ /**
+ * Calls remmina_pref_get_ssh_parseconfig and returns its result.
+ */
+ { "pref_get_ssh_parseconfig", remmina_pref_get_ssh_parseconfig_wrapper, METH_VARARGS, NULL },
+
+ /**
+ * Calls remmina_pref_keymap_get_keyval and returns its result.
+ */
+ { "pref_keymap_get_keyval", (PyCFunction)remmina_pref_keymap_get_keyval_wrapper, METH_VARARGS | METH_KEYWORDS,
+ NULL },
+
+ /**
+ * Calls remmina_widget_pool_register and returns its result.
+ */
+ { "widget_pool_register", (PyCFunction)remmina_widget_pool_register_wrapper, METH_VARARGS | METH_KEYWORDS, NULL },
+
+ /**
+ * Calls rcw_open_from_file_full and returns its result.
+ */
+ { "rcw_open_from_file_full", (PyCFunction)rcw_open_from_file_full_wrapper, METH_VARARGS | METH_KEYWORDS, NULL },
+
+ /**
+ * Calls remmina_public_get_server_port and returns its result.
+ */
+ { "public_get_server_port", (PyCFunction)remmina_public_get_server_port_wrapper, METH_VARARGS | METH_KEYWORDS,
+ NULL },
+
+ /**
+ * Calls remmina_masterthread_exec_is_main_thread and returns its result.
+ */
+ { "masterthread_exec_is_main_thread", remmina_masterthread_exec_is_main_thread_wrapper, METH_VARARGS, NULL },
+
+ /**
+ * Calls remmina_gtksocket_available and returns its result.
+ */
+ { "gtksocket_available", remmina_gtksocket_available_wrapper, METH_VARARGS, NULL },
+
+ /**
+ * Calls remmina_protocol_widget_get_profile_remote_width and returns its result.
+ */
+ { "protocol_widget_get_profile_remote_width",
+ (PyCFunction)remmina_protocol_widget_get_profile_remote_width_wrapper, METH_VARARGS | METH_KEYWORDS, NULL },
+
+ /**
+ * Calls remmina_protocol_widget_get_profile_remote_height and returns its result.
+ */
+ { "protocol_widget_get_profile_remote_height",
+ (PyCFunction)remmina_protocol_widget_get_profile_remote_height_wrapper, METH_VARARGS | METH_KEYWORDS, NULL },
+ { "protocol_plugin_signal_connection_opened", (PyCFunction)remmina_protocol_plugin_signal_connection_opened_wrapper,
+ METH_VARARGS, NULL },
+
+ { "protocol_plugin_signal_connection_closed", (PyCFunction)remmina_protocol_plugin_signal_connection_closed_wrapper,
+ METH_VARARGS, NULL },
+
+ { "protocol_plugin_init_auth", (PyCFunction)remmina_protocol_plugin_init_auth_wrapper, METH_VARARGS | METH_KEYWORDS,
+ NULL },
+
+ /* Sentinel */
+ { NULL }
+};
+
+/**
+ * Adapter struct to handle Remmina protocol settings.
+ */
+typedef struct
+{
+ PyObject_HEAD
+ RemminaProtocolSettingType settingType;
+ gchar* name;
+ gchar* label;
+ gboolean compact;
+ PyObject* opt1;
+ PyObject* opt2;
+} PyRemminaProtocolSetting;
+
+/**
+ * The definition of the Python module 'remmina'.
+ */
+static PyModuleDef remmina_python_module_type = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "remmina",
+ .m_doc = "Remmina API.",
+ .m_size = -1,
+ .m_methods = remmina_python_module_type_methods
+};
+
+// -- Python Object -> Setting
+
+/**
+ * Initializes the memory and the fields of the remmina.Setting Python type.
+ * @details This function is callback for the Python engine.
+ */
+static PyObject* python_protocol_setting_new(PyTypeObject* type, PyObject* args, PyObject* kwargs)
+{
+ TRACE_CALL(__func__);
+
+ PyRemminaProtocolSetting* self = (PyRemminaProtocolSetting*)type->tp_alloc(type, 0);
+
+ if (!self)
+ {
+ return NULL;
+ }
+
+ self->name = "";
+ self->label = "";
+ self->compact = FALSE;
+ self->opt1 = NULL;
+ self->opt2 = NULL;
+ self->settingType = 0;
+
+ return (PyObject*)self;
+}
+
+/**
+ * Constructor of the remmina.Setting Python type.
+ * @details This function is callback for the Python engine.
+ */
+static int python_protocol_setting_init(PyRemminaProtocolSetting* self, PyObject* args, PyObject* kwargs)
+{
+ TRACE_CALL(__func__);
+
+ static gchar* kwlist[] = { "type", "name", "label", "compact", "opt1", "opt2", NULL };
+ PyObject* name;
+ PyObject* label;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|lOOpOO", kwlist,
+ &self->settingType, &name, &label, &self->compact, &self->opt1, &self->opt2))
+ {
+ return -1;
+ }
+
+ Py_ssize_t len = PyUnicode_GetLength(label);
+ if (len == 0)
+ {
+ self->label = "";
+ }
+ else
+ {
+ self->label = python_wrapper_copy_string_from_python(label, len);
+ if (!self->label)
+ {
+ g_printerr("Unable to extract label during initialization of Python settings module!\n");
+ python_wrapper_check_error();
+ }
+ }
+
+ len = PyUnicode_GetLength(name);
+ if (len == 0)
+ {
+ self->name = "";
+ }
+ else
+ {
+ self->name = python_wrapper_copy_string_from_python(label, len);
+ if (!self->name)
+ {
+ g_printerr("Unable to extract name during initialization of Python settings module!\n");
+ python_wrapper_check_error();
+ }
+ }
+
+ return 0;
+}
+
+static PyMemberDef python_protocol_setting_type_members[] = {
+ { "settingType", offsetof(PyRemminaProtocolSetting, settingType), T_INT, 0, NULL },
+ { "name", offsetof(PyRemminaProtocolSetting, name), T_STRING, 0, NULL },
+ { "label", offsetof(PyRemminaProtocolSetting, label), T_STRING, 0, NULL },
+ { "compact", offsetof(PyRemminaProtocolSetting, compact), T_BOOL, 0, NULL },
+ { "opt1", offsetof(PyRemminaProtocolSetting, opt1), T_OBJECT, 0, NULL },
+ { "opt2", offsetof(PyRemminaProtocolSetting, opt2), T_OBJECT, 0, NULL },
+ { NULL }
+};
+
+static PyTypeObject python_protocol_setting_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "remmina.Setting",
+ .tp_doc = "Remmina Setting information",
+ .tp_basicsize = sizeof(PyRemminaProtocolSetting),
+ .tp_itemsize = 0,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = python_protocol_setting_new,
+ .tp_init = (initproc)python_protocol_setting_init,
+ .tp_members = python_protocol_setting_type_members
+};
+
+// -- Python Type -> Feature
+
+
+static PyMemberDef python_protocol_feature_members[] = {
+ { "type", T_INT, offsetof(PyRemminaProtocolFeature, type), 0, NULL },
+ { "id", T_INT, offsetof(PyRemminaProtocolFeature, id), 0, NULL },
+ { "opt1", T_OBJECT, offsetof(PyRemminaProtocolFeature, opt1), 0, NULL },
+ { "opt2", T_OBJECT, offsetof(PyRemminaProtocolFeature, opt2), 0, NULL },
+ { "opt3", T_OBJECT, offsetof(PyRemminaProtocolFeature, opt3), 0, NULL },
+ { NULL }
+};
+
+PyObject* python_protocol_feature_new(PyTypeObject* type, PyObject* kws, PyObject* args)
+{
+ TRACE_CALL(__func__);
+
+ PyRemminaProtocolFeature* self;
+ self = (PyRemminaProtocolFeature*)type->tp_alloc(type, 0);
+ if (!self)
+ return NULL;
+
+ self->id = 0;
+ self->type = 0;
+ self->opt1 = (PyGeneric*)Py_None;
+ self->opt1->raw = NULL;
+ self->opt1->type_hint = REMMINA_TYPEHINT_UNDEFINED;
+ self->opt2 = (PyGeneric*)Py_None;
+ self->opt2->raw = NULL;
+ self->opt2->type_hint = REMMINA_TYPEHINT_UNDEFINED;
+ self->opt3 = (PyGeneric*)Py_None;
+ self->opt3->raw = NULL;
+ self->opt3->type_hint = REMMINA_TYPEHINT_UNDEFINED;
+
+ return (PyObject*)self;
+}
+
+static int python_protocol_feature_init(PyRemminaProtocolFeature* self, PyObject* args, PyObject* kwargs)
+{
+ TRACE_CALL(__func__);
+
+ static char* kwlist[] = { "type", "id", "opt1", "opt2", "opt3", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|llOOO", kwlist, &self->type, &self->id, &self->opt1, &self
+ ->opt2, &self->opt3))
+ return -1;
+
+ return 0;
+}
+
+static PyTypeObject python_protocol_feature_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "remmina.ProtocolFeature",
+ .tp_doc = "Remmina Setting information",
+ .tp_basicsize = sizeof(PyRemminaProtocolFeature),
+ .tp_itemsize = 0,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = python_protocol_feature_new,
+ .tp_init = (initproc)python_protocol_feature_init,
+ .tp_members = python_protocol_feature_members
+};
+
+PyRemminaProtocolFeature* python_wrapper_protocol_feature_new(void)
+{
+ PyRemminaProtocolFeature* feature = (PyRemminaProtocolFeature*)PyObject_New(PyRemminaProtocolFeature, &python_protocol_feature_type);
+ feature->id = 0;
+ feature->opt1 = python_wrapper_generic_new();
+ feature->opt1->raw = NULL;
+ feature->opt1->type_hint = REMMINA_TYPEHINT_UNDEFINED;
+ feature->opt2 = python_wrapper_generic_new();
+ feature->opt2->raw = NULL;
+ feature->opt2->type_hint = REMMINA_TYPEHINT_UNDEFINED;
+ feature->opt3 = python_wrapper_generic_new();
+ feature->opt3->raw = NULL;
+ feature->opt3->type_hint = REMMINA_TYPEHINT_UNDEFINED;
+ feature->type = 0;
+ Py_IncRef((PyObject*)feature);
+ return feature;
+}
+
+
+// -- Python Type -> Screenshot Data
+
+static PyMemberDef python_screenshot_data_members[] = {
+ { "buffer", T_OBJECT, offsetof(PyRemminaPluginScreenshotData, buffer), 0, NULL },
+ { "width", T_INT, offsetof(PyRemminaPluginScreenshotData, width), 0, NULL },
+ { "height", T_INT, offsetof(PyRemminaPluginScreenshotData, height), 0, NULL },
+ { "bitsPerPixel", T_INT, offsetof(PyRemminaPluginScreenshotData, bitsPerPixel), 0, NULL },
+ { "bytesPerPixel", T_INT, offsetof(PyRemminaPluginScreenshotData, bytesPerPixel), 0, NULL },
+ { NULL }
+};
+
+PyObject* python_screenshot_data_new(PyTypeObject* type, PyObject* kws, PyObject* args)
+{
+ TRACE_CALL(__func__);
+
+ PyRemminaPluginScreenshotData* self;
+ self = (PyRemminaPluginScreenshotData*)type->tp_alloc(type, 0);
+ if (!self)
+ return NULL;
+
+ self->buffer = (PyByteArrayObject*)PyObject_New(PyByteArrayObject, &PyByteArray_Type);
+ self->height = 0;
+ self->width = 0;
+ self->bitsPerPixel = 0;
+ self->bytesPerPixel = 0;
+
+ return (PyObject*)self;
+}
+
+static int python_screenshot_data_init(PyRemminaPluginScreenshotData* self, PyObject* args, PyObject* kwargs)
+{
+ TRACE_CALL(__func__);
+
+ g_printerr("Not to be initialized within Python!");
+ return -1;
+}
+
+static PyTypeObject python_screenshot_data_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "remmina.RemminaScreenshotData",
+ .tp_doc = "Remmina Screenshot Data",
+ .tp_basicsize = sizeof(PyRemminaPluginScreenshotData),
+ .tp_itemsize = 0,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = python_screenshot_data_new,
+ .tp_init = (initproc)python_screenshot_data_init,
+ .tp_members = python_screenshot_data_members
+};
+PyRemminaPluginScreenshotData* python_wrapper_screenshot_data_new(void)
+{
+ PyRemminaPluginScreenshotData* data = (PyRemminaPluginScreenshotData*)PyObject_New(PyRemminaPluginScreenshotData, &python_screenshot_data_type);
+ data->buffer = PyObject_New(PyByteArrayObject, &PyByteArray_Type);
+ Py_IncRef((PyObject*)data->buffer);
+ data->height = 0;
+ data->width = 0;
+ data->bitsPerPixel = 0;
+ data->bytesPerPixel = 0;
+ return data;
+}
+
+static PyObject* python_wrapper_generic_to_int(PyGeneric* self, PyObject* args);
+static PyObject* python_wrapper_generic_to_bool(PyGeneric* self, PyObject* args);
+static PyObject* python_wrapper_generic_to_string(PyGeneric* self, PyObject* args);
+
+static void python_wrapper_generic_dealloc(PyObject* self)
+{
+ PyObject_Del(self);
+}
+
+static PyMethodDef python_wrapper_generic_methods[] = {
+ { "to_int", (PyCFunction)python_wrapper_generic_to_int, METH_NOARGS, "" },
+ { "to_bool", (PyCFunction)python_wrapper_generic_to_bool, METH_NOARGS, "" },
+ { "to_string", (PyCFunction)python_wrapper_generic_to_string, METH_NOARGS, "" },
+ { NULL }
+};
+
+static PyMemberDef python_wrapper_generic_members[] = {
+ { "raw", T_OBJECT, offsetof(PyGeneric, raw), 0, "" },
+ { NULL }
+};
+
+PyObject* python_wrapper_generic_type_new(PyTypeObject* type, PyObject* kws, PyObject* args)
+{
+ TRACE_CALL(__func__);
+
+ PyGeneric* self;
+ self = (PyGeneric*)type->tp_alloc(type, 0);
+ if (!self)
+ return NULL;
+
+ self->raw = Py_None;
+
+ return (PyObject*)self;
+}
+
+static int python_wrapper_generic_init(PyGeneric* self, PyObject* args, PyObject* kwargs)
+{
+ TRACE_CALL(__func__);
+
+ static char* kwlist[] = { "raw", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwlist, &self->raw))
+ return -1;
+
+ return 0;
+}
+
+static PyTypeObject python_generic_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "remmina.Generic",
+ .tp_doc = "",
+ .tp_basicsize = sizeof(PyGeneric),
+ .tp_itemsize = 0,
+ .tp_dealloc = python_wrapper_generic_dealloc,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_new = python_wrapper_generic_type_new,
+ .tp_init = (initproc)python_wrapper_generic_init,
+ .tp_members = python_wrapper_generic_members,
+ .tp_methods = python_wrapper_generic_methods,
+};
+
+PyGeneric* python_wrapper_generic_new(void)
+{
+ PyGeneric* generic = (PyGeneric*)PyObject_New(PyGeneric, &python_generic_type);
+ generic->raw = PyLong_FromLongLong(0LL);
+ Py_IncRef((PyObject*)generic);
+ return generic;
+}
+
+static PyObject* python_wrapper_generic_to_int(PyGeneric* self, PyObject* args)
+{
+ SELF_CHECK();
+
+ if (self->raw == NULL)
+ {
+ return Py_None;
+ }
+ else if (self->type_hint == REMMINA_TYPEHINT_SIGNED)
+ {
+ return PyLong_FromLongLong((long long)self->raw);
+ }
+ else if (self->type_hint == REMMINA_TYPEHINT_UNSIGNED)
+ {
+ return PyLong_FromUnsignedLongLong((unsigned long long)self->raw);
+ }
+
+ return Py_None;
+}
+static PyObject* python_wrapper_generic_to_bool(PyGeneric* self, PyObject* args)
+{
+ SELF_CHECK();
+
+ if (self->raw == NULL)
+ {
+ return Py_None;
+ }
+ else if (self->type_hint == REMMINA_TYPEHINT_BOOLEAN)
+ {
+ return PyBool_FromLong((long)self->raw);
+ }
+
+ return Py_None;
+}
+static PyObject* python_wrapper_generic_to_string(PyGeneric* self, PyObject* args)
+{
+ SELF_CHECK();
+
+ if (self->raw == NULL)
+ {
+ return Py_None;
+ }
+ else if (self->type_hint == REMMINA_TYPEHINT_STRING)
+ {
+ return PyUnicode_FromString((const char*)self->raw);
+ }
+
+ return Py_None;
+}
+
+/**
+ * Is called from the Python engine when it initializes the 'remmina' module.
+ * @details This function is only called by the Python engine!
+ */
+PyMODINIT_FUNC python_wrapper_module_initialize(void)
+{
+ TRACE_CALL(__func__);
+
+ if (PyType_Ready(&python_screenshot_data_type) < 0)
+ {
+ PyErr_Print();
+ return NULL;
+ }
+
+ if (PyType_Ready(&python_generic_type) < 0)
+ {
+ PyErr_Print();
+ return NULL;
+ }
+
+ if (PyType_Ready(&python_protocol_setting_type) < 0)
+ {
+ PyErr_Print();
+ return NULL;
+ }
+
+ if (PyType_Ready(&python_protocol_feature_type) < 0)
+ {
+ PyErr_Print();
+ return NULL;
+ }
+
+ python_wrapper_protocol_widget_type_ready();
+ python_wrapper_remmina_init_types();
+
+ PyObject* module = PyModule_Create(&remmina_python_module_type);
+ if (!module)
+ {
+ PyErr_Print();
+ return NULL;
+ }
+
+ PyModule_AddIntConstant(module, "BUTTONS_CLOSE", (long)GTK_BUTTONS_CLOSE);
+ PyModule_AddIntConstant(module, "BUTTONS_NONE", (long)GTK_BUTTONS_NONE);
+ PyModule_AddIntConstant(module, "BUTTONS_OK", (long)GTK_BUTTONS_OK);
+ PyModule_AddIntConstant(module, "BUTTONS_CLOSE", (long)GTK_BUTTONS_CLOSE);
+ PyModule_AddIntConstant(module, "BUTTONS_CANCEL", (long)GTK_BUTTONS_CANCEL);
+ PyModule_AddIntConstant(module, "BUTTONS_YES_NO", (long)GTK_BUTTONS_YES_NO);
+ PyModule_AddIntConstant(module, "BUTTONS_OK_CANCEL", (long)GTK_BUTTONS_OK_CANCEL);
+
+ PyModule_AddIntConstant(module, "MESSAGE_INFO", (long)GTK_MESSAGE_INFO);
+ PyModule_AddIntConstant(module, "MESSAGE_WARNING", (long)GTK_MESSAGE_WARNING);
+ PyModule_AddIntConstant(module, "MESSAGE_QUESTION", (long)GTK_MESSAGE_QUESTION);
+ PyModule_AddIntConstant(module, "MESSAGE_ERROR", (long)GTK_MESSAGE_ERROR);
+ PyModule_AddIntConstant(module, "MESSAGE_OTHER", (long)GTK_MESSAGE_OTHER);
+
+ PyModule_AddIntConstant(module, "PROTOCOL_SETTING_TYPE_SERVER", (long)REMMINA_PROTOCOL_SETTING_TYPE_SERVER);
+ PyModule_AddIntConstant(module, "PROTOCOL_SETTING_TYPE_PASSWORD", (long)REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD);
+ PyModule_AddIntConstant(module, "PROTOCOL_SETTING_TYPE_RESOLUTION", (long)REMMINA_PROTOCOL_SETTING_TYPE_RESOLUTION);
+ PyModule_AddIntConstant(module, "PROTOCOL_SETTING_TYPE_ASSISTANCE", (long)REMMINA_PROTOCOL_SETTING_TYPE_ASSISTANCE);
+ PyModule_AddIntConstant(module, "PROTOCOL_SETTING_TYPE_KEYMAP", (long)REMMINA_PROTOCOL_SETTING_TYPE_KEYMAP);
+ PyModule_AddIntConstant(module, "PROTOCOL_SETTING_TYPE_TEXT", (long)REMMINA_PROTOCOL_SETTING_TYPE_TEXT);
+ PyModule_AddIntConstant(module, "PROTOCOL_SETTING_TYPE_SELECT", (long)REMMINA_PROTOCOL_SETTING_TYPE_SELECT);
+ PyModule_AddIntConstant(module, "PROTOCOL_SETTING_TYPE_COMBO", (long)REMMINA_PROTOCOL_SETTING_TYPE_COMBO);
+ PyModule_AddIntConstant(module, "PROTOCOL_SETTING_TYPE_CHECK", (long)REMMINA_PROTOCOL_SETTING_TYPE_CHECK);
+ PyModule_AddIntConstant(module, "PROTOCOL_SETTING_TYPE_FILE", (long)REMMINA_PROTOCOL_SETTING_TYPE_FILE);
+ PyModule_AddIntConstant(module, "PROTOCOL_SETTING_TYPE_FOLDER", (long)REMMINA_PROTOCOL_SETTING_TYPE_FOLDER);
+ PyModule_AddIntConstant(module, "PROTOCOL_FEATURE_TYPE_MULTIMON", (long)REMMINA_PROTOCOL_FEATURE_TYPE_MULTIMON);
+
+ PyModule_AddIntConstant(module, "PROTOCOL_FEATURE_TYPE_PREF", (long)REMMINA_PROTOCOL_FEATURE_TYPE_PREF);
+ PyModule_AddIntConstant(module, "PROTOCOL_FEATURE_TYPE_TOOL", (long)REMMINA_PROTOCOL_FEATURE_TYPE_TOOL);
+ PyModule_AddIntConstant(module, "PROTOCOL_FEATURE_TYPE_UNFOCUS", (long)REMMINA_PROTOCOL_FEATURE_TYPE_UNFOCUS);
+ PyModule_AddIntConstant(module, "PROTOCOL_FEATURE_TYPE_SCALE", (long)REMMINA_PROTOCOL_FEATURE_TYPE_SCALE);
+ PyModule_AddIntConstant(module, "PROTOCOL_FEATURE_TYPE_DYNRESUPDATE", (long)REMMINA_PROTOCOL_FEATURE_TYPE_DYNRESUPDATE);
+ PyModule_AddIntConstant(module, "PROTOCOL_FEATURE_TYPE_GTKSOCKET", (long)REMMINA_PROTOCOL_FEATURE_TYPE_GTKSOCKET);
+
+ PyModule_AddIntConstant(module, "PROTOCOL_SSH_SETTING_NONE", (long)REMMINA_PROTOCOL_SSH_SETTING_NONE);
+ PyModule_AddIntConstant(module, "PROTOCOL_SSH_SETTING_TUNNEL", (long)REMMINA_PROTOCOL_SSH_SETTING_TUNNEL);
+ PyModule_AddIntConstant(module, "PROTOCOL_SSH_SETTING_SSH", (long)REMMINA_PROTOCOL_SSH_SETTING_SSH);
+ PyModule_AddIntConstant(module, "PROTOCOL_SSH_SETTING_REVERSE_TUNNEL", (long)REMMINA_PROTOCOL_SSH_SETTING_REVERSE_TUNNEL);
+ PyModule_AddIntConstant(module, "PROTOCOL_SSH_SETTING_SFTP", (long)REMMINA_PROTOCOL_SSH_SETTING_SFTP);
+
+ PyModule_AddIntConstant(module, "PROTOCOL_FEATURE_PREF_RADIO", (long)REMMINA_PROTOCOL_FEATURE_PREF_RADIO);
+ PyModule_AddIntConstant(module, "PROTOCOL_FEATURE_PREF_CHECK", (long)REMMINA_PROTOCOL_FEATURE_PREF_CHECK);
+
+ PyModule_AddIntConstant(module, "MESSAGE_PANEL_FLAG_USERNAME", REMMINA_MESSAGE_PANEL_FLAG_USERNAME);
+ PyModule_AddIntConstant(module, "MESSAGE_PANEL_FLAG_USERNAME_READONLY", REMMINA_MESSAGE_PANEL_FLAG_USERNAME_READONLY);
+ PyModule_AddIntConstant(module, "MESSAGE_PANEL_FLAG_DOMAIN", REMMINA_MESSAGE_PANEL_FLAG_DOMAIN);
+ PyModule_AddIntConstant(module, "MESSAGE_PANEL_FLAG_SAVEPASSWORD", REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD);
+
+ if (PyModule_AddObject(module, "Setting", (PyObject*)&python_protocol_setting_type) < 0)
+ {
+ Py_DECREF(&python_protocol_setting_type);
+ Py_DECREF(module);
+ PyErr_Print();
+ return NULL;
+ }
+
+ Py_INCREF(&python_protocol_feature_type);
+ if (PyModule_AddObject(module, "Feature", (PyObject*)&python_protocol_feature_type) < 0)
+ {
+ Py_DECREF(&python_protocol_setting_type);
+ Py_DECREF(&python_protocol_feature_type);
+ Py_DECREF(module);
+ PyErr_Print();
+ return NULL;
+ }
+
+ return module;
+}
+
+/**
+ * Initializes all globals and registers the 'remmina' module in the Python engine.
+ * @details This
+ */
+void python_wrapper_module_init(void)
+{
+ TRACE_CALL(__func__);
+
+ if (PyImport_AppendInittab("remmina", python_wrapper_module_initialize))
+ {
+ PyErr_Print();
+ exit(1);
+ }
+
+ python_wrapper_entry_init();
+ python_wrapper_protocol_init();
+ python_wrapper_tool_init();
+ python_wrapper_pref_init();
+ python_wrapper_secret_init();
+ python_wrapper_file_init();
+}
+
+gboolean python_wrapper_check_mandatory_member(PyObject* instance, const gchar* member)
+{
+ TRACE_CALL(__func__);
+
+ if (PyObject_HasAttrString(instance, member))
+ {
+ return TRUE;
+ }
+
+ g_printerr("Missing mandatory member '%s' in Python plugin instance!\n", member);
+ return FALSE;
+}
+
+static PyObject* remmina_register_plugin_wrapper(PyObject* self, PyObject* plugin_instance)
+{
+ TRACE_CALL(__func__);
+
+ if (plugin_instance)
+ {
+ if (!python_wrapper_check_mandatory_member(plugin_instance, "name")
+ || !python_wrapper_check_mandatory_member(plugin_instance, "version"))
+ {
+ return NULL;
+ }
+
+ /* Protocol plugin definition and features */
+ const gchar* pluginType = PyUnicode_AsUTF8(PyObject_GetAttrString(plugin_instance, "type"));
+
+ RemminaPlugin* remmina_plugin = NULL;
+
+ PyPlugin* plugin = (PyPlugin*)python_wrapper_malloc(sizeof(PyPlugin));
+ plugin->instance = plugin_instance;
+ Py_INCREF(plugin_instance);
+ plugin->protocol_plugin = NULL;
+ plugin->entry_plugin = NULL;
+ plugin->file_plugin = NULL;
+ plugin->pref_plugin = NULL;
+ plugin->secret_plugin = NULL;
+ plugin->tool_plugin = NULL;
+ g_print("New Python plugin registered: %ld\n", PyObject_Hash(plugin_instance));
+
+ if (g_str_equal(pluginType, "protocol"))
+ {
+ remmina_plugin = python_wrapper_create_protocol_plugin(plugin);
+ }
+ else if (g_str_equal(pluginType, "entry"))
+ {
+ remmina_plugin = python_wrapper_create_entry_plugin(plugin);
+ }
+ else if (g_str_equal(pluginType, "file"))
+ {
+ remmina_plugin = python_wrapper_create_file_plugin(plugin);
+ }
+ else if (g_str_equal(pluginType, "tool"))
+ {
+ remmina_plugin = python_wrapper_create_tool_plugin(plugin);
+ }
+ else if (g_str_equal(pluginType, "pref"))
+ {
+ remmina_plugin = python_wrapper_create_pref_plugin(plugin);
+ }
+ else if (g_str_equal(pluginType, "secret"))
+ {
+ remmina_plugin = python_wrapper_create_secret_plugin(plugin);
+ }
+ else
+ {
+ g_printerr("Unknown plugin type: %s\n", pluginType);
+ }
+
+ if (remmina_plugin)
+ {
+ if (remmina_plugin->type == REMMINA_PLUGIN_TYPE_PROTOCOL)
+ {
+ plugin->gp = python_wrapper_protocol_widget_create();
+ }
+
+ if (python_wrapper_get_service()->register_plugin((RemminaPlugin*)remmina_plugin)) {
+ g_print("%s: Successfully reigstered!\n", remmina_plugin->name);
+ } else {
+ g_print("%s: Failed to reigster!\n", remmina_plugin->name);
+ }
+ }
+ }
+
+ PyErr_Clear();
+ return Py_None;
+}
+
+static PyObject* remmina_file_get_datadir_wrapper(PyObject* self, PyObject* plugin)
+{
+ TRACE_CALL(__func__);
+
+ PyObject* result = Py_None;
+ const gchar* datadir = python_wrapper_get_service()->file_get_user_datadir();
+
+ if (datadir)
+ {
+ result = PyUnicode_FromFormat("%s", datadir);
+ }
+
+ python_wrapper_check_error();
+ return result;
+}
+
+static PyObject* remmina_file_new_wrapper(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+ TRACE_CALL(__func__);
+
+ RemminaFile* file = python_wrapper_get_service()->file_new();
+ if (file)
+ {
+ return (PyObject*)python_wrapper_remmina_file_to_python(file);
+ }
+
+ python_wrapper_check_error();
+ return Py_None;
+}
+
+static PyObject* remmina_unlock_new_wrapper(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+ TRACE_CALL(__func__);
+
+ static char* kwlist[] = { "window", NULL};
+ GtkWindow* window = NULL;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|0", kwlist, &window))
+ {
+ return Py_None;
+ }
+
+ return PyBool_FromLong(python_wrapper_get_service()->plugin_unlock_new(window));
+}
+
+static PyObject* remmina_pref_set_value_wrapper(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+ TRACE_CALL(__func__);
+
+ static char* kwlist[] = { "key", "value", NULL };
+ gchar* key, * value;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ss", kwlist, &key, &value))
+ {
+ return Py_None;
+ }
+
+ if (key)
+ {
+ python_wrapper_get_service()->pref_set_value(key, value);
+ }
+
+ python_wrapper_check_error();
+ return Py_None;
+}
+
+static PyObject* remmina_pref_get_value_wrapper(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+ TRACE_CALL(__func__);
+
+ static char* kwlist[] = { "key", NULL };
+ gchar* key;
+ PyObject* result = Py_None;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwlist, &key))
+ {
+ return Py_None;
+ }
+
+ if (key)
+ {
+ const gchar* value = python_wrapper_get_service()->pref_get_value(key);
+ if (value)
+ {
+ result = PyUnicode_FromFormat("%s", value);
+ }
+ }
+
+ python_wrapper_check_error();
+ return result;
+}
+
+static PyObject* remmina_pref_get_scale_quality_wrapper(PyObject* self, PyObject* plugin)
+{
+ TRACE_CALL(__func__);
+
+ PyObject* result = PyLong_FromLong(python_wrapper_get_service()->pref_get_scale_quality());
+ python_wrapper_check_error();
+ return result;
+}
+
+static PyObject* remmina_pref_get_sshtunnel_port_wrapper(PyObject* self, PyObject* plugin)
+{
+ TRACE_CALL(__func__);
+
+ PyObject* result = PyLong_FromLong(python_wrapper_get_service()->pref_get_sshtunnel_port());
+ python_wrapper_check_error();
+ return result;
+}
+
+static PyObject* remmina_pref_get_ssh_loglevel_wrapper(PyObject* self, PyObject* plugin)
+{
+ TRACE_CALL(__func__);
+
+ PyObject* result = PyLong_FromLong(python_wrapper_get_service()->pref_get_ssh_loglevel());
+ python_wrapper_check_error();
+ return result;
+}
+
+static PyObject* remmina_pref_get_ssh_parseconfig_wrapper(PyObject* self, PyObject* plugin)
+{
+ TRACE_CALL(__func__);
+
+ PyObject* result = PyLong_FromLong(python_wrapper_get_service()->pref_get_ssh_parseconfig());
+ python_wrapper_check_error();
+ return result;
+}
+
+static PyObject* remmina_pref_keymap_get_keyval_wrapper(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+ TRACE_CALL(__func__);
+
+ static char* kwlist[] = { "keymap", "keyval", NULL };
+ gchar* keymap;
+ guint keyval;
+ PyObject* result = Py_None;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sl", kwlist, &keymap, &keyval))
+ {
+ return PyLong_FromLong(-1);
+ }
+
+ if (keymap)
+ {
+ const guint value = python_wrapper_get_service()->pref_keymap_get_keyval(keymap, keyval);
+ result = PyLong_FromUnsignedLong(value);
+ }
+
+ python_wrapper_check_error();
+ return result;
+}
+
+static PyObject* python_wrapper_log_print_wrapper(PyObject* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+
+ gchar* text;
+ if (!PyArg_ParseTuple(args, "s", &text) || !text)
+ {
+ return Py_None;
+ }
+
+ python_wrapper_get_service()->log_print(text);
+ return Py_None;
+}
+
+static PyObject* python_wrapper_debug_wrapper(PyObject* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+
+ gchar* text;
+ if (!PyArg_ParseTuple(args, "s", &text) || !text)
+ {
+ return Py_None;
+ }
+
+ python_wrapper_get_service()->_remmina_debug("python", "%s", text);
+ return Py_None;
+}
+
+static PyObject* remmina_widget_pool_register_wrapper(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+ TRACE_CALL(__func__);
+
+ static char* kwlist[] = { "widget", NULL };
+ PyObject* widget;
+
+ if (PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &widget) && widget)
+ {
+ python_wrapper_get_service()->widget_pool_register(get_pywidget(widget));
+ }
+
+ return Py_None;
+}
+
+static PyObject* rcw_open_from_file_full_wrapper(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+ TRACE_CALL(__func__);
+
+ static char* kwlist[] = { "remminafile", "data", "handler", NULL };
+ PyObject* pyremminafile;
+ PyObject* data;
+
+ if (PyArg_ParseTupleAndKeywords(args, kwargs, "OOO", kwlist, &pyremminafile, &data) && pyremminafile && data)
+ {
+ python_wrapper_get_service()->rcw_open_from_file_full((RemminaFile*)pyremminafile, NULL, (void*)data, NULL);
+ }
+
+ return Py_None;
+}
+
+static PyObject* remmina_public_get_server_port_wrapper(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+ TRACE_CALL(__func__);
+
+ static char* kwlist[] = { "server", "defaultport", NULL };
+ gchar* server;
+ gint defaultport;
+
+ if (PyArg_ParseTupleAndKeywords(args, kwargs, "sl", kwlist, &server, &defaultport) && server)
+ {
+ gchar* host;
+ gint port;
+ python_wrapper_get_service()->get_server_port(server, defaultport, &host, &port);
+
+ PyObject* result = PyList_New(2);
+ PyList_SetItem(result, 0, PyUnicode_FromString(host));
+ PyList_SetItem(result, 1, PyLong_FromLong(port));
+
+ return PyList_AsTuple(result);
+ }
+
+ return Py_None;
+}
+
+static PyObject* remmina_masterthread_exec_is_main_thread_wrapper(PyObject* self, PyObject* plugin)
+{
+ TRACE_CALL(__func__);
+
+ return PyBool_FromLong(python_wrapper_get_service()->is_main_thread());
+}
+
+static PyObject* remmina_gtksocket_available_wrapper(PyObject* self, PyObject* plugin)
+{
+ TRACE_CALL(__func__);
+
+ return PyBool_FromLong(python_wrapper_get_service()->gtksocket_available());
+}
+
+static PyObject*
+remmina_protocol_widget_get_profile_remote_height_wrapper(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+ TRACE_CALL(__func__);
+
+ static char* kwlist[] = { "widget", NULL };
+ PyPlugin* plugin;
+
+ if (PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &plugin) && plugin && plugin->gp)
+ {
+ python_wrapper_get_service()->get_profile_remote_height(plugin->gp->gp);
+ }
+
+ return Py_None;
+}
+
+static PyObject*
+remmina_protocol_widget_get_profile_remote_width_wrapper(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+ TRACE_CALL(__func__);
+
+ static char* kwlist[] = { "widget", NULL };
+ PyPlugin* plugin;
+
+ if (PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &plugin) && plugin && plugin->gp)
+ {
+ python_wrapper_get_service()->get_profile_remote_width(plugin->gp->gp);
+ }
+
+ return Py_None;
+}
+
+void python_wrapper_to_protocol_setting(RemminaProtocolSetting* dest, PyObject* setting)
+{
+ TRACE_CALL(__func__);
+
+ PyRemminaProtocolSetting* src = (PyRemminaProtocolSetting*)setting;
+ Py_INCREF(setting);
+ dest->name = src->name;
+ dest->label = src->label;
+ dest->compact = src->compact;
+ dest->type = src->settingType;
+ dest->validator = NULL;
+ dest->validator_data = NULL;
+ python_wrapper_to_generic(src->opt1, &dest->opt1);
+ python_wrapper_to_generic(src->opt2, &dest->opt2);
+}
+
+void python_wrapper_to_protocol_feature(RemminaProtocolFeature* dest, PyObject* feature)
+{
+ TRACE_CALL(__func__);
+
+ PyRemminaProtocolFeature* src = (PyRemminaProtocolFeature*)feature;
+ Py_INCREF(feature);
+ dest->id = src->id;
+ dest->type = src->type;
+ dest->opt1 = src->opt1->raw;
+ dest->opt1_type_hint = src->opt1->type_hint;
+ dest->opt2 = src->opt2->raw;
+ dest->opt2_type_hint = src->opt2->type_hint;
+ dest->opt3 = src->opt3->raw;
+ dest->opt3_type_hint = src->opt3->type_hint;
+}
+
+PyObject* python_wrapper_show_dialog_wrapper(PyObject* self, PyObject* args, PyObject* kwargs)
+{
+ TRACE_CALL(__func__);
+
+ static char* kwlist[] = { "type", "buttons", "message", NULL };
+ GtkMessageType msgType;
+ GtkButtonsType btnType;
+ gchar* message;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "lls", kwlist, &msgType, &btnType, &message))
+ {
+ return PyLong_FromLong(-1);
+ }
+
+ python_wrapper_get_service()->show_dialog(msgType, btnType, message);
+
+ return Py_None;
+}
+
+PyObject* python_wrapper_get_mainwindow_wrapper(PyObject* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+
+ GtkWindow* result = python_wrapper_get_service()->get_window();
+
+ if (!result)
+ {
+ return Py_None;
+ }
+
+ return (PyObject*)new_pywidget((GObject*)result);
+}
+
+static PyObject* remmina_protocol_plugin_signal_connection_closed_wrapper(PyObject* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+
+ PyObject* pygp = NULL;
+ if (!PyArg_ParseTuple(args, "O", &pygp) || !pygp)
+ {
+ g_printerr("Please provide the Remmina protocol widget instance!");
+ return Py_None;
+ }
+
+ python_wrapper_get_service()->protocol_plugin_signal_connection_closed(((PyRemminaProtocolWidget*)pygp)->gp);
+ return Py_None;
+}
+
+static PyObject* remmina_protocol_plugin_init_auth_wrapper(PyObject* module, PyObject* args, PyObject* kwds)
+{
+ TRACE_CALL(__func__);
+
+ static gchar* keyword_list[] = { "widget", "flags", "title", "default_username", "default_password",
+ "default_domain", "password_prompt" };
+
+ PyRemminaProtocolWidget* self;
+ gint pflags = 0;
+ gchar* title, * default_username, * default_password, * default_domain, * password_prompt;
+
+ if (PyArg_ParseTupleAndKeywords(args, kwds, "Oisssss", keyword_list, &self, &pflags, &title, &default_username,
+ &default_password, &default_domain, &password_prompt))
+ {
+ if (pflags != 0 && !(pflags & REMMINA_MESSAGE_PANEL_FLAG_USERNAME)
+ && !(pflags & REMMINA_MESSAGE_PANEL_FLAG_USERNAME_READONLY)
+ && !(pflags & REMMINA_MESSAGE_PANEL_FLAG_DOMAIN)
+ && !(pflags & REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD))
+ {
+ g_printerr("panel_auth(pflags, title, default_username, default_password, default_domain, password_prompt): "
+ "%d is not a known value for RemminaMessagePanelFlags!\n", pflags);
+ }
+ else
+ {
+ return Py_BuildValue("i", python_wrapper_get_service()->protocol_widget_panel_auth(self
+ ->gp, pflags, title, default_username, default_password, default_domain, password_prompt));
+ }
+ }
+ else
+ {
+ g_printerr("panel_auth(pflags, title, default_username, default_password, default_domain, password_prompt): Error parsing arguments!\n");
+ PyErr_Print();
+ }
+ return Py_None;
+}
+
+static PyObject* remmina_protocol_plugin_signal_connection_opened_wrapper(PyObject* self, PyObject* args)
+{
+ PyObject* pygp = NULL;
+ if (!PyArg_ParseTuple(args, "O", &pygp) || !pygp)
+ {
+ g_printerr("Please provide the Remmina protocol widget instance!");
+ return Py_None;
+ }
+
+ python_wrapper_get_service()->protocol_plugin_signal_connection_opened(((PyRemminaProtocolWidget*)pygp)->gp);
+ return Py_None;
+}
diff --git a/plugins/python_wrapper/python_wrapper_remmina.h b/plugins/python_wrapper/python_wrapper_remmina.h
new file mode 100644
index 0000000..4b3df13
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_remmina.h
@@ -0,0 +1,93 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2014-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * 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.
+ */
+
+/**
+ * @file python_wrapper_remmina.h
+ *
+ * @brief Contains the implementation of the Python module 'remmina', provided to interface with the application from
+ * the Python plugin source.
+ *
+ * @detail In contrast to the wrapper functions that exist in the plugin specialisation files (e.g.
+ * python_wrapper_protocol.c or python_wrapper_entry.c), this file contains the API for the
+ * communication in the direction, from Python to Remmina. This means, if in the Python plugin a function
+ * is called, that is defined in Remmina, C code, at least in this file, is executed.
+ */
+
+#pragma once
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// I N C L U D E S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "python_wrapper_common.h"
+#include "python_wrapper_protocol_widget.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+G_BEGIN_DECLS
+
+/**
+ * Initializes the 'remmina' module in the Python engine.
+ */
+void python_wrapper_module_init(void);
+
+/**
+ * @brief Returns a pointer to the Python instance, mapped to the RemminaProtocolWidget or null if not found.
+ *
+ * @param gp The widget that is owned by the plugin that should be found.
+ *
+ * @details Remmina expects this callback function to be part of one plugin, which is the reason no instance information
+ * is explicitly passed. To bridge that, this function can be used to retrieve the very plugin instance owning
+ * the given RemminaProtocolWidget.
+ */
+PyPlugin* python_wrapper_module_get_plugin(RemminaProtocolWidget* gp);
+
+/**
+ * @brief Converts the PyObject to RemminaProtocolSetting.
+ *
+ * @param dest A target for the converted value.
+ * @param setting The source value to convert.
+ */
+void python_wrapper_to_protocol_setting(RemminaProtocolSetting* dest, PyObject* setting);
+
+/**
+ * @brief Converts the PyObject to RemminaProtocolFeature.
+ *
+ * @param dest A target for the converted value.
+ * @param setting The source value to convert.
+ */
+void python_wrapper_to_protocol_feature(RemminaProtocolFeature* dest, PyObject* feature);
+
+G_END_DECLS
diff --git a/plugins/python_wrapper/python_wrapper_remmina_file.c b/plugins/python_wrapper/python_wrapper_remmina_file.c
new file mode 100644
index 0000000..001c940
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_remmina_file.c
@@ -0,0 +1,205 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * 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 "remmina/remmina_trace_calls.h"
+#include "python_wrapper_common.h"
+#include "python_wrapper_remmina_file.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// D E C L A R A T I O N S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static PyObject* file_get_path(PyRemminaFile* self, PyObject* args);
+static PyObject* file_set_setting(PyRemminaFile* self, PyObject* args, PyObject* kwds);
+static PyObject* file_get_setting(PyRemminaFile* self, PyObject* args, PyObject* kwds);
+static PyObject* file_get_secret(PyRemminaFile* self, PyObject* setting);
+static PyObject* file_unsave_passwords(PyRemminaFile* self, PyObject* args);
+
+static void file_dealloc(PyObject* self)
+{
+ PyObject_Del(self);
+}
+
+static PyMethodDef python_remmina_file_type_methods[] = {
+ { "get_path", (PyCFunction)file_get_path, METH_NOARGS, "" },
+ { "set_setting", (PyCFunction)file_set_setting, METH_VARARGS | METH_KEYWORDS, "Set file setting" },
+ { "get_setting", (PyCFunction)file_get_setting, METH_VARARGS | METH_KEYWORDS, "Get file setting" },
+ { "get_secret", (PyCFunction)file_get_secret, METH_VARARGS, "Get secret file setting" },
+ { "unsave_passwords", (PyCFunction)file_unsave_passwords, METH_NOARGS, "" },
+ { NULL }
+};
+
+/**
+ * @brief The definition of the Python module 'remmina'.
+ */
+static PyTypeObject python_remmina_file_type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "remmina.RemminaFile",
+ .tp_doc = "",
+ .tp_basicsize = sizeof(PyRemminaFile),
+ .tp_itemsize = 0,
+ .tp_flags = Py_TPFLAGS_DEFAULT,
+ .tp_methods = python_remmina_file_type_methods,
+ .tp_dealloc = file_dealloc
+};
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+void python_wrapper_remmina_init_types(void)
+{
+ PyType_Ready(&python_remmina_file_type);
+}
+
+PyRemminaFile* python_wrapper_remmina_file_to_python(RemminaFile* file)
+{
+ TRACE_CALL(__func__);
+
+ PyRemminaFile* result = PyObject_New(PyRemminaFile, &python_remmina_file_type);
+ result->file = file;
+ Py_INCREF(result);
+ return result;
+}
+
+static PyObject* file_get_path(PyRemminaFile* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+
+ return Py_BuildValue("s", python_wrapper_get_service()->file_get_path(self->file));
+}
+
+static PyObject* file_set_setting(PyRemminaFile* self, PyObject* args, PyObject* kwds)
+{
+ TRACE_CALL(__func__);
+
+ static char* keyword_list[] = { "key", "value", NULL };
+ gchar* key;
+ PyObject* value;
+
+ if (PyArg_ParseTupleAndKeywords(args, kwds, "s|O", keyword_list, &key, &value))
+ {
+ if (PyUnicode_Check(value))
+ {
+ python_wrapper_get_service()->file_set_string(self->file, key, PyUnicode_AsUTF8(value));
+ }
+ else if (PyLong_Check(value))
+ {
+ python_wrapper_get_service()->file_set_int(self->file, key, PyLong_AsLong(value));
+ }
+ else
+ {
+ g_printerr("%s: Not a string or int value\n", PyUnicode_AsUTF8(PyObject_Str(value)));
+ }
+ return Py_None;
+ }
+ else
+ {
+ g_printerr("file.set_setting(key, value): Error parsing arguments!\n");
+ PyErr_Print();
+ return NULL;
+ }
+}
+
+static PyObject* file_get_setting(PyRemminaFile* self, PyObject* args, PyObject* kwds)
+{
+ TRACE_CALL(__func__);
+
+ static gchar* keyword_list[] = { "key", "default" };
+ gchar* key;
+ PyObject* def;
+
+ if (PyArg_ParseTupleAndKeywords(args, kwds, "sO", keyword_list, &key, &def))
+ {
+ if (PyUnicode_Check(def))
+ {
+ return Py_BuildValue("s", python_wrapper_get_service()->file_get_string(self->file, key));
+ }
+ else if (PyBool_Check(def))
+ {
+ return python_wrapper_get_service()->file_get_int(self->file, key, (gint)PyLong_AsLong(def)) ? Py_True : Py_False;
+ }
+ else if (PyLong_Check(def))
+ {
+ return Py_BuildValue("i", python_wrapper_get_service()->file_get_int(self->file, key, (gint)PyLong_AsLong(def)));
+ }
+ else
+ {
+ g_printerr("%s: Not a string or int value\n", PyUnicode_AsUTF8(PyObject_Str(def)));
+ }
+ return def;
+ }
+ else
+ {
+ g_printerr("file.get_setting(key, default): Error parsing arguments!\n");
+ PyErr_Print();
+ return Py_None;
+ }
+}
+
+static PyObject* file_get_secret(PyRemminaFile* self, PyObject* key)
+{
+ TRACE_CALL(__func__);
+
+ if (key && PyUnicode_Check(key))
+ {
+ return Py_BuildValue("s", python_wrapper_get_service()->file_get_secret(self->file, PyUnicode_AsUTF8(key)));
+ }
+ else
+ {
+ g_printerr("file.get_secret(key): Error parsing arguments!\n");
+ PyErr_Print();
+ return NULL;
+ }
+}
+
+static PyObject* file_unsave_passwords(PyRemminaFile* self, PyObject* args)
+{
+ TRACE_CALL(__func__);
+
+ if (self)
+ {
+ python_wrapper_get_service()->file_unsave_passwords(self->file);
+ return Py_None;
+ }
+ else
+ {
+ g_printerr("unsave_passwords(): self is null!\n");
+ return NULL;
+ }
+}
diff --git a/plugins/python_wrapper/python_wrapper_remmina_file.h b/plugins/python_wrapper/python_wrapper_remmina_file.h
new file mode 100644
index 0000000..2899ae0
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_remmina_file.h
@@ -0,0 +1,63 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo
+ *
+ * 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.
+ */
+
+
+/**
+ * @file python_wrapper_protocol.h
+ *
+ * @brief Contains the implementation of the Python type remmina.RemminaFile.
+ */
+
+#pragma once
+
+#include "python_wrapper_common.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/**
+ * Wrapper for a Python object that contains a pointer to an instance of RemminaFile.
+ */
+typedef struct
+{
+ PyObject_HEAD
+ RemminaFile* file;
+} PyRemminaFile;
+
+void python_wrapper_remmina_init_types(void);
+
+/**
+ * Converts the instance of RemminaFile to a Python object that can be passed to the Python engine.
+ */
+PyRemminaFile* python_wrapper_remmina_file_to_python(RemminaFile* file);
diff --git a/plugins/python_wrapper/python_wrapper_secret.c b/plugins/python_wrapper/python_wrapper_secret.c
new file mode 100644
index 0000000..1f34176
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_secret.c
@@ -0,0 +1,138 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2014-2021 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 L U C E S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "python_wrapper_common.h"
+#include "python_wrapper_secret.h"
+#include "python_wrapper_remmina_file.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+void python_wrapper_secret_init(void)
+{
+ TRACE_CALL(__func__);
+}
+
+gboolean python_wrapper_secret_init_wrapper(RemminaSecretPlugin* instance)
+{
+ TRACE_CALL(__func__);
+
+ PyPlugin* plugin = python_wrapper_get_plugin(instance->name);
+ PyObject* result = CallPythonMethod(plugin->instance, "init", NULL);
+ return result == Py_None || result != Py_False;
+}
+
+gboolean python_wrapper_secret_is_service_available_wrapper(RemminaSecretPlugin* instance)
+{
+ TRACE_CALL(__func__);
+
+ PyPlugin* plugin = python_wrapper_get_plugin(instance->name);
+ PyObject* result = CallPythonMethod(plugin->instance, "is_service_available", NULL);
+ return result == Py_None || result != Py_False;
+}
+
+void
+python_wrapper_secret_store_password_wrapper(RemminaSecretPlugin* instance, RemminaFile* file, const gchar* key, const gchar* password)
+{
+ TRACE_CALL(__func__);
+
+ PyPlugin* plugin = python_wrapper_get_plugin(instance->name);
+ CallPythonMethod(plugin
+ ->instance, "store_password", "Oss", (PyObject*)python_wrapper_remmina_file_to_python(file), key, password);
+}
+
+gchar*
+python_wrapper_secret_get_password_wrapper(RemminaSecretPlugin* instance, RemminaFile* file, const gchar* key)
+{
+ TRACE_CALL(__func__);
+
+ PyPlugin* plugin = python_wrapper_get_plugin(instance->name);
+ PyObject* result = CallPythonMethod(plugin
+ ->instance, "get_password", "Os", (PyObject*)python_wrapper_remmina_file_to_python(file), key);
+ Py_ssize_t len = PyUnicode_GetLength(result);
+ if (len == 0)
+ {
+ return NULL;
+ }
+
+ return python_wrapper_copy_string_from_python(result, len);
+}
+
+void
+python_wrapper_secret_delete_password_wrapper(RemminaSecretPlugin* instance, RemminaFile* file, const gchar* key)
+{
+ TRACE_CALL(__func__);
+
+ PyPlugin* plugin = python_wrapper_get_plugin(instance->name);
+ CallPythonMethod(plugin
+ ->instance, "delete_password", "Os", (PyObject*)python_wrapper_remmina_file_to_python(file), key);
+}
+
+RemminaPlugin* python_wrapper_create_secret_plugin(PyPlugin* plugin)
+{
+ TRACE_CALL(__func__);
+
+ PyObject* instance = plugin->instance;
+
+ if (!python_wrapper_check_attribute(instance, ATTR_NAME))
+ {
+ return NULL;
+ }
+
+ RemminaSecretPlugin* remmina_plugin = (RemminaSecretPlugin*)python_wrapper_malloc(sizeof(RemminaSecretPlugin));
+
+ remmina_plugin->type = REMMINA_PLUGIN_TYPE_SECRET;
+ remmina_plugin->domain = GETTEXT_PACKAGE;
+ remmina_plugin->name = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_NAME));
+ remmina_plugin->version = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_VERSION));
+ remmina_plugin->description = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_DESCRIPTION));
+ remmina_plugin->init_order = PyLong_AsLong(PyObject_GetAttrString(instance, ATTR_INIT_ORDER));
+
+ remmina_plugin->init = python_wrapper_secret_init_wrapper;
+ remmina_plugin->is_service_available = python_wrapper_secret_is_service_available_wrapper;
+ remmina_plugin->store_password = python_wrapper_secret_store_password_wrapper;
+ remmina_plugin->get_password = python_wrapper_secret_get_password_wrapper;
+ remmina_plugin->delete_password = python_wrapper_secret_delete_password_wrapper;
+
+ plugin->secret_plugin = remmina_plugin;
+ plugin->generic_plugin = (RemminaPlugin*)remmina_plugin;
+
+ python_wrapper_add_plugin(plugin);
+
+ return (RemminaPlugin*)remmina_plugin;
+} \ No newline at end of file
diff --git a/plugins/python_wrapper/python_wrapper_secret.h b/plugins/python_wrapper/python_wrapper_secret.h
new file mode 100644
index 0000000..b40e058
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_secret.h
@@ -0,0 +1,68 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2014-2021 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.
+ */
+
+/**
+ * @file python_wrapper_entry.h
+ *
+ * @brief Contains the specialisation of RemminaPluginEntry plugins in Python.
+ */
+
+#pragma once
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// I N L U C E S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "python_wrapper_common.h"
+#include "remmina/plugin.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+G_BEGIN_DECLS
+
+/**
+ * Initializes the Python plugin specialisation for secret plugins.
+ */
+void python_wrapper_secret_init(void);
+
+/**
+ * @brief Creates a new instance of the RemminaPluginSecret, initializes its members and references the wrapper
+ * functions.
+ * @param instance The instance of the Python plugin.
+ * @return Returns a new instance of the RemminaPlugin (must be freed!).
+ */
+RemminaPlugin* python_wrapper_create_secret_plugin(PyPlugin* instance);
+
+G_END_DECLS
diff --git a/plugins/python_wrapper/python_wrapper_tool.c b/plugins/python_wrapper/python_wrapper_tool.c
new file mode 100644
index 0000000..db721b6
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_tool.c
@@ -0,0 +1,84 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2014-2021 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 L U C E S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "python_wrapper_common.h"
+#include "python_wrapper_tool.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+void python_wrapper_tool_init(void)
+{
+ TRACE_CALL(__func__);
+}
+
+void python_wrapper_tool_exec_func_wrapper(GtkMenuItem* self, RemminaToolPlugin* instance)
+{
+ TRACE_CALL(__func__);
+
+ PyPlugin* plugin = python_wrapper_get_plugin(instance->name);
+ CallPythonMethod(plugin->instance, "exec_func", NULL);
+}
+
+RemminaPlugin* python_wrapper_create_tool_plugin(PyPlugin* plugin)
+{
+ TRACE_CALL(__func__);
+
+ PyObject* instance = plugin->instance;
+
+ if (!python_wrapper_check_attribute(instance, ATTR_NAME))
+ {
+ return NULL;
+ }
+
+ RemminaToolPlugin* remmina_plugin = (RemminaToolPlugin*)python_wrapper_malloc(sizeof(RemminaToolPlugin));
+
+ remmina_plugin->type = REMMINA_PLUGIN_TYPE_TOOL;
+ remmina_plugin->domain = GETTEXT_PACKAGE;
+ remmina_plugin->name = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_NAME));
+ remmina_plugin->version = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_VERSION));
+ remmina_plugin->description = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_DESCRIPTION));
+ remmina_plugin->exec_func = python_wrapper_tool_exec_func_wrapper;
+
+ plugin->tool_plugin = remmina_plugin;
+ plugin->generic_plugin = (RemminaPlugin*)remmina_plugin;
+
+ python_wrapper_add_plugin(plugin);
+
+ return (RemminaPlugin*)remmina_plugin;
+}
diff --git a/plugins/python_wrapper/python_wrapper_tool.h b/plugins/python_wrapper/python_wrapper_tool.h
new file mode 100644
index 0000000..595e7b3
--- /dev/null
+++ b/plugins/python_wrapper/python_wrapper_tool.h
@@ -0,0 +1,69 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2014-2021 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.
+ */
+
+/**
+ * @file python_wrapper_entry.h
+ *
+ * @brief Contains the specialisation of RemminaPluginEntry plugins in Python.
+ */
+
+#pragma once
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// I N L U C E S
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#include "remmina/plugin.h"
+#include "python_wrapper_common.h"
+
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// A P I
+////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+G_BEGIN_DECLS
+
+/**
+ * Initializes the Python plugin specialisation for tool plugins.
+ */
+void python_wrapper_tool_init(void);
+
+/**
+ * @brief Creates a new instance of the RemminaPluginTool, initializes its members and references the wrapper
+ * functions.
+ * @param instance The instance of the Python plugin.
+ * @return Returns a new instance of the RemminaPlugin (must be freed!).
+ */
+RemminaPlugin* python_wrapper_create_tool_plugin(PyPlugin* instance);
+
+G_END_DECLS
+