diff options
author | Antenore Gatta (tmow) <antenore@simbiosi.org> | 2021-01-04 09:10:30 +0300 |
---|---|---|
committer | Antenore Gatta (tmow) <antenore@simbiosi.org> | 2021-01-04 09:10:30 +0300 |
commit | 57f7845e551fd47bcdb46304c64a8a11a94768db (patch) | |
tree | ebfa38fc47ca199497f9a021bbeaf0d6ef2c4db4 | |
parent | 8220012e15d58f5953ed642a45d20431f77f61b8 (diff) | |
parent | ef3cdec971b7f111b20d5ac5fa11a9c8b8d7459c (diff) |
Merge branch 'master' into 'master'
Add capability to load Python plugins (not finished).
See merge request Remmina/Remmina!2157
26 files changed, 3316 insertions, 30 deletions
diff --git a/plugins/pyvnc/pyvnc.py b/plugins/pyvnc/pyvnc.py new file mode 100644 index 000000000..362c605ee --- /dev/null +++ b/plugins/pyvnc/pyvnc.py @@ -0,0 +1,97 @@ +import sys +import remmina +import enum +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, GLib +import psutil + +class VncFeature: + PrefQuality = 1 + PrefViewonly = 2 + PrefDisableserverinput = 3 + ToolRefresh = 4 + ToolChat = 5 + ToolSendCtrlAltDel = 6 + Scale = 7 + Unfocus = 8 + + +class Plugin: + def __init__(self): + self.name = "PyVNC" + self.type = "protocol" + self.description = "VNC but in Python!" + self.version = "1.0" + self.icon_name = "remmina-vnc-symbolic" + self.icon_name_ssh = "remmina-vnc-ssh-symbolic" + self.ssh_setting = remmina.PROTOCOL_SSH_SETTING_TUNNEL + + self.features = [ + remmina.Feature( + type=remmina.PROTOCOL_FEATURE_TYPE_PREF, + id=VncFeature.PrefQuality, + opt1=remmina.PROTOCOL_FEATURE_PREF_RADIO, + opt2="quality", + opt3=None) + ,remmina.Feature(remmina.PROTOCOL_FEATURE_TYPE_PREF, VncFeature.PrefViewonly, remmina.PROTOCOL_FEATURE_PREF_CHECK, "viewonly", None) + ,remmina.Feature(remmina.PROTOCOL_FEATURE_TYPE_PREF, VncFeature.PrefDisableserverinput, remmina.PROTOCOL_SETTING_TYPE_CHECK, "disableserverinput", "Disable server input") + ,remmina.Feature(remmina.PROTOCOL_FEATURE_TYPE_TOOL, VncFeature.ToolRefresh, "Refresh", None, None) + ,remmina.Feature(remmina.PROTOCOL_FEATURE_TYPE_TOOL, VncFeature.ToolChat, "Open Chat…", "face-smile", None) + ,remmina.Feature(remmina.PROTOCOL_FEATURE_TYPE_TOOL, VncFeature.ToolSendCtrlAltDel, "Send Ctrl+Alt+Delete", None, None) + ,remmina.Feature(remmina.PROTOCOL_FEATURE_TYPE_SCALE, VncFeature.Scale, None, None, None) + ,remmina.Feature(remmina.PROTOCOL_FEATURE_TYPE_UNFOCUS, VncFeature.Unfocus, None, None, None) + ] + + colordepths = ("8", "256 colors (8 bpp)", "16", "High color (16 bpp)", "32", "True color (32 bpp)") + print(type(colordepths)) + qualities = ("0", "Poor (fastest)", "1","Medium", "2","Good", "9","Best (slowest)") + print(type(qualities)) + self.basic_settings = [ + remmina.Setting(type=remmina.PROTOCOL_SETTING_TYPE_SERVER, name="server", label="", compact=False, opt1="_rfb._tcp",opt2=None) + , remmina.Setting(type=remmina.PROTOCOL_SETTING_TYPE_TEXT, name="proxy", label="Repeater", compact=False, opt1=None, opt2=None) + , remmina.Setting(type=remmina.PROTOCOL_SETTING_TYPE_TEXT, name="username", label="Username", compact=False, opt1=None, opt2=None) + , remmina.Setting(type=remmina.PROTOCOL_SETTING_TYPE_PASSWORD,name="password", label="User password",compact=False, opt1=None, opt2=None) + , remmina.Setting(type=remmina.PROTOCOL_SETTING_TYPE_SELECT, name="colordepth",label="Color depth", compact=False, opt1=colordepths,opt2=None) + , remmina.Setting(type=remmina.PROTOCOL_SETTING_TYPE_SELECT, name="quality", label="Quality", compact=False, opt1=qualities,opt2=None) + , remmina.Setting(type=remmina.PROTOCOL_SETTING_TYPE_KEYMAP, name="keymap", label="", compact=False, opt1=None, opt2=None) + ] + self.advanced_settings = [ + remmina.Setting(remmina.PROTOCOL_SETTING_TYPE_CHECK, "showcursor", "Show remote cursor", True, None, None) + , remmina.Setting(remmina.PROTOCOL_SETTING_TYPE_CHECK, "viewonly", "View only", False, None, None) + , remmina.Setting(remmina.PROTOCOL_SETTING_TYPE_CHECK, "disableclipboard", "Disable clipboard sync", True, None, None) + , remmina.Setting(remmina.PROTOCOL_SETTING_TYPE_CHECK, "disableencryption", "Disable encryption", False, None, None) + , remmina.Setting(remmina.PROTOCOL_SETTING_TYPE_CHECK, "disableserverinput", "Disable server input", True, None, None) + , remmina.Setting(remmina.PROTOCOL_SETTING_TYPE_CHECK, "disablepasswordstoring", "Disable password storing", False, None, None) + ] + + def init(self, gp): + print("[PyVNC.init]: Called!") + pass + + def open_connection(self, gp): + print("[PyVNC.open_connection]: Called!") + pass + + def close_connection(self, gp): + print("[PyVNC.close_connection]: Called!") + pass + + def query_feature(self, gp): + print("[PyVNC.query_feature]: Called!") + pass + + def call_feature(self, gp): + print("[PyVNC.call_feature]: Called!") + pass + + def keystroke(self, gp): + print("[PyVNC.keystroke]: Called!") + pass + + def screenshot(self, gp): + print("[PyVNC.screenshot]: Called!") + pass + +myPlugin = Plugin() +remmina.register_plugin(myPlugin)
\ No newline at end of file diff --git a/plugins/tool_hello_world_python/toolsdevler.py b/plugins/tool_hello_world_python/toolsdevler.py new file mode 100644 index 000000000..5e4cab25b --- /dev/null +++ b/plugins/tool_hello_world_python/toolsdevler.py @@ -0,0 +1,72 @@ +import sys + +if not hasattr(sys, 'argv'): + sys.argv = [''] + +import remmina +import gi +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk, GLib +import psutil + +class HelloPlugin: + def __init__(self): + self.name = "Hello" + self.type = "protocol" + self.description = "Hello World!" + self.version = "1.0" + self.icon_name = "remmina-tool" + self.icon_name_ssh = "remmina-tool" + self.btn = Gtk.Button(label="Hello!") + self.btn.connect("clicked", self.callback_add, "hello") + pass + + def callback_add(self, widget, data): + print("Click :)") + + def name(self): + return "Hello" + + def init(self): + print("Init!") + return True + + + def open_connection(self, viewport): + print("open_connection!") + def foreach_child(child): + child.add(self.btn) + self.btn.show() + viewport.foreach(foreach_child) + print("Connected!") + + remmina.log_print("[%s]: Plugin open connection\n" % self.name) + return True + + def draw(self, widget, cr, color): + cr.rectangle(0, 0, 100, 100) + cr.set_source_rgb(color[0], color[1], color[2]) + cr.fill() + cr.queue_draw_area(0, 0, 100, 100) + + return True + + def close_connection(self, viewport): + print("close_connection!") + remmina.log_print("[%s]: Plugin close connection\n" % self.name) + return True + + def query_feature(self): + pass + + def call_feature(self): + pass + + def send_keystrokes(self): + pass + + def get_plugin_screenshot(self): + pass + +myPlugin = HelloPlugin() +remmina.register_plugin(myPlugin) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 297eeaa04..4236290c5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -77,6 +77,18 @@ list(APPEND REMMINA_SRCS "remmina_message_panel.h" "remmina_plugin_manager.c" "remmina_plugin_manager.h" + "remmina_plugin_native.c" + "remmina_plugin_native.h" + "remmina_plugin_python.c" + "remmina_plugin_python.h" + "remmina_plugin_python_module.c" + "remmina_plugin_python_module.h" + "remmina_plugin_python_remmina.c" + "remmina_plugin_python_remmina.h" + "remmina_plugin_python_protocol_widget.c" + "remmina_plugin_python_protocol_widget.h" + "remmina_plugin_python_remmina_file.c" + "remmina_plugin_python_remmina_file.h" "remmina_ext_exec.c" "remmina_ext_exec.h" "remmina_pref.c" @@ -131,6 +143,10 @@ add_executable(remmina ${REMMINA_SRCS}) include_directories(${GTK_INCLUDE_DIRS} ${gio_INCLUDE_DIRS} ${gio-unix_INCLUDE_DIRS}) target_link_libraries(remmina ${GTK_LIBRARIES}) +find_package(PythonLibs REQUIRED) +include_directories(${PYTHON_INCLUDE_DIRS}) +target_link_libraries(remmina ${PYTHON_LIBRARIES}) + if(WITH_MANPAGES) install(FILES remmina.1 DESTINATION ${CMAKE_INSTALL_FULL_MANDIR}/man1) endif() diff --git a/src/include/remmina/plugin.h b/src/include/remmina/plugin.h index 5ad75ce28..20e67bfd6 100644 --- a/src/include/remmina/plugin.h +++ b/src/include/remmina/plugin.h @@ -36,6 +36,7 @@ #pragma once +#include <stdarg.h> #include <remmina/types.h> #include "remmina/remmina_trace_calls.h" @@ -58,6 +59,7 @@ typedef struct _RemminaPlugin { const gchar * version; } RemminaPlugin; +typedef struct _RemminaProtocolPlugin _RemminaProtocolPlugin; typedef struct _RemminaProtocolPlugin { RemminaPluginType type; const gchar * name; diff --git a/src/include/remmina/types.h b/src/include/remmina/types.h index db5f8bfab..79145c7e9 100644 --- a/src/include/remmina/types.h +++ b/src/include/remmina/types.h @@ -36,6 +36,8 @@ #pragma once +#include <glib.h> + G_BEGIN_DECLS typedef struct _RemminaFile RemminaFile; @@ -97,8 +99,8 @@ typedef struct _RemminaProtocolSetting { const gchar * name; const gchar * label; gboolean compact; - const gpointer opt1; - const gpointer opt2; + gpointer opt1; + gpointer opt2; } RemminaProtocolSetting; typedef enum { diff --git a/src/pygobject.h b/src/pygobject.h new file mode 100644 index 000000000..026806be5 --- /dev/null +++ b/src/pygobject.h @@ -0,0 +1,635 @@ +/* -*- Mode: C; c-basic-offset: 4 -*- */ +#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_ */
\ No newline at end of file @@ -4178,6 +4178,9 @@ GtkWidget *rcw_open_from_file_full(RemminaFile *remminafile, GCallback disconnec return cnnobj->proto; } +GtkWindow* rcw_get_gtkwindow(RemminaConnectionObject *cnnobj) { return &cnnobj->cnnwin->window; } +GtkWidget* rcw_get_gtkviewport(RemminaConnectionObject *cnnobj) { return cnnobj->viewport; } + void rcw_set_delete_confirm_mode(RemminaConnectionWindow *cnnwin, RemminaConnectionWindowOnDeleteConfirmMode mode) { TRACE_CALL(__func__); @@ -77,11 +77,15 @@ void rcw_open_from_file(RemminaFile *remminafile); gboolean rcw_delete(RemminaConnectionWindow *cnnwin); void rcw_set_delete_confirm_mode(RemminaConnectionWindow *cnnwin, RemminaConnectionWindowOnDeleteConfirmMode mode); GtkWidget *rcw_open_from_file_full(RemminaFile *remminafile, GCallback disconnect_cb, gpointer data, guint *handler); +GtkWindow* rcw_get_gtkwindow(RemminaConnectionObject *cnnobj); +GtkWidget* rcw_get_gtkviewport(RemminaConnectionObject *cnnobj); void rco_destroy_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp); void rco_show_message_panel(RemminaConnectionObject *cnnobj, RemminaMessagePanel *mp); void rco_get_monitor_geometry(RemminaConnectionObject *cnnobj, GdkRectangle *sz); + + #define MESSAGE_PANEL_SPINNER 0x00000001 #define MESSAGE_PANEL_OKBUTTON 0x00000002 diff --git a/src/remmina.c b/src/remmina.c index 25563ee35..4cb3f5d56 100644 --- a/src/remmina.c +++ b/src/remmina.c @@ -49,6 +49,8 @@ #include "remmina_main.h" #include "remmina_masterthread_exec.h" #include "remmina_plugin_manager.h" +#include "remmina_plugin_native.h" +#include "remmina_plugin_python.h" #include "remmina_pref.h" #include "remmina_public.h" #include "remmina_sftp_plugin.h" @@ -354,7 +356,9 @@ int main(int argc, char *argv[]) /* Initialize some Remmina parts needed also on a local instance for correct handle-local-options */ remmina_pref_init(); remmina_file_manager_init(); + remmina_plugin_manager_init(); + app_id = g_application_id_is_valid(REMMINA_APP_ID) ? REMMINA_APP_ID : NULL; diff --git a/src/remmina_plugin_manager.c b/src/remmina_plugin_manager.c index 6bc3abade..54658aeb0 100644 --- a/src/remmina_plugin_manager.c +++ b/src/remmina_plugin_manager.c @@ -53,6 +53,8 @@ #include "remmina_widget_pool.h" #include "rcw.h" #include "remmina_plugin_manager.h" +#include "remmina_plugin_native.h" +#include "remmina_plugin_python.h" #include "remmina_public.h" #include "remmina_masterthread_exec.h" #include "remmina/remmina_trace_calls.h" @@ -166,8 +168,6 @@ gboolean remmina_gtksocket_available() return available; } - - RemminaPluginService remmina_plugin_manager_service = { remmina_plugin_manager_register_plugin, @@ -248,34 +248,26 @@ RemminaPluginService remmina_plugin_manager_service = remmina_gtksocket_available, remmina_protocol_widget_get_profile_remote_width, remmina_protocol_widget_get_profile_remote_height - }; +const char *get_filename_ext(const char *filename) { + const char* last = strrchr(filename, '/'); + const char *dot = strrchr(last, '.'); + if(!dot || dot == filename) return ""; + return dot + 1; +} + static void remmina_plugin_manager_load_plugin(const gchar *name) { - TRACE_CALL(__func__); - GModule *module; - RemminaPluginEntryFunc entry; - - module = g_module_open(name, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL); - - if (!module) { - g_print("Could not load plugin: %s.\n", name); - g_print("Error: %s\n", g_module_error()); - return; + const char* ext = get_filename_ext(name); + + if (g_str_equal(G_MODULE_SUFFIX, ext)) { + remmina_plugin_native_load(&remmina_plugin_manager_service, name); + } else if (g_str_equal("py", ext)) { + remmina_plugin_python_load(&remmina_plugin_manager_service, name); + } else { + g_print("%s: Skip unsupported file type '%s'\n", name, ext); } - - if (!g_module_symbol(module, "remmina_plugin_entry", (gpointer*)&entry)) { - g_print("Could not locate plugin entry: %s.\n", name); - return; - } - - if (!entry(&remmina_plugin_manager_service)) { - g_print("Plugin entry returned false: %s.\n", name); - return; - } - - /* We don’t close the module because we will need it throughout the process lifetime */ } static gint compare_secret_plugin_init_order(gconstpointer a, gconstpointer b) @@ -292,7 +284,7 @@ static gint compare_secret_plugin_init_order(gconstpointer a, gconstpointer b) return 0; } -void remmina_plugin_manager_init(void) +void remmina_plugin_manager_init() { TRACE_CALL(__func__); GDir *dir; @@ -305,20 +297,23 @@ void remmina_plugin_manager_init(void) GSList *sple; remmina_plugin_table = g_ptr_array_new(); - + remmina_plugin_python_init(); + if (!g_module_supported()) { g_print("Dynamic loading of plugins is not supported on this platform!\n"); return; } + g_print("Load modules from %s\n", REMMINA_RUNTIME_PLUGINDIR); dir = g_dir_open(REMMINA_RUNTIME_PLUGINDIR, 0, NULL); + if (dir == NULL) return; while ((name = g_dir_read_name(dir)) != NULL) { if ((ptr = strrchr(name, '.')) == NULL) continue; ptr++; - if (g_strcmp0(ptr, G_MODULE_SUFFIX) != 0) + if (!remmina_plugin_manager_loader_supported(ptr)) continue; fullpath = g_strdup_printf(REMMINA_RUNTIME_PLUGINDIR "/%s", name); remmina_plugin_manager_load_plugin(fullpath); @@ -356,6 +351,11 @@ void remmina_plugin_manager_init(void) g_slist_free(secret_plugins); } +gboolean remmina_plugin_manager_loader_supported(const char *filetype) { + TRACE_CALL(__func__); + return g_str_equal("py", filetype) || g_str_equal(G_MODULE_SUFFIX, filetype); +} + RemminaPlugin* remmina_plugin_manager_get_plugin(RemminaPluginType type, const gchar *name) { TRACE_CALL(__func__); diff --git a/src/remmina_plugin_manager.h b/src/remmina_plugin_manager.h index bb2b3e637..731f48e0c 100644 --- a/src/remmina_plugin_manager.h +++ b/src/remmina_plugin_manager.h @@ -58,4 +58,14 @@ gboolean remmina_gtksocket_available(); extern RemminaPluginService remmina_plugin_manager_service; +typedef gboolean (*RemminaPluginLoaderFunc)(RemminaPluginService*, const gchar* name); + +typedef struct { + char* filetype; + RemminaPluginLoaderFunc func; +} RemminaPluginLoader; + +gboolean remmina_plugin_manager_supported(const char *filetype); +void remmina_plugin_manager_add_loader(char *filetype, RemminaPluginLoaderFunc func); + G_END_DECLS diff --git a/src/remmina_plugin_native.c b/src/remmina_plugin_native.c new file mode 100644 index 000000000..da1145ac4 --- /dev/null +++ b/src/remmina_plugin_native.c @@ -0,0 +1,47 @@ +#include "config.h" + +#include <gtk/gtk.h> +#include <glib/gi18n.h> +#include <gio/gio.h> +#include <string.h> + +#include <gdk/gdkx.h> + +#include "remmina_public.h" +#include "remmina_file_manager.h" +#include "remmina_pref.h" +#include "remmina_protocol_widget.h" +#include "remmina_log.h" +#include "remmina_widget_pool.h" +#include "rcw.h" +#include "remmina_public.h" +#include "remmina_plugin_native.h" +#include "remmina_masterthread_exec.h" +#include "remmina/remmina_trace_calls.h" + +gboolean remmina_plugin_native_load(RemminaPluginService* service, const char* name) { + TRACE_CALL(__func__); + GModule *module; + RemminaPluginEntryFunc entry; + + module = g_module_open(name, G_MODULE_BIND_LAZY | G_MODULE_BIND_LOCAL); + + if (!module) { + g_print("Failed to load plugin: %s.\n", name); + g_print("Error: %s\n", g_module_error()); + return FALSE; + } + + if (!g_module_symbol(module, "remmina_plugin_entry", (gpointer*)&entry)) { + g_print("Failed to locate plugin entry: %s.\n", name); + return FALSE; + } + + if (!entry(service)) { + g_print("Plugin entry returned false: %s.\n", name); + return FALSE; + } + + return TRUE; + /* We don’t close the module because we will need it throughout the process lifetime */ +} diff --git a/src/remmina_plugin_native.h b/src/remmina_plugin_native.h new file mode 100644 index 000000000..cc234323a --- /dev/null +++ b/src/remmina_plugin_native.h @@ -0,0 +1,47 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010 Vic Lee + * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo + * Copyright (C) 2016-2020 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. + * + */ + +#pragma once + +#include "remmina_plugin_manager.h" + +G_BEGIN_DECLS + +typedef gboolean (*RemminaPluginMain)(gchar *name); + +gboolean remmina_plugin_native_load(RemminaPluginService* service, const char* name); + +G_END_DECLS diff --git a/src/remmina_plugin_python.c b/src/remmina_plugin_python.c new file mode 100644 index 000000000..f8e21dcda --- /dev/null +++ b/src/remmina_plugin_python.c @@ -0,0 +1,153 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2009-2011 Vic Lee + * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo + * Copyright (C) 2016-2020 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 remmina_plugin_python.c + * @brief Remmina Python plugin loader. + * @author Mathias Winterhalter + * @date 14.10.2020 + * + * When Remmina discovers Python scripts in the plugin root folder the plugin manager + * passes the path to the Python plugin loader. There it gets exexuted 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 remmina_plugin_python_module.c). + * + * @see http://www.remmina.org/wp for more information. + */ + +#include <gtk/gtk.h> +#define PY_SSIZE_T_CLEAN +#include <Python.h> +#include <structmember.h> + +#include "pygobject.h" + +#include "config.h" +#include "remmina/remmina_trace_calls.h" +#include "remmina_plugin_python.h" +#include "remmina_plugin_python_module.h" + +/** + * @brief Extracts the filename without extention from a path. + * + * @param in The string to extract the filename from + * @param out The resulting filename without extention (must point to allocated memory). + * + * @return The length of the filename extracted. + */ +static int basename_no_ext(const char* in, char** out); + +/** + * + */ +void remmina_plugin_python_init(void) { + TRACE_CALL(__FUNC__); + + remmina_plugin_python_module_init(); + + Py_Initialize(); + + // Tell the Python engine where to look for Python scripts. + PyRun_SimpleString("import sys"); + PyRun_SimpleString("sys.path.append('" REMMINA_RUNTIME_PLUGINDIR "')"); + + pygobject_init(-1, -1, -1); +} + +gboolean remmina_plugin_python_load(RemminaPluginService* service, const gchar* name) { + TRACE_CALL(__FUNC__); + + gchar* filename = NULL; + if (basename_no_ext(name, &filename) == 0) { + g_printerr("[%s:%s]: %s can not extract filename from name!\n", __FILE__, __LINE__, name); + return FALSE; + } + + PyObject *plugin_name = PyUnicode_DecodeFSDefault(filename); + free(filename); + + if (!plugin_name) { + g_printerr("[%s:%s]: Error converting plugin file name to PyUnicode!\n", __FILE__, __LINE__); + return FALSE; + } + + wchar_t* program_name = NULL; + Py_ssize_t len = PyUnicode_AsWideChar(plugin_name, program_name, 0); + program_name = malloc(sizeof(wchar_t)*len); + if (!program_name) { + g_printerr("[%s:%s]: Failed allocating %d bytes!\n", __FILE__, __LINE__, (sizeof(wchar_t)*len)); + return FALSE; + } + + PyUnicode_AsWideChar(plugin_name, program_name, len); + + // This line works around the issue that in Python the sys.argv array is empty. This causes several problems since + // many rely on the fact that the program name is set in sys.argv[0]. + PySys_SetArgv(1, &program_name); + + if (PyImport_Import(plugin_name)) { + return TRUE; + } + + g_print("Failed to load python plugin file: \"%s\"\n", name); + PyErr_Print(); + return FALSE; +} + + +static int basename_no_ext(const char* in, char** out) { + const char* base = strrchr(in, '/'); + if (base) { + base++; + } + + const char* base_end = strrchr(base, '.'); + if (!base_end) { + base_end = base + strlen(base); + } + + int length = base_end - base; + int memsize = sizeof(char*) * ((length) + 1); + *out = (char*)malloc(memsize); + memset(*out, 0, memsize); + strncpy(*out, base, length); + (*out)[length] = '\0'; + + return length; +} diff --git a/src/remmina_plugin_python.h b/src/remmina_plugin_python.h new file mode 100644 index 000000000..3254618b9 --- /dev/null +++ b/src/remmina_plugin_python.h @@ -0,0 +1,59 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010 Vic Lee + * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo + * Copyright (C) 2016-2020 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. + * + */ + +#pragma once + +#include "remmina/plugin.h" + +G_BEGIN_DECLS + +/** + * @brief Initializes the Python plugin loaders. + * @details This does not load any plugins but initializes globals and the Python engine itself. + */ +void remmina_plugin_python_init(void); + +/** + * @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 remmina_plugin_python_load(RemminaPluginService* service, const gchar* filename); + +G_END_DECLS diff --git a/src/remmina_plugin_python_module.c b/src/remmina_plugin_python_module.c new file mode 100644 index 000000000..100a6a42e --- /dev/null +++ b/src/remmina_plugin_python_module.c @@ -0,0 +1,284 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2009-2011 Vic Lee + * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo + * Copyright (C) 2016-2020 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 remmina_plugin_python_module.c + * @brief Implementation of the Python module 'remmina'. + * @author Mathias Winterhalter + * @date 14.10.2020 + * + * This file acts as a broker between Remmina and the Python plugins. It abstracts the communication flow + * over the RemminaPluginService and redirects calls to the correct Python plugin. The PyRemminaProtocolWidget + * takes care of providing the API inside the Python script. + * + * + * @see http://www.remmina.org/wp for more information. + */ + +#include <glib.h> +#include <gtk/gtk.h> +#define PY_SSIZE_T_CLEAN +#include <Python.h> +#include <structmember.h> + +#include "config.h" +#include "pygobject.h" +#include "remmina_plugin_manager.h" +#include "remmina_plugin_python_remmina.h" +#include "remmina/plugin.h" +#include "remmina_protocol_widget.h" + +/** + * @brief Handles the initialization of the Python plugin. + * @details This function prepares the plugin structure and calls the init method of the + * plugin Python class. + * + * @param gp The protocol widget used by the plugin. + */ +static void remmina_protocol_init_wrapper(RemminaProtocolWidget *gp) +{ + TRACE_CALL(__func__); + PyPlugin* py_plugin = remmina_plugin_python_module_get_plugin(gp); + py_plugin->gp->gp = gp; + PyObject_CallMethod(py_plugin, "init", "O", py_plugin->gp); +} + +/** + * @brief + * @details + * + * @param gp The protocol widget used by the plugin. + */ +static gboolean remmina_protocol_open_connection_wrapper(RemminaProtocolWidget *gp) +{ + TRACE_CALL(__func__); + remmina_plugin_manager_service.protocol_plugin_signal_connection_opened(gp); + PyPlugin* py_plugin = remmina_plugin_python_module_get_plugin(gp); + PyObject* result = PyObject_CallMethod(py_plugin, "open_connection", "O", py_plugin->gp); + return result == Py_True; +} + +/** + * @brief + * @details + * + * @param gp The protocol widget used by the plugin. + */ +static gboolean remmina_protocol_close_connection_wrapper(RemminaProtocolWidget *gp) +{ + TRACE_CALL(__func__); + PyPlugin* py_plugin = remmina_plugin_python_module_get_plugin(gp); + PyObject* result = PyObject_CallMethod(py_plugin, "close_connection", "O", py_plugin->gp); + return result == Py_True; +} + +/** + * @brief + * @details + * + * @param gp The protocol widget used by the plugin. + */ +static gboolean remmina_protocol_query_feature_wrapper(RemminaProtocolPlugin* plugin, RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature) +{ + TRACE_CALL(__func__); + PyPlugin* py_plugin = remmina_plugin_python_module_get_plugin(gp); + PyObject* result = PyObject_CallMethod(py_plugin, "query_feature", "O", py_plugin->gp); + return result == Py_True; +} + +/** + * @brief + * @details + * + * @param gp The protocol widget used by the plugin. + */ +static void remmina_protocol_call_feature_wrapper(RemminaProtocolPlugin* plugin, RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature) +{ + TRACE_CALL(__func__); + PyPlugin* py_plugin = remmina_plugin_python_module_get_plugin(gp); + PyObject* result = PyObject_CallMethod(py_plugin, "call_feature", "O", py_plugin->gp); +} + +/** + * @brief + * @details + * + * @param gp The protocol widget used by the plugin. + */ +static void remmina_protocol_send_keytrokes_wrapper(RemminaProtocolPlugin* plugin, RemminaProtocolWidget *gp, const guint keystrokes[], const gint keylen) +{ + TRACE_CALL(__func__); + PyPlugin* py_plugin = remmina_plugin_python_module_get_plugin(gp); + PyObject* result = PyObject_CallMethod(py_plugin, "send_keystrokes", "O", py_plugin->gp); +} + +/** + * @brief + * @details + * + * @param gp The protocol widget used by the plugin. + */ +static gboolean remmina_protocol_get_plugin_screenshot_wrapper(RemminaProtocolPlugin* plugin, RemminaProtocolWidget *gp, RemminaPluginScreenshotData *rpsd) +{ + TRACE_CALL(__func__); + PyPlugin* py_plugin = remmina_plugin_python_module_get_plugin(gp); + PyObject* result = PyObject_CallMethod(py_plugin, "get_plugin_screenshot", "O", py_plugin->gp); + return result == Py_True; +} + + +static long GetEnumOrDefault(PyObject* instance, gchar* constant_name, long def) { + PyObject* attr = PyObject_GetAttrString(instance, constant_name); + if (attr && PyLong_Check(attr)) { + return PyLong_AsLong(attr); + } else { + return def; + } +} + +RemminaPlugin* remmina_plugin_python_create_protocol_plugin(PyObject* pluginInstance) +{ + RemminaProtocolPlugin* remmina_plugin = (RemminaProtocolPlugin*)malloc(sizeof(RemminaProtocolPlugin)); + + if(!PyObject_HasAttrString(pluginInstance, "icon_name_ssh")) { + g_printerr("Error creating Remmina plugin. Python plugin instance is missing member: icon_name_ssh\n"); + return NULL; + } + else if(!PyObject_HasAttrString(pluginInstance, "icon_name")) { + g_printerr("Error creating Remmina plugin. Python plugin instance is missing member: icon_name\n"); + return NULL; + } + else if(!PyObject_HasAttrString(pluginInstance, "features")) { + g_printerr("Error creating Remmina plugin. Python plugin instance is missing member: features\n"); + return NULL; + } + else if(!PyObject_HasAttrString(pluginInstance, "basic_settings")) { + g_printerr("Error creating Remmina plugin. Python plugin instance is missing member: basic_settings\n"); + return NULL; + } + else if(!PyObject_HasAttrString(pluginInstance, "advanced_settings")) { + g_printerr("Error creating Remmina plugin. Python plugin instance is missing member: advanced_settings\n"); + return NULL; + } + else if(!PyObject_HasAttrString(pluginInstance, "ssh_setting")) { + g_printerr("Error creating Remmina plugin. Python plugin instance is missing member: ssh_setting\n"); + return NULL; + } + + remmina_plugin->type = REMMINA_PLUGIN_TYPE_PROTOCOL; + remmina_plugin->name = PyUnicode_AsUTF8(PyObject_GetAttrString(pluginInstance, "name")); // Name + remmina_plugin->description = PyUnicode_AsUTF8(PyObject_GetAttrString(pluginInstance, "description")); // Description + remmina_plugin->domain = GETTEXT_PACKAGE; // Translation domain + remmina_plugin->version = PyUnicode_AsUTF8(PyObject_GetAttrString(pluginInstance, "version")); // Version number + remmina_plugin->icon_name = PyUnicode_AsUTF8(PyObject_GetAttrString(pluginInstance, "icon_name")); // Icon for normal connection + remmina_plugin->icon_name_ssh = PyUnicode_AsUTF8(PyObject_GetAttrString(pluginInstance, "icon_name_ssh")); // Icon for SSH connection + + PyObject* basic_settings_list = PyObject_GetAttrString(pluginInstance, "basic_settings"); + Py_ssize_t len = PyList_Size(basic_settings_list); + if (len) { + remmina_plugin->basic_settings = (RemminaProtocolSetting*)malloc(sizeof(RemminaProtocolSetting) * len); + memset(&remmina_plugin->basic_settings[len], 0, sizeof(RemminaProtocolSetting)); + + for (Py_ssize_t i = 0; i < len; ++i) { + RemminaProtocolSetting* dest = remmina_plugin->basic_settings + i; + ToRemminaProtocolSetting(dest, PyList_GetItem(basic_settings_list, i)); + } + } else { + remmina_plugin->basic_settings = NULL; + } + + PyObject* advanced_settings_list = PyObject_GetAttrString(pluginInstance, "advanced_settings"); + len = PyList_Size(advanced_settings_list); + if (len) { + remmina_plugin->advanced_settings = (RemminaProtocolSetting*)malloc(sizeof(RemminaProtocolSetting) * (len+1)); + memset(&remmina_plugin->advanced_settings[len], 0, sizeof(RemminaProtocolSetting)); + + for (Py_ssize_t i = 0; i < len; ++i) { + RemminaProtocolSetting* dest = remmina_plugin->advanced_settings + i; + ToRemminaProtocolSetting(dest, PyList_GetItem(advanced_settings_list, i)); + } + } else { + remmina_plugin->advanced_settings = NULL; + } + + + PyObject* features_list = PyObject_GetAttrString(pluginInstance, "features"); + len = PyList_Size(features_list); + if (len) { + remmina_plugin->features = (RemminaProtocolFeature*)malloc(sizeof(RemminaProtocolFeature) * (len+1)); + memset(&remmina_plugin->features[len], 0, sizeof(RemminaProtocolFeature)); + + for (Py_ssize_t i = 0; i < len; ++i) { + RemminaProtocolFeature* dest = remmina_plugin->features + i; + ToRemminaProtocolFeature(dest, PyList_GetItem(features_list, i)); + } + } else { + remmina_plugin->features = NULL; + } + + remmina_plugin->ssh_setting = (RemminaProtocolSSHSetting)GetEnumOrDefault(pluginInstance, "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 + + return remmina_plugin; +} + +RemminaPlugin* remmina_plugin_python_create_entry_plugin(pluginInstance) +{ + +} +RemminaPlugin* remmina_plugin_python_create_file_plugin(pluginInstance) +{ + +} +RemminaPlugin* remmina_plugin_python_create_tool_plugin(pluginInstance) +{ + +} +RemminaPlugin* remmina_plugin_python_create_pref_plugin(pluginInstance) +{ + +} +RemminaPlugin* remmina_plugin_python_create_secret_plugin(pluginInstance) +{ + +} diff --git a/src/remmina_plugin_python_module.h b/src/remmina_plugin_python_module.h new file mode 100644 index 000000000..021938db8 --- /dev/null +++ b/src/remmina_plugin_python_module.h @@ -0,0 +1,44 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010 Vic Lee + * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo + * Copyright (C) 2016-2020 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. + * + */ + +#pragma once + +RemminaPlugin* remmina_plugin_python_create_protocol_plugin(PyObject* pluginInstance); +RemminaPlugin* remmina_plugin_python_create_entry_plugin(PyObject* pluginInstance); +RemminaPlugin* remmina_plugin_python_create_file_plugin(PyObject* pluginInstance); +RemminaPlugin* remmina_plugin_python_create_tool_plugin(PyObject* pluginInstance); +RemminaPlugin* remmina_plugin_python_create_pref_plugin(PyObject* pluginInstance); +RemminaPlugin* remmina_plugin_python_create_secret_plugin(PyObject* pluginInstance); diff --git a/src/remmina_plugin_python_protocol_widget.c b/src/remmina_plugin_python_protocol_widget.c new file mode 100644 index 000000000..043cc2670 --- /dev/null +++ b/src/remmina_plugin_python_protocol_widget.c @@ -0,0 +1,751 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2009-2011 Vic Lee + * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo + * Copyright (C) 2016-2020 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 remmina_plugin_python_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. + */ + +#include <glib.h> +#include <gtk/gtk.h> +#define PY_SSIZE_T_CLEAN +#include <Python.h> +#include <structmember.h> + +#include "config.h" +#include "pygobject.h" +#include "remmina_plugin_manager.h" +#include "remmina/plugin.h" +#include "remmina_protocol_widget.h" +#include "remmina_file.h" +#include "remmina_plugin_python_remmina.h" +#include "remmina_plugin_python_remmina_file.h" + +#include "remmina_plugin_python_protocol_widget.h" + +// -- Python Type -> RemminaWidget + +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* args); +static PyObject* protocol_widget_get_height(PyRemminaProtocolWidget* self, PyObject* args); +static PyObject* protocol_widget_set_height(PyRemminaProtocolWidget* self, PyObject* args); +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* args); +static PyObject* protocol_widget_has_error(PyRemminaProtocolWidget* self, PyObject* args); +static PyObject* protocol_widget_set_error(PyRemminaProtocolWidget* self, PyObject* args); +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* args); +static PyObject* protocol_widget_register_hostkey(PyRemminaProtocolWidget* self, PyObject* args); +static PyObject* protocol_widget_start_direct_tunnel(PyRemminaProtocolWidget* self, PyObject* args); +static PyObject* protocol_widget_start_reverse_tunnel(PyRemminaProtocolWidget* self, PyObject* args); +static PyObject* protocol_widget_start_xport_tunnel(PyRemminaProtocolWidget* self, PyObject* args); +static PyObject* protocol_widget_set_display(PyRemminaProtocolWidget* self, PyObject* args); +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_auth(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* args); +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 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_VARARGS, "" }, + {"emit_signal", (PyCFunction)protocol_widget_emit_signal, METH_VARARGS, "" }, + {"register_hostkey", (PyCFunction)protocol_widget_register_hostkey, METH_VARARGS, "" }, + {"start_direct_tunnel", (PyCFunctionWithKeywords)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_auth", (PyCFunction)protocol_widget_panel_auth, METH_VARARGS | METH_KEYWORDS, "" }, + {"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, "" } +}; + +static PyTypeObject python_protocol_widget_type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "remmina.RemminaWidget", + .tp_doc = "Remmina protocol widget", + .tp_basicsize = sizeof(PyRemminaProtocolWidget), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = PyType_GenericNew, + .tp_methods = python_protocol_widget_type_methods +}; + +typedef struct { + PyObject_HEAD + PyDictObject* settings; + PyDictObject* spsettings; +} PyRemminaFile; + +#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; \ + } + +static PyObject* protocol_widget_get_viewport(PyRemminaProtocolWidget* self, PyObject* args) +{ + TRACE_CALL(__func__); + return pygobject_new(G_OBJECT(remmina_protocol_widget_gtkviewport(self->gp))); +} + +static PyObject* protocol_widget_get_width(PyRemminaProtocolWidget* self, PyObject* args) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + return Py_BuildValue("i", remmina_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 && PyLong_Check(var_width)) { + gint width = (gint)PyLong_AsLong(var_width); + remmina_protocol_widget_set_height(self->gp, width); + } else { + g_printerr("set_width(val): Error parsing arguments!\n"); + PyErr_Print(); + } + + return Py_None; +} + +static PyObject* protocol_widget_get_height(PyRemminaProtocolWidget* self, PyObject* args) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + return Py_BuildValue("i", remmina_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 && PyLong_Check(var_height)) { + gint height = (gint)PyLong_AsLong(var_height); + remmina_protocol_widget_set_height(self->gp, height); + } else { + g_printerr("set_height(val): Error parsing arguments!\n"); + PyErr_Print(); + } + + return Py_None; +} + +static PyObject* protocol_widget_get_current_scale_mode(PyRemminaProtocolWidget* self, PyObject* args) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + return Py_BuildValue("i", remmina_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", remmina_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 && PyBool_Check(var_expand)) { + remmina_protocol_widget_set_expand(self->gp, PyObject_IsTrue(var_expand)); + } else { + g_printerr("set_expand(val): Error parsing arguments!\n"); + PyErr_Print(); + } + return Py_None; +} + +static PyObject* protocol_widget_has_error(PyRemminaProtocolWidget* self, PyObject* args) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + return Py_BuildValue("p", remmina_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 && PyUnicode_Check(var_msg)) { + gchar* msg = PyUnicode_AsUTF8(var_msg); + remmina_protocol_widget_set_error(self->gp, msg); + } else { + g_printerr("set_error(msg): Error parsing arguments!\n"); + PyErr_Print(); + } + return Py_None; +} + +static PyObject* protocol_widget_is_closed(PyRemminaProtocolWidget* self, PyObject* args) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + return Py_BuildValue("p", remmina_protocol_widget_is_closed(self->gp)); +} + +static PyObject* protocol_widget_get_file(PyRemminaProtocolWidget* self, PyObject* args) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + RemminaFile* file = remmina_protocol_widget_get_file(self->gp); + return remmina_plugin_python_remmina_file_to_python(file); +} + +static PyObject* protocol_widget_emit_signal(PyRemminaProtocolWidget* self, PyObject* var_signal) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + if (var_signal && PyUnicode_Check(var_signal)) { + remmina_protocol_widget_set_error(self->gp, PyUnicode_AsUTF8(var_signal)); + } else { + g_printerr("emit_signal(signal): Error parsing arguments!\n"); + PyErr_Print(); + } + return Py_None; +} + +static PyObject* protocol_widget_register_hostkey(PyRemminaProtocolWidget* self, PyObject* var_widget) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + if (var_widget) { + remmina_protocol_widget_register_hostkey(self->gp, pygobject_get(var_widget)); + } else { + g_printerr("register_hostkey(widget): Error parsing arguments!\n"); + PyErr_Print(); + } + 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 && PyArg_ParseTuple(args, "ii", &default_port, &port_plus)) { + return Py_BuildValue("s", remmina_protocol_widget_start_direct_tunnel(self->gp, default_port, port_plus)); + } else { + g_printerr("start_direct_tunnel(default_port, port_plus): Error parsing arguments!\n"); + PyErr_Print(); + } + return Py_None; +} + +static PyObject* protocol_widget_start_reverse_tunnel(PyRemminaProtocolWidget* self, PyObject* var_local_port) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + if (var_local_port && PyLong_Check(var_local_port)) { + return Py_BuildValue("p", remmina_protocol_widget_start_reverse_tunnel(self->gp, (gint)PyLong_AsLong(var_local_port))); + } else { + g_printerr("start_direct_tunnel(local_port): Error parsing arguments!\n"); + PyErr_Print(); + } + return Py_None; +} + +static gboolean xport_tunnel_init(RemminaProtocolWidget *gp, gint remotedisplay, const gchar *server, gint port) +{ + PyPlugin* plugin = remmina_plugin_python_module_get_plugin(gp); + PyObject* result = PyObject_CallMethod(plugin, "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", remmina_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 && PyLong_Check(var_display)) { + remmina_protocol_widget_set_display(self->gp, (gint)PyLong_AsLong(var_display)); + } else { + g_printerr("set_display(display): Error parsing arguments!\n"); + PyErr_Print(); + } + return Py_None; +} + +static PyObject* protocol_widget_signal_connection_closed(PyRemminaProtocolWidget* self, PyObject* args) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + remmina_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(); + + remmina_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(); + + remmina_protocol_widget_update_align(self->gp); + return Py_None; +} + +static PyObject* protocol_widget_unlock_dynres(PyRemminaProtocolWidget* self, PyObject* args) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + remmina_protocol_widget_unlock_dynres(self->gp); + return Py_None; +} + +static PyObject* protocol_widget_desktop_resize(PyRemminaProtocolWidget* self, PyObject* args) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + remmina_protocol_widget_desktop_resize(self->gp); + return Py_None; +} + +static PyObject* protocol_widget_panel_auth(PyRemminaProtocolWidget* self, PyObject* args) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + gint pflags = 0; + gchar* title, default_username, default_password, default_domain, password_prompt; + + if (PyArg_ParseTuple(args, "isssss", &pflags, &title, &default_username, &default_password, &default_domain, &password_prompt)) { + if (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 { + remmina_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* 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)) { + remmina_protocol_widget_panel_new_certificate(self->gp, subject, issuer, fingerprint); + } else { + g_printerr("panel_new_certificate(subject, issuer, fingerprint): Error parsing arguments!\n"); + PyErr_Print(); + } + 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)) { + remmina_protocol_widget_panel_changed_certificate(self->gp, subject, issuer, new_fingerprint, old_fingerprint); + } else { + g_printerr("panel_changed_certificate(subject, issuer, new_fingerprint, old_fingerprint): Error parsing arguments!\n"); + PyErr_Print(); + } + return Py_None; +} + +static PyObject* protocol_widget_get_username(PyRemminaProtocolWidget* self, PyObject* args) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + return Py_BuildValue("s", remmina_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", remmina_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", remmina_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", remmina_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", remmina_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", remmina_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", remmina_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", remmina_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", remmina_protocol_widget_get_clientkey(self->gp)); +} + +static PyObject* protocol_widget_save_cred(PyRemminaProtocolWidget* self, PyObject* args) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + remmina_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)) { + remmina_protocol_widget_panel_show_listen(self->gp, port); + } else { + g_printerr("panel_show_listen(port): Error parsing arguments!\n"); + PyErr_Print(); + } + return Py_None; +} + +static PyObject* protocol_widget_panel_show_retry(PyRemminaProtocolWidget* self, PyObject* args) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + remmina_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(); + + remmina_protocol_widget_panel_show(self->gp); + return Py_None; +} + +static PyObject* protocol_widget_panel_hide(PyRemminaProtocolWidget* self, PyObject* args) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + remmina_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)) { + remmina_protocol_widget_ssh_exec(self->gp, wait, cmd); + } else { + g_printerr("ssh_exec(wait, cmd): Error parsing arguments!\n"); + PyErr_Print(); + } + return Py_None; +} + +static gboolean _on_send_callback_wrapper(RemminaProtocolWidget *gp, const gchar *text) +{ + PyPlugin* plugin = remmina_plugin_python_module_get_plugin(gp); + PyObject* result = PyObject_CallMethod(plugin, "on_send", "Os", gp, text); + return PyObject_IsTrue(result); +} + +static gboolean _on_destroy_callback_wrapper(RemminaProtocolWidget *gp) +{ + PyPlugin* plugin = remmina_plugin_python_module_get_plugin(gp); + PyObject* result = PyObject_CallMethod(plugin, "on_destroy", "O", gp); + return PyObject_IsTrue(result); +} + +static PyObject* protocol_widget_chat_open(PyRemminaProtocolWidget* self, PyObject* var_name) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + if (var_name, PyUnicode_Check(var_name)) { + remmina_protocol_widget_chat_open(self->gp, PyUnicode_AsUTF8(var_name), _on_send_callback_wrapper, _on_destroy_callback_wrapper); + } else { + g_printerr("chat_open(name): Error parsing arguments!\n"); + PyErr_Print(); + } + + return Py_None; +} + +static PyObject* protocol_widget_chat_close(PyRemminaProtocolWidget* self, PyObject* args) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + + remmina_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)) { + remmina_protocol_widget_chat_receive(self->gp, text); + } else { + g_printerr("chat_receive(text): Error parsing arguments!\n"); + PyErr_Print(); + } + + return Py_None; +} + +static PyObject* protocol_widget_send_keys_signals(PyRemminaProtocolWidget* self, PyObject* args) +{ + TRACE_CALL(__func__); + SELF_CHECK(); + gchar* keyvals; + int length; + GdkEventType event_type; + + if (PyArg_ParseTuple(args, "sii", &keyvals, &length, &event_type)) { + if (event_type < GDK_NOTHING || event_type >= GDK_EVENT_LAST) { + g_printerr("send_keys_signals(keyvals, length, event_type): " + "%d is not a known value for GdkEventType!\n", event_type); + } else { + remmina_protocol_widget_send_keys_signals(self->gp, keyvals, length, event_type); + } + } else { + g_printerr("send_keys_signals(keyvals): Error parsing arguments!\n"); + PyErr_Print(); + } + + return Py_None; +} + diff --git a/src/remmina_plugin_python_protocol_widget.h b/src/remmina_plugin_python_protocol_widget.h new file mode 100644 index 000000000..f56c2cc35 --- /dev/null +++ b/src/remmina_plugin_python_protocol_widget.h @@ -0,0 +1,37 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010 Vic Lee + * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo + * Copyright (C) 2016-2020 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. + * + */ + +#pragma once diff --git a/src/remmina_plugin_python_remmina.c b/src/remmina_plugin_python_remmina.c new file mode 100644 index 000000000..1c85fd320 --- /dev/null +++ b/src/remmina_plugin_python_remmina.c @@ -0,0 +1,788 @@ +#include <glib.h> +#include <gtk/gtk.h> +#define PY_SSIZE_T_CLEAN +#include <Python.h> +#include <structmember.h> + +#include "config.h" +#include "pygobject.h" +#include "remmina_plugin_manager.h" +#include "remmina/plugin.h" +#include "remmina_protocol_widget.h" + +#include "remmina_plugin_python_remmina.h" + +/** + * @brief Holds pairs of Python and Remmina plugin instances (PyPlugin). + */ +GPtrArray *remmina_plugin_registry = NULL; + +/** + * + */ +gboolean remmina_plugin_python_check_mandatory_member(PyObject* instance, const gchar* member); + +/** + * @brief Wraps the log_printf function of RemminaPluginService. + * + * @details This function is only called by the Python engine! There is only one argument + * since Python offers an inline string formatting which renders any formatting capabilities + * redundant. + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param msg The message to print. + */ +static PyObject* remmina_plugin_python_log_printf_wrapper(PyObject* self, PyObject* msg); + +/** + * @brief Accepts an instance of a Python class representing a Remmina plugin. + * + * @details This function is only called by the Python engine! + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin An instance of a Python plugin class. + */ +static PyObject* remmina_register_plugin_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief Retrieves the viewport of the current connection window if available. + * + * @details This function is only called by the Python engine! + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_plugin_python_get_viewport(PyObject* self, PyObject* handle); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_file_get_datadir_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_file_new_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_pref_set_value_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_pref_get_value_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_pref_get_scale_quality_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_pref_get_sshtunnel_port_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_pref_get_ssh_loglevel_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_pref_get_ssh_parseconfig_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_pref_keymap_get_keyval_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_log_print_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_log_printf_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_widget_pool_register_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* rcw_open_from_file_full_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_public_get_server_port_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_masterthread_exec_is_main_thread_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_gtksocket_available_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_protocol_widget_get_profile_remote_heigh_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief + * + * @details + * + * @param self Is always NULL since it is a static function of the 'remmina' module + * @param plugin The handle to retrieve the RemminaConnectionWidget + */ +static PyObject* remmina_protocol_widget_get_profile_remote_width_wrapper(PyObject* self, PyObject* plugin); + +/** + * @brief The functions of the remmina module. + * + * @details If any function has to be added this is the place to do it. This list is referenced + * by the PyModuleDef (describing the module) instance below. + */ +static PyMethodDef remmina_python_module_type_methods[] = { + {"register_plugin", (PyCFunction)remmina_register_plugin_wrapper, METH_O, NULL }, + {"log_print", (PyCFunction)remmina_plugin_python_log_printf_wrapper, METH_VARARGS, NULL }, + {"get_datadir", (PyCFunction)remmina_file_get_datadir_wrapper}, + {"file_new", (PyCFunction)remmina_file_new_wrapper}, + + {"pref_set_value", (PyCFunction)remmina_pref_set_value_wrapper}, + {"pref_get_value", (PyCFunction)remmina_pref_get_value_wrapper}, + {"pref_get_scale_quality", (PyCFunction)remmina_pref_get_scale_quality_wrapper}, + {"pref_get_sshtunnel_port", (PyCFunction)remmina_pref_get_sshtunnel_port_wrapper}, + {"pref_get_ssh_loglevel", (PyCFunction)remmina_pref_get_ssh_loglevel_wrapper}, + {"pref_get_ssh_parseconfig", (PyCFunction)remmina_pref_get_ssh_parseconfig_wrapper}, + {"pref_keymap_get_keyval", (PyCFunction)remmina_pref_keymap_get_keyval_wrapper}, + + {"log_print", (PyCFunction)remmina_log_print_wrapper}, + {"log_printf", (PyCFunction)remmina_log_printf_wrapper}, + + {"widget_pool_register", (PyCFunction)remmina_widget_pool_register_wrapper}, + + {"rcw_open_from_file_full", (PyCFunction)rcw_open_from_file_full_wrapper }, + {"public_get_server_port", (PyCFunction)remmina_public_get_server_port_wrapper}, + {"masterthread_exec_is_main_thread", (PyCFunction)remmina_masterthread_exec_is_main_thread_wrapper}, + {"gtksocket_available", (PyCFunction)remmina_gtksocket_available_wrapper}, + {"protocol_widget_get_profile_remote_width", (PyCFunction)remmina_protocol_widget_get_profile_remote_width_wrapper}, + {"protocol_widget_get_profile_remote_heigh", (PyCFunction)remmina_protocol_widget_get_profile_remote_heigh_wrapper}, + {NULL} /* Sentinel */ +}; + +typedef struct { + PyObject_HEAD + RemminaProtocolSettingType settingType; + gchar* name; + gchar* label; + gboolean compact; + PyObject* opt1; + PyObject* opt2; +} PyRemminaProtocolSetting; + + +/** + * @brief 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 + +static PyObject* python_protocol_setting_new(PyTypeObject * type, PyObject* args, PyObject* kwds) +{ + PyRemminaProtocolSetting *self; + 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 self; +} + +static int python_protocol_setting_init(PyRemminaProtocolSetting *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = { "type", "name", "label", "compact", "opt1", "opt2", NULL }; + PyObject* name, *label; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|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 = g_malloc(sizeof(char) * (len + 1)); + self->label[len] = 0; + memcpy(self->label, PyUnicode_AsUTF8(label), len); + } + + len = PyUnicode_GetLength(name); + if (len == 0) { + self->name = ""; + } else { + self->name = g_malloc(sizeof(char) * (len + 1)); + self->name[len] = 0; + memcpy(self->name, PyUnicode_AsUTF8(name), len); + } + + 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 = python_protocol_setting_init, + .tp_members = python_protocol_setting_type_members +}; + +// -- Python Type -> Feature + +typedef struct { + PyObject_HEAD + RemminaProtocolFeatureType* type; + gint id; + PyObject* opt1; + PyObject* opt2; + PyObject* opt3; +} PyRemminaProtocolFeature; + +static PyMemberDef python_protocol_feature_members[] = { + { "type", offsetof(PyRemminaProtocolFeature, type), T_INT, 0, NULL }, + { "id", offsetof(PyRemminaProtocolFeature, id), T_STRING, 0, NULL }, + { "opt1", offsetof(PyRemminaProtocolFeature, opt1), T_OBJECT, 0, NULL }, + { "opt2", offsetof(PyRemminaProtocolFeature, opt2), T_OBJECT, 0, NULL }, + { "opt3", offsetof(PyRemminaProtocolFeature, opt3), T_OBJECT, 0, NULL }, + {NULL} +}; + +static PyObject* python_protocol_feature_new(PyTypeObject * type, PyObject* kws, PyObject* args) +{ + PyRemminaProtocolFeature *self; + self = (PyRemminaProtocolFeature*)type->tp_alloc(type, 0); + if (!self) + return NULL; + + self->id = 0; + self->type = 0; + self->opt1 = NULL; + self->opt2 = NULL; + self->opt3 = NULL; + + return (PyObject*)self; +} + +static int python_protocol_feature_init(PyRemminaProtocolFeature *self, PyObject *args, PyObject *kwds) +{ + static char *kwlist[] = { "type", "id", "opt1", "opt2", "opt3", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|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.Feature", + .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 = python_protocol_feature_init, + .tp_members = python_protocol_feature_members +}; + +/** + * @brief Is called from the Python engine when it initializes the 'remmina' module. + * @details This function is only called by the Python engine! + */ +static PyMODINIT_FUNC remmina_plugin_python_module_initialize(void) +{ + TRACE_CALL(__func__); + if (PyType_Ready(&python_protocol_setting_type) < 0) { + g_printerr("Error initializing remmina.Setting!\n"); + PyErr_Print(); + return NULL; + } + if (PyType_Ready(&python_protocol_feature_type) < 0) { + g_printerr("Error initializing remmina.Feature!\n"); + PyErr_Print(); + return NULL; + } + + PyObject* module = PyModule_Create(&remmina_python_module_type); + if (!module) { + g_printerr("Error creating module 'remmina'!\n"); + PyErr_Print(); + return NULL; + } + + 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_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_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); + + 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; +} + +/** + * @brief Initializes all globals and registers the 'remmina' module in the Python engine. + * @details This + */ +void remmina_plugin_python_module_init(void) +{ + TRACE_CALL(__func__); + remmina_plugin_registry = g_ptr_array_new(); + if (PyImport_AppendInittab("remmina", remmina_plugin_python_module_initialize)) { + g_print("Error initializing remmina module for python!\n"); + PyErr_Print(); + } +} + +gboolean remmina_plugin_python_check_mandatory_member(PyObject* instance, const gchar* member) +{ + if (PyObject_HasAttrString(instance, member)) + return TRUE; + + g_printerr("Missing mandatory member in Python plugin instance: %s\n", member); + return FALSE; +} + +static PyObject* remmina_plugin_python_log_printf_wrapper(PyObject* self, PyObject* msg) +{ + TRACE_CALL(__func__); + char* fmt = NULL; + + if (PyArg_ParseTuple(msg, "s", &fmt)) { + remmina_log_printf(fmt); + } else { + g_print("Failed to load.\n"); + PyErr_Print(); + return Py_None; + } + + return Py_None; +} + +static PyObject* remmina_register_plugin_wrapper(PyObject* self, PyObject* plugin_instance) +{ + TRACE_CALL(__func__); + + if (plugin_instance) { + if (!remmina_plugin_python_check_mandatory_member(plugin_instance, "name") + || !remmina_plugin_python_check_mandatory_member(plugin_instance, "version")) { + return NULL; + } + + /* Protocol plugin definition and features */ + RemminaPlugin* remmina_plugin = NULL; + gboolean is_protocol_plugin = FALSE; + + const gchar* pluginType = PyUnicode_AsUTF8(PyObject_GetAttrString(plugin_instance, "type")); + + if (g_str_equal(pluginType, "protocol")) { + remmina_plugin = remmina_plugin_python_create_protocol_plugin(plugin_instance); + is_protocol_plugin = TRUE; + } else if (g_str_equal(pluginType, "entry")) { + remmina_plugin = remmina_plugin_python_create_entry_plugin(plugin_instance); + } else if (g_str_equal(pluginType, "file")) { + remmina_plugin = remmina_plugin_python_create_file_plugin(plugin_instance); + } else if (g_str_equal(pluginType, "tool")) { + remmina_plugin = remmina_plugin_python_create_tool_plugin(plugin_instance); + } else if (g_str_equal(pluginType, "pref")) { + remmina_plugin = remmina_plugin_python_create_pref_plugin(plugin_instance); + } else if (g_str_equal(pluginType, "secret")) { + remmina_plugin = remmina_plugin_python_create_secret_plugin(plugin_instance); + } else { + g_printerr("Unknown plugin type: %s\n", pluginType); + } + + if (remmina_plugin) { + g_ptr_array_add(remmina_plugin_registry, plugin_instance); + remmina_plugin_manager_service.register_plugin((RemminaPlugin *)remmina_plugin); + + PyPlugin* plugin = (PyPlugin*)malloc(sizeof(PyPlugin)); + Py_INCREF(plugin); + plugin->protocol_plugin = remmina_plugin; + plugin->generic_plugin = remmina_plugin; + plugin->entry_plugin = NULL; + plugin->file_plugin = NULL; + plugin->pref_plugin = NULL; + plugin->secret_plugin = NULL; + plugin->tool_plugin = NULL; + plugin->pythonInstance = plugin_instance; + if (is_protocol_plugin) { + plugin->gp = malloc(sizeof(PyRemminaProtocolWidget)); + plugin->gp->gp = NULL; + } + g_ptr_array_add(remmina_plugin_registry, plugin); + } + } + + return Py_None; +} + +static PyObject* remmina_file_get_datadir_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + +static PyObject* remmina_file_new_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + +static PyObject* remmina_pref_set_value_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + +static PyObject* remmina_pref_get_value_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + + +static PyObject* remmina_pref_get_scale_quality_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + + +static PyObject* remmina_pref_get_sshtunnel_port_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + + +static PyObject* remmina_pref_get_ssh_loglevel_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + + +static PyObject* remmina_pref_get_ssh_parseconfig_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + + +static PyObject* remmina_pref_keymap_get_keyval_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + + +static PyObject* remmina_log_print_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + + +static PyObject* remmina_log_printf_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + + +static PyObject* remmina_widget_pool_register_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + + +static PyObject* rcw_open_from_file_full_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + + +static PyObject* remmina_public_get_server_port_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + + +static PyObject* remmina_masterthread_exec_is_main_thread_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + + +static PyObject* remmina_gtksocket_available_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + + +static PyObject* remmina_protocol_widget_get_profile_remote_heigh_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + + +static PyObject* remmina_protocol_widget_get_profile_remote_width_wrapper(PyObject* self, PyObject* plugin) +{ + return Py_None; +} + +static gboolean remmina_plugin_equal(gconstpointer lhs, gconstpointer rhs) +{ + if (lhs && ((PyPlugin*)lhs)->generic_plugin && rhs) + return g_str_equal(((PyPlugin*)lhs)->generic_plugin->name, ((gchar*)rhs)); + else + return lhs == rhs; +} + +PyPlugin* remmina_plugin_python_module_get_plugin(RemminaProtocolWidget* gp) +{ + static PyPlugin* cached_plugin = NULL; + static RemminaProtocolWidget* cached_widget = NULL; + if (cached_widget && gp == cached_widget) { + return cached_plugin; + } + guint index = 0; + for (int i = 0; i < remmina_plugin_registry->len; ++i) { + PyPlugin* plugin = (PyPlugin*)g_ptr_array_index(remmina_plugin_registry, i); + if (g_str_equal(gp->plugin->name, plugin->generic_plugin->name)) + return cached_plugin = plugin; + } + g_printerr("[%s:%s]: No plugin named %s!\n", __FILE__, __LINE__, gp->plugin->name); + return NULL; +} + +static void GetGeneric(PyObject* field, gpointer* target) +{ + if (!field || field == Py_None) { + *target = NULL; + return; + } + Py_INCREF(field); + if (PyUnicode_Check(field)) { + Py_ssize_t len = PyUnicode_GetLength(field); + + if (len == 0) { + *target = ""; + } else { + gchar* tmp = g_malloc(sizeof(char) * (len + 1)); + *(tmp + len) = 0; + memcpy(tmp, PyUnicode_AsUTF8(field), len); + *target = tmp; + } + + } else if (PyLong_Check(field)) { + *target = malloc(sizeof(long)); + *target = PyLong_AsLong(field); + } else if (PyTuple_Check(field)) { + Py_ssize_t len = PyTuple_Size(field); + if (len) { + gpointer* dest = (gpointer*)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); + GetGeneric(item, dest + i); + } + + *target = dest; + } + } + Py_DECREF(field); +} + +void ToRemminaProtocolSetting(RemminaProtocolSetting* dest, PyObject* setting) +{ + PyRemminaProtocolSetting* src = (PyRemminaProtocolSetting*)setting; + Py_INCREF(setting); + dest->name = src->name; + dest->label = src->label; + dest->compact = src->compact; + dest->type = src->settingType; + GetGeneric(src->opt1, &dest->opt1); + GetGeneric(src->opt2, &dest->opt2); +} + +void ToRemminaProtocolFeature(RemminaProtocolFeature* dest, PyObject* feature) { + PyRemminaProtocolFeature* src = (PyRemminaProtocolFeature*)feature; + Py_INCREF(feature); + dest->id = src->id; + dest->type = src->type; + GetGeneric(src->opt1, &dest->opt1); + GetGeneric(src->opt2, &dest->opt2); + GetGeneric(src->opt3, &dest->opt3); +} diff --git a/src/remmina_plugin_python_remmina.h b/src/remmina_plugin_python_remmina.h new file mode 100644 index 000000000..d10804c9b --- /dev/null +++ b/src/remmina_plugin_python_remmina.h @@ -0,0 +1,85 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2010 Vic Lee + * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo + * Copyright (C) 2016-2020 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. + * + */ + +#pragma once + +/** + * @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 { + PyObject *pythonInstance; + + RemminaProtocolPlugin *protocol_plugin; + RemminaFilePlugin* file_plugin; + RemminaSecretPlugin* secret_plugin; + RemminaToolPlugin* tool_plugin; + RemminaEntryPlugin* entry_plugin; + RemminaPrefPlugin* pref_plugin; + RemminaPlugin* generic_plugin; + + PyRemminaProtocolWidget *gp; +} PyPlugin; + + + +/** + * @brief Initializes the 'remmina' module in the Python engine. + */ +void remmina_plugin_python_module_init(void); + +/** + * @brief Returns a pointer to the Python instance, mapped to the RemminaProtocolWidget or null if not 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* remmina_plugin_python_module_get_plugin(RemminaProtocolWidget* gp); + +void ToRemminaProtocolSetting(RemminaProtocolSetting* dest, PyObject* setting); diff --git a/src/remmina_plugin_python_remmina_file.c b/src/remmina_plugin_python_remmina_file.c new file mode 100644 index 000000000..0446a1b3c --- /dev/null +++ b/src/remmina_plugin_python_remmina_file.c @@ -0,0 +1,126 @@ +#include <glib.h> +#include <gtk/gtk.h> +#define PY_SSIZE_T_CLEAN +#include <Python.h> +#include <structmember.h> + +#include "remmina/remmina_trace_calls.h" +#include "remmina_file.h" +#include "remmina_plugin_python_remmina_file.h" + +typedef struct { + PyObject_HEAD + RemminaFile* file; +} PyRemminaFile; + +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", (PyCFunctionWithKeywords)file_set_setting, METH_VARARGS | METH_KEYWORDS, "Set file setting" }, + { "get_setting", (PyCFunctionWithKeywords)file_set_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 }, + {NULL} +}; + +/** + * @brief The definition of the Python module 'remmina'. + */ +static PyTypeObject python_remmina_file_type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "remmina.RemminaFileType", + .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 +}; + +PyObject* remmina_plugin_python_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) +{ + return Py_BuildValue("s", remmina_file_get_filename(self->file)); +} + +static PyObject* file_set_setting(PyRemminaFile* self, PyObject* args, PyObject* kwds) +{ + static const gchar* keyword_list[] = { "key", "value" }; + gchar* key; + PyObject* value; + + if (PyArg_ParseTupleAndKeywords(args, kwds, "sO", keyword_list, &key, &value)) { + if (PyUnicode_Check(value)) { + remmina_file_set_string(self->file, key, PyUnicode_AsUTF8(value)); + } else if (PyLong_Check(value)) { + remmina_file_set_int(self->file, key, PyUnicode_AsUTF8(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) +{ + static const 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", remmina_file_get_string(self->file, key)); + } else if (PyLong_Check(def)) { + return Py_BuildValue("i", remmina_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 NULL; + } +} + +static PyObject* file_get_secret(PyRemminaFile* self, PyObject* key) +{ + static const gchar* keyword_list[] = { "key", "default" }; + + if (key && PyUnicode_Check(key)) { + return Py_BuildValue("s", remmina_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) +{ + if (self) { + remmina_file_unsave_passwords(self->file); + return Py_None; + } else { + g_printerr("unsave_passwords(): self is null!\n"); + return NULL; + } +} diff --git a/src/remmina_plugin_python_remmina_file.h b/src/remmina_plugin_python_remmina_file.h new file mode 100644 index 000000000..08ed3e4bb --- /dev/null +++ b/src/remmina_plugin_python_remmina_file.h @@ -0,0 +1,3 @@ +#pragma once + +PyObject* remmina_plugin_python_remmina_file_to_python(RemminaFile* file); diff --git a/src/remmina_protocol_widget.c b/src/remmina_protocol_widget.c index e8c41b544..ec314977f 100644 --- a/src/remmina_protocol_widget.c +++ b/src/remmina_protocol_widget.c @@ -1808,11 +1808,20 @@ void remmina_protocol_widget_setup(RemminaProtocolWidget *gp, RemminaFile *remmi return; } gp->priv->plugin = plugin; + gp->plugin = plugin; gp->priv->scalemode = remmina_file_get_int(gp->priv->remmina_file, "scale", FALSE); gp->priv->scaler_expand = remmina_file_get_int(gp->priv->remmina_file, "scaler_expand", FALSE); } +GtkWindow* remmina_protocol_widget_get_gtkwindow(RemminaProtocolWidget *gp) { + return rcw_get_gtkwindow(gp->cnnobj); +} + +GtkWidget *remmina_protocol_widget_gtkviewport(RemminaProtocolWidget *gp) { + return rcw_get_gtkviewport(gp->cnnobj); +} + GtkWidget *remmina_protocol_widget_new(void) { return GTK_WIDGET(g_object_new(REMMINA_TYPE_PROTOCOL_WIDGET, NULL)); diff --git a/src/remmina_protocol_widget.h b/src/remmina_protocol_widget.h index d8d401f7a..562537755 100644 --- a/src/remmina_protocol_widget.h +++ b/src/remmina_protocol_widget.h @@ -53,11 +53,13 @@ G_BEGIN_DECLS #define REMMINA_PROTOCOL_WIDGET_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_PROTOCOL_WIDGET, RemminaProtocolWidgetClass)) typedef struct _RemminaProtocolWidgetPriv RemminaProtocolWidgetPriv; +typedef struct _RemminaProtocolPlugin RemminaProtocolPlugin; struct _RemminaProtocolWidget { GtkEventBox event_box; RemminaConnectionObject * cnnobj; RemminaProtocolWidgetPriv * priv; + RemminaProtocolPlugin * plugin; }; struct _RemminaProtocolWidgetClass { @@ -74,6 +76,9 @@ struct _RemminaProtocolWidgetClass { GType remmina_protocol_widget_get_type(void) G_GNUC_CONST; +GtkWindow* remmina_protocol_widget_get_gtkwindow(RemminaProtocolWidget *gp); +GtkWidget* remmina_protocol_widget_gtkviewport(RemminaProtocolWidget *gp); + GtkWidget *remmina_protocol_widget_new(void); void remmina_protocol_widget_setup(RemminaProtocolWidget *gp, RemminaFile *remminafile, RemminaConnectionObject *cnnobj); diff --git a/src/remmina_public.c b/src/remmina_public.c index 50eef6790..3ac9971eb 100644 --- a/src/remmina_public.c +++ b/src/remmina_public.c @@ -67,6 +67,9 @@ remmina_public_create_combo_entry(const gchar *text, const gchar *def, gboolean gchar *buf, *ptr1, *ptr2; gint i; + g_print("text: %s\n", text); + g_print("def: %s\n", def); + combo = gtk_combo_box_text_new_with_entry(); found = FALSE; |