Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/Remmina/Remmina.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntenore Gatta (tmow) <antenore@simbiosi.org>2021-01-04 09:10:30 +0300
committerAntenore Gatta (tmow) <antenore@simbiosi.org>2021-01-04 09:10:30 +0300
commit57f7845e551fd47bcdb46304c64a8a11a94768db (patch)
treeebfa38fc47ca199497f9a021bbeaf0d6ef2c4db4
parent8220012e15d58f5953ed642a45d20431f77f61b8 (diff)
parentef3cdec971b7f111b20d5ac5fa11a9c8b8d7459c (diff)
Merge branch 'master' into 'master'
Add capability to load Python plugins (not finished). See merge request Remmina/Remmina!2157
-rw-r--r--plugins/pyvnc/pyvnc.py97
-rw-r--r--plugins/tool_hello_world_python/toolsdevler.py72
-rw-r--r--src/CMakeLists.txt16
-rw-r--r--src/include/remmina/plugin.h2
-rw-r--r--src/include/remmina/types.h6
-rw-r--r--src/pygobject.h635
-rw-r--r--src/rcw.c3
-rw-r--r--src/rcw.h4
-rw-r--r--src/remmina.c4
-rw-r--r--src/remmina_plugin_manager.c56
-rw-r--r--src/remmina_plugin_manager.h10
-rw-r--r--src/remmina_plugin_native.c47
-rw-r--r--src/remmina_plugin_native.h47
-rw-r--r--src/remmina_plugin_python.c153
-rw-r--r--src/remmina_plugin_python.h59
-rw-r--r--src/remmina_plugin_python_module.c284
-rw-r--r--src/remmina_plugin_python_module.h44
-rw-r--r--src/remmina_plugin_python_protocol_widget.c751
-rw-r--r--src/remmina_plugin_python_protocol_widget.h37
-rw-r--r--src/remmina_plugin_python_remmina.c788
-rw-r--r--src/remmina_plugin_python_remmina.h85
-rw-r--r--src/remmina_plugin_python_remmina_file.c126
-rw-r--r--src/remmina_plugin_python_remmina_file.h3
-rw-r--r--src/remmina_protocol_widget.c9
-rw-r--r--src/remmina_protocol_widget.h5
-rw-r--r--src/remmina_public.c3
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
diff --git a/src/rcw.c b/src/rcw.c
index a4f00ddc1..2f01b936a 100644
--- a/src/rcw.c
+++ b/src/rcw.c
@@ -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__);
diff --git a/src/rcw.h b/src/rcw.h
index 3e20180ae..7e39996ce 100644
--- a/src/rcw.h
+++ b/src/rcw.h
@@ -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;