diff options
Diffstat (limited to 'plugins/python_wrapper')
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 + |