diff options
author | Mathias Winterhalter <2370037-ToolsDevler@users.noreply.gitlab.com> | 2022-04-18 02:43:50 +0300 |
---|---|---|
committer | Antenore Gatta (tmow) <antenore@simbiosi.org> | 2022-04-18 02:43:50 +0300 |
commit | 6092eca0b3576f89bd586e89b3e56ad477b8bb3c (patch) | |
tree | 217f4248fb0d4068b9b7363d9049133a7a1d5ff1 | |
parent | 9333622adbaaa572822fcde486b74691d386b8eb (diff) |
Python plugins
46 files changed, 3647 insertions, 1519 deletions
diff --git a/.gitignore b/.gitignore index 272f81a3a..578139080 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ *~ build builddir +cmake-build-debug/ +cmake-build-release/ tags data/desktop/org.remmina.Remmina.desktop diff --git a/flatpak/org.remmina.Remmina-local.json b/flatpak/org.remmina.Remmina-local.json index 99ee971df..c437fc40c 100644 --- a/flatpak/org.remmina.Remmina-local.json +++ b/flatpak/org.remmina.Remmina-local.json @@ -1,7 +1,7 @@ { "app-id": "org.remmina.Remmina", "runtime": "org.gnome.Platform", - "runtime-version": "41", + "runtime-version": "42", "sdk": "org.gnome.Sdk", "command": "remmina", "cleanup": [ @@ -66,7 +66,7 @@ ], "modules": [ "shared-modules/intltool/intltool-0.51.json", - "shared-modules/dbus-glib/dbus-glib-0.110.json", + "shared-modules/dbus-glib/dbus-glib.json", "shared-modules/libappindicator/libappindicator-gtk3-12.10.json", "shared-modules/libusb/libusb.json", { diff --git a/flatpak/org.remmina.Remmina.json b/flatpak/org.remmina.Remmina.json index e08480aff..99d0155e3 100644 --- a/flatpak/org.remmina.Remmina.json +++ b/flatpak/org.remmina.Remmina.json @@ -66,7 +66,7 @@ ], "modules": [ "shared-modules/intltool/intltool-0.51.json", - "shared-modules/dbus-glib/dbus-glib-0.110.json", + "shared-modules/dbus-glib/dbus-glib.json", "shared-modules/libappindicator/libappindicator-gtk3-12.10.json", "shared-modules/libusb/libusb.json", { diff --git a/flatpak/shared-modules b/flatpak/shared-modules -Subproject e71f3d4bb6d16088230464ffe33743a8cfa28b0 +Subproject 977feac6610e324a44e38fc2946b3d333e170a7 diff --git a/plugins/pyvnc/pyvnc.py b/plugins/pyvnc/pyvnc.py deleted file mode 100644 index 04c6c24e1..000000000 --- a/plugins/pyvnc/pyvnc.py +++ /dev/null @@ -1,97 +0,0 @@ -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 = "org.remmina.Remmina-vnc-symbolic" - self.icon_name_ssh = "org.remmina.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) diff --git a/plugins/secret/src/glibsecret_plugin.c b/plugins/secret/src/glibsecret_plugin.c index 5bd585762..dc6d8cc36 100644 --- a/plugins/secret/src/glibsecret_plugin.c +++ b/plugins/secret/src/glibsecret_plugin.c @@ -61,7 +61,7 @@ static SecretCollection* defaultcollection; #endif -gboolean remmina_plugin_glibsecret_is_service_available() +gboolean remmina_plugin_glibsecret_is_service_available(RemminaSecretPlugin* plugin) { #ifdef LIBSECRET_VERSION_0_18 if (secretservice && defaultcollection) @@ -73,7 +73,7 @@ gboolean remmina_plugin_glibsecret_is_service_available() #endif } -static void remmina_plugin_glibsecret_unlock_secret_service() +static void remmina_plugin_glibsecret_unlock_secret_service(RemminaSecretPlugin* plugin) { TRACE_CALL(__func__); @@ -97,7 +97,7 @@ static void remmina_plugin_glibsecret_unlock_secret_service() return; } -void remmina_plugin_glibsecret_store_password(RemminaFile *remminafile, const gchar *key, const gchar *password) +void remmina_plugin_glibsecret_store_password(RemminaSecretPlugin* plugin, RemminaFile *remminafile, const gchar *key, const gchar *password) { TRACE_CALL(__func__); GError *r = NULL; @@ -118,7 +118,7 @@ void remmina_plugin_glibsecret_store_password(RemminaFile *remminafile, const gc } gchar* -remmina_plugin_glibsecret_get_password(RemminaFile *remminafile, const gchar *key) +remmina_plugin_glibsecret_get_password(RemminaSecretPlugin* plugin, RemminaFile *remminafile, const gchar *key) { TRACE_CALL(__func__); GError *r = NULL; @@ -138,7 +138,7 @@ remmina_plugin_glibsecret_get_password(RemminaFile *remminafile, const gchar *ke } } -void remmina_plugin_glibsecret_delete_password(RemminaFile *remminafile, const gchar *key) +void remmina_plugin_glibsecret_delete_password(RemminaSecretPlugin* plugin, RemminaFile *remminafile, const gchar *key) { TRACE_CALL(__func__); GError *r = NULL; @@ -152,7 +152,7 @@ void remmina_plugin_glibsecret_delete_password(RemminaFile *remminafile, const g REMMINA_PLUGIN_DEBUG("password “%s” cannot be deleted for file %s", key, path); } -gboolean remmina_plugin_glibsecret_init() +gboolean remmina_plugin_glibsecret_init(RemminaSecretPlugin* plugin) { #ifdef LIBSECRET_VERSION_0_18 GError *error; @@ -173,7 +173,7 @@ gboolean remmina_plugin_glibsecret_init() return FALSE; } - remmina_plugin_glibsecret_unlock_secret_service(); + remmina_plugin_glibsecret_unlock_secret_service(plugin); return TRUE; #else diff --git a/plugins/tool_hello_world_python/toolsdevler.py b/plugins/tool_hello_world_python/toolsdevler.py deleted file mode 100644 index d47d86d5e..000000000 --- a/plugins/tool_hello_world_python/toolsdevler.py +++ /dev/null @@ -1,72 +0,0 @@ -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 = "org.remmina.Remmina-tool-symbolic" - self.icon_name_ssh = "org.remmina.Remmina-tool-symbolic" - 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 ca002d95f..a743f5d9a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -158,8 +158,20 @@ if(WITH_PYTHONLIBS) ${SRCINDEX} "remmina_plugin_python.c" "remmina_plugin_python.h" - "remmina_plugin_python_module.c" - "remmina_plugin_python_module.h" + "remmina_plugin_python_protocol.c" + "remmina_plugin_python_protocol.h" + "remmina_plugin_python_entry.c" + "remmina_plugin_python_entry.h" + "remmina_plugin_python_file.c" + "remmina_plugin_python_file.h" + "remmina_plugin_python_secret.c" + "remmina_plugin_python_secret.h" + "remmina_plugin_python_tool.c" + "remmina_plugin_python_tool.h" + "remmina_plugin_python_pref.c" + "remmina_plugin_python_pref.h" + "remmina_plugin_python_common.c" + "remmina_plugin_python_common.h" "remmina_plugin_python_remmina.c" "remmina_plugin_python_remmina.h" "remmina_plugin_python_protocol_widget.c" diff --git a/src/include/remmina/plugin.h b/src/include/remmina/plugin.h index a6ff980f3..c75289af8 100644 --- a/src/include/remmina/plugin.h +++ b/src/include/remmina/plugin.h @@ -70,7 +70,7 @@ typedef struct _RemminaProtocolPlugin { const gchar * icon_name; const gchar * icon_name_ssh; const RemminaProtocolSetting * basic_settings; - const RemminaProtocolSetting * advanced_settings; + const RemminaProtocolSetting * advanced_settings; RemminaProtocolSSHSetting ssh_setting; const RemminaProtocolFeature * features; @@ -92,7 +92,7 @@ typedef struct _RemminaEntryPlugin { const gchar * domain; const gchar * version; - void (*entry_func)(void); + void (*entry_func)(struct _RemminaEntryPlugin* instance); } RemminaEntryPlugin; typedef struct _RemminaFilePlugin { @@ -102,10 +102,10 @@ typedef struct _RemminaFilePlugin { const gchar * domain; const gchar * version; - gboolean (*import_test_func)(const gchar *from_file); - RemminaFile * (*import_func)(const gchar * from_file); - gboolean (*export_test_func)(RemminaFile *file); - gboolean (*export_func)(RemminaFile *file, const gchar *to_file); + gboolean (*import_test_func)(struct _RemminaFilePlugin* instance, const gchar *from_file); + RemminaFile * (*import_func)(struct _RemminaFilePlugin* instance, const gchar * from_file); + gboolean (*export_test_func)(struct _RemminaFilePlugin* instance, RemminaFile *file); + gboolean (*export_func)(struct _RemminaFilePlugin* instance, RemminaFile *file, const gchar *to_file); const gchar * export_hints; } RemminaFilePlugin; @@ -116,7 +116,7 @@ typedef struct _RemminaToolPlugin { const gchar * domain; const gchar * version; - void (*exec_func)(void); + void (*exec_func)(GtkMenuItem* item, struct _RemminaToolPlugin* instance); } RemminaToolPlugin; typedef struct _RemminaPrefPlugin { @@ -127,7 +127,7 @@ typedef struct _RemminaPrefPlugin { const gchar * version; const gchar * pref_label; - GtkWidget * (*get_pref_body)(void); + GtkWidget * (*get_pref_body)(struct _RemminaPrefPlugin* instance); } RemminaPrefPlugin; typedef struct _RemminaSecretPlugin { @@ -138,11 +138,11 @@ typedef struct _RemminaSecretPlugin { const gchar * version; int init_order; - gboolean (*init)(void); - gboolean (*is_service_available)(void); - void (*store_password)(RemminaFile *remminafile, const gchar *key, const gchar *password); - gchar * (*get_password)(RemminaFile * remminafile, const gchar *key); - void (*delete_password)(RemminaFile *remminafile, const gchar *key); + gboolean (*init)(struct _RemminaSecretPlugin* instance); + gboolean (*is_service_available)(struct _RemminaSecretPlugin* instance); + void (*store_password)(struct _RemminaSecretPlugin* instance, RemminaFile *remminafile, const gchar *key, const gchar *password); + gchar * (*get_password)(struct _RemminaSecretPlugin* instance, RemminaFile * remminafile, const gchar *key); + void (*delete_password)(struct _RemminaSecretPlugin* instance, RemminaFile *remminafile, const gchar *key); } RemminaSecretPlugin; /* Plugin Service is a struct containing a list of function pointers, diff --git a/src/include/remmina/types.h b/src/include/remmina/types.h index 3f27691a8..4db5a5e17 100644 --- a/src/include/remmina/types.h +++ b/src/include/remmina/types.h @@ -37,6 +37,7 @@ #pragma once #include <glib.h> +#include <glib-object.h> G_BEGIN_DECLS @@ -56,12 +57,27 @@ typedef enum { #define REMMINA_PROTOCOL_FEATURE_PREF_RADIO 1 #define REMMINA_PROTOCOL_FEATURE_PREF_CHECK 2 +typedef enum +{ + REMMINA_TYPEHINT_STRING, + REMMINA_TYPEHINT_SIGNED, + REMMINA_TYPEHINT_UNSIGNED, + REMMINA_TYPEHINT_BOOLEAN, + REMMINA_TYPEHINT_CPOINTER, + REMMINA_TYPEHINT_RAW, + REMMINA_TYPEHINT_TUPLE, + REMMINA_TYPEHINT_UNDEFINED, +} RemminaTypeHint; + typedef struct _RemminaProtocolFeature { RemminaProtocolFeatureType type; gint id; gpointer opt1; gpointer opt2; gpointer opt3; + RemminaTypeHint opt1_type_hint; + RemminaTypeHint opt2_type_hint; + RemminaTypeHint opt3_type_hint; } RemminaProtocolFeature; typedef struct _RemminaPluginScreenshotData { @@ -3682,7 +3682,7 @@ static RemminaConnectionWindow *rcw_create_scrolled(gint width, gint height, gbo gtk_widget_realize(GTK_WIDGET(cnnwin)); gtk_window_set_default_size(GTK_WINDOW(cnnwin), width, height); - g_object_set(settings, "gtk-application-prefer-dark-theme", remmina_pref.dark_theme, NULL); + g_object_set(settings, "gtk-application-prefer-dark-theme", &remmina_pref.dark_theme, NULL); /* Create the toolbar */ toolbar = rcw_create_toolbar(cnnwin, SCROLLED_WINDOW_MODE); diff --git a/src/remmina.c b/src/remmina.c index c3eb55c46..9d658d73e 100644 --- a/src/remmina.c +++ b/src/remmina.c @@ -303,7 +303,7 @@ static void remmina_on_startup(GApplication *app) if (!secret_plugin) g_print("Warning: Remmina is running without a secret plugin. Passwords will be saved in a less secure way.\n"); else - if (!secret_plugin->is_service_available()) + if (!secret_plugin->is_service_available(secret_plugin)) g_print("Warning: Remmina is running with a secrecy plugin, but it cannot connect to a secrecy service.\n"); remmina_exec_command(REMMINA_COMMAND_AUTOSTART, NULL); diff --git a/src/remmina_exec.c b/src/remmina_exec.c index bc6067edf..6b2c5cbd9 100644 --- a/src/remmina_exec.c +++ b/src/remmina_exec.c @@ -486,7 +486,7 @@ void remmina_exec_command(RemminaCommandType command, const gchar* data) case REMMINA_COMMAND_PLUGIN: plugin = (RemminaEntryPlugin*)remmina_plugin_manager_get_plugin(REMMINA_PLUGIN_TYPE_ENTRY, data); if (plugin) { - plugin->entry_func(); + plugin->entry_func(plugin); }else { widget = gtk_message_dialog_new(NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, _("Plugin %s is not registered."), data); diff --git a/src/remmina_file.c b/src/remmina_file.c index 9dc38a516..06db1ebc0 100644 --- a/src/remmina_file.c +++ b/src/remmina_file.c @@ -387,7 +387,7 @@ remmina_file_load(const gchar *filename) } secret_plugin = remmina_plugin_manager_get_secret_plugin(); - secret_service_available = secret_plugin && secret_plugin->is_service_available(); + secret_service_available = secret_plugin && secret_plugin->is_service_available(secret_plugin); remminafile->filename = g_strdup(filename); gsize nkeys = 0; @@ -421,7 +421,7 @@ remmina_file_load(const gchar *filename) } #endif if ((g_strcmp0(s, ".") == 0) && (secret_service_available)) { - gchar *sec = secret_plugin->get_password(remminafile, key); + gchar *sec = secret_plugin->get_password(secret_plugin, remminafile, key); remmina_file_set_string(remminafile, key, sec); /* Annotate in spsettings that this value comes from secret_plugin */ g_hash_table_insert(remminafile->spsettings, g_strdup(key), NULL); @@ -741,7 +741,7 @@ void remmina_file_save(RemminaFile *remminafile) } secret_plugin = remmina_plugin_manager_get_secret_plugin(); - secret_service_available = secret_plugin && secret_plugin->is_service_available(); + secret_service_available = secret_plugin && secret_plugin->is_service_available(secret_plugin); g_hash_table_iter_init(&iter, remminafile->settings); while (g_hash_table_iter_next(&iter, (gpointer *)&key, (gpointer *)&value)) { @@ -751,11 +751,11 @@ void remmina_file_save(RemminaFile *remminafile) REMMINA_DEBUG("We have a secret and disablepasswordstoring=0"); if (value && value[0]) { if (g_strcmp0(value, ".") != 0) - secret_plugin->store_password(remminafile, key, value); + secret_plugin->store_password(secret_plugin, remminafile, key, value); g_key_file_set_string(gkeyfile, KEYFILE_GROUP_REMMINA, key, "."); } else { g_key_file_set_string(gkeyfile, KEYFILE_GROUP_REMMINA, key, ""); - secret_plugin->delete_password(remminafile, key); + secret_plugin->delete_password(secret_plugin, remminafile, key); } } else { REMMINA_DEBUG("We have a password and disablepasswordstoring=0"); @@ -771,7 +771,7 @@ void remmina_file_save(RemminaFile *remminafile) if (value && value[0]) { if (g_strcmp0(value, ".") != 0) { REMMINA_DEBUG("Deleting the secret in the keyring as disablepasswordstoring=1"); - secret_plugin->delete_password(remminafile, key); + secret_plugin->delete_password(secret_plugin, remminafile, key); g_key_file_set_string(gkeyfile, KEYFILE_GROUP_REMMINA, key, "."); } } @@ -831,7 +831,7 @@ void remmina_file_store_secret_plugin_password(RemminaFile *remminafile, const g if (g_hash_table_lookup_extended(remminafile->spsettings, g_strdup(key), NULL, NULL)) { plugin = remmina_plugin_manager_get_secret_plugin(); - plugin->store_password(remminafile, key, value); + plugin->store_password(plugin, remminafile, key, value); } else { remmina_file_set_string(remminafile, key, value); remmina_file_save(remminafile); diff --git a/src/remmina_file_manager.c b/src/remmina_file_manager.c index 0c87d6a97..1edf3e391 100644 --- a/src/remmina_file_manager.c +++ b/src/remmina_file_manager.c @@ -364,7 +364,7 @@ RemminaFile *remmina_file_manager_load_file(const gchar *filename) } else { plugin = remmina_plugin_manager_get_import_file_handler(filename); if (plugin) - remminafile = plugin->import_func(filename); + remminafile = plugin->import_func(plugin, filename); } return remminafile; } diff --git a/src/remmina_main.c b/src/remmina_main.c index 8b1fc2657..b856b7df5 100644 --- a/src/remmina_main.c +++ b/src/remmina_main.c @@ -286,7 +286,7 @@ static void remmina_main_show_snap_welcome() g_print(" but we can’t find the secret plugin inside the SNAP.\n"); need_snap_interface_connections = TRUE; } else { - if (!remmina_secret_plugin->is_service_available()) { + if (!remmina_secret_plugin->is_service_available(remmina_secret_plugin)) { g_print(" but we can’t access a secret service. Secret service or SNAP interface connection is missing.\n"); need_snap_interface_connections = TRUE; } @@ -987,7 +987,7 @@ void remmina_main_on_action_application_preferences(GSimpleAction *action, GVari gtk_widget_show_all(widget); /* Switch to a dark theme if the user enabled it */ settings = gtk_settings_get_default(); - g_object_set(settings, "gtk-application-prefer-dark-theme", remmina_pref.dark_theme, NULL); + g_object_set(settings, "gtk-application-prefer-dark-theme", &remmina_pref.dark_theme, NULL); } void remmina_main_on_action_application_default(GSimpleAction *action, GVariant *param, gpointer data) @@ -1050,7 +1050,7 @@ static void remmina_main_import_file_list(GSList *files) for (element = files; element; element = element->next) { path = (gchar *)element->data; plugin = remmina_plugin_manager_get_import_file_handler(path); - if (plugin && (remminafile = plugin->import_func(path)) != NULL && remmina_file_get_string(remminafile, "name")) { + if (plugin && (remminafile = plugin->import_func(plugin, path)) != NULL && remmina_file_get_string(remminafile, "name")) { remmina_file_generate_filename(remminafile); remmina_file_save(remminafile); imported = TRUE; @@ -1119,7 +1119,7 @@ void remmina_main_on_action_tools_export(GSimpleAction *action, GVariant *param, dialog = gtk_file_chooser_dialog_new(plugin->export_hints, remminamain->window, GTK_FILE_CHOOSER_ACTION_SAVE, _("_Save"), GTK_RESPONSE_ACCEPT, NULL); if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) - plugin->export_func(remminafile, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog))); + plugin->export_func(plugin, remminafile, gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog))); gtk_widget_destroy(dialog); } else { dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR, GTK_BUTTONS_OK, @@ -1415,7 +1415,7 @@ static gboolean remmina_main_add_tool_plugin(gchar *name, RemminaPlugin *plugin, gtk_widget_show(menuitem); gtk_menu_shell_append(GTK_MENU_SHELL(remminamain->menu_popup_full), menuitem); - g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(tool_plugin->exec_func), NULL); + g_signal_connect(G_OBJECT(menuitem), "activate", G_CALLBACK(tool_plugin->exec_func), tool_plugin); return FALSE; } @@ -1436,7 +1436,7 @@ static void remmina_main_init(void) REMMINA_DEBUG("Initializing the Remmina main window"); /* Switch to a dark theme if the user enabled it */ settings = gtk_settings_get_default(); - g_object_set(settings, "gtk-application-prefer-dark-theme", remmina_pref.dark_theme, NULL); + g_object_set(settings, "gtk-application-prefer-dark-theme", &remmina_pref.dark_theme, NULL); REMMINA_DEBUG ("Initializing monitor"); remminamain->monitor = remmina_network_monitor_new(); @@ -1607,14 +1607,23 @@ void remmina_main_update_file_datetime(RemminaFile *file) remmina_main_load_files(); } -void remmina_main_show_warning_dialog(const gchar *message) -{ +void remmina_main_show_dialog(GtkMessageType msg, GtkButtonsType buttons, const gchar* message) { GtkWidget *dialog; if (remminamain->window) { - dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE, - message, g_get_application_name()); + dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, msg, buttons, message); gtk_dialog_run(GTK_DIALOG(dialog)); gtk_widget_destroy(dialog); } } + +void remmina_main_show_warning_dialog(const gchar *message) { + GtkWidget *dialog; + + if (remminamain->window) { + dialog = gtk_message_dialog_new(remminamain->window, GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE, + message, g_get_application_name()); + gtk_dialog_run(GTK_DIALOG(dialog)); + gtk_widget_destroy(dialog); + } +} diff --git a/src/remmina_main.h b/src/remmina_main.h index b49844fca..e381cfad3 100644 --- a/src/remmina_main.h +++ b/src/remmina_main.h @@ -109,6 +109,7 @@ void remmina_main_destroy(void); void remmina_main_on_destroy_event(void); void remmina_main_save_before_destroy(void); +void remmina_main_show_dialog(GtkMessageType msg, GtkButtonsType buttons, const gchar* message); void remmina_main_show_warning_dialog(const gchar *message); void remmina_main_on_action_application_about(GSimpleAction *action, GVariant *param, gpointer data); void remmina_main_on_action_application_news(GSimpleAction *action, GVariant *param, gpointer data); diff --git a/src/remmina_mpchange.c b/src/remmina_mpchange.c index e05904b9e..6f777b584 100644 --- a/src/remmina_mpchange.c +++ b/src/remmina_mpchange.c @@ -326,7 +326,7 @@ static gboolean remmina_file_multipasswd_changer_mt(gpointer d) if (secret_plugin == NULL) { initerror = _("The multi password changer requires a secrecy plugin.\n"); }else { - if (!secret_plugin->is_service_available()) { + if (!secret_plugin->is_service_available(secret_plugin)) { initerror = _("The multi password changer requires a secrecy service.\n"); } } diff --git a/src/remmina_plugin_manager.c b/src/remmina_plugin_manager.c index ea4cc057e..f61f236d4 100644 --- a/src/remmina_plugin_manager.c +++ b/src/remmina_plugin_manager.c @@ -355,7 +355,7 @@ void remmina_plugin_manager_init() sple = secret_plugins; while(sple != NULL) { sp = (RemminaSecretPlugin*)sple->data; - if (sp->init()) { + if (sp->init(sp)) { g_print("The %s secret plugin has been initialized and it will be your default secret plugin\n", sp->name); remmina_secret_plugin = sp; @@ -516,7 +516,7 @@ RemminaFilePlugin* remmina_plugin_manager_get_import_file_handler(const gchar *f if (plugin->type != REMMINA_PLUGIN_TYPE_FILE) continue; - if (plugin->import_test_func(file)) { + if (plugin->import_test_func(plugin, file)) { return plugin; } } @@ -533,7 +533,7 @@ RemminaFilePlugin* remmina_plugin_manager_get_export_file_handler(RemminaFile *r plugin = (RemminaFilePlugin*)g_ptr_array_index(remmina_plugin_table, i); if (plugin->type != REMMINA_PLUGIN_TYPE_FILE) continue; - if (plugin->export_test_func(remminafile)) { + if (plugin->export_test_func(plugin, remminafile)) { return plugin; } } diff --git a/src/remmina_plugin_native.c b/src/remmina_plugin_native.c index f2d2ba989..9961d5641 100644 --- a/src/remmina_plugin_native.c +++ b/src/remmina_plugin_native.c @@ -53,29 +53,33 @@ #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; +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) { + 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)) { + 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)) { + if (!entry(service)) + { g_print("Plugin entry returned false: %s.\n", name); return FALSE; } - return TRUE; + 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 index 7d9d8a123..21406654c 100644 --- a/src/remmina_plugin_native.h +++ b/src/remmina_plugin_native.h @@ -38,7 +38,7 @@ G_BEGIN_DECLS -typedef gboolean (*RemminaPluginMain)(gchar *name); +typedef gboolean (* RemminaPluginMain)(gchar* name); gboolean remmina_plugin_native_load(RemminaPluginService* service, const char* name); diff --git a/src/remmina_plugin_python.c b/src/remmina_plugin_python.c index eecb33f60..be83a4635 100644 --- a/src/remmina_plugin_python.c +++ b/src/remmina_plugin_python.c @@ -17,134 +17,160 @@ * 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 executed and the plugin - * classes get mapped to "real" Remmina plugin instances. - * - * For the communication between Remmina and Python the python module called 'remmina' - * is initialized and loaded into the environment of the plugin script - * (@see remmina_plugin_python_module.c). - * - * @see http://www.remmina.org/wp for more information. + * 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. */ -#include <gtk/gtk.h> -#define PY_SSIZE_T_CLEAN -#include <Python.h> -#include <structmember.h> +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N C L U D E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #include "config.h" #include "remmina/remmina_trace_calls.h" +#include "remmina_plugin_python_common.h" #include "remmina_plugin_python.h" -#include "remmina_plugin_python_module.h" +#include "remmina_plugin_python_remmina.h" #include "remmina_plugin_python_protocol_widget.h" +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// D E C L A R A T I O N S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * An null terminated array of commands that are executed after the initialization of the Python engine. Every entry + * represents a line of Python code. + */ +static const char + * python_init_commands[] = { "import sys", "sys.path.append('" REMMINA_RUNTIME_PLUGINDIR "')", NULL // Sentinel +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// U T I L S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** - * @brief Extracts the filename without extension from a path. + * @brief Extracts the filename without extension from a path. * * @param in The string to extract the filename from * @param out The resulting filename without extension (must point to allocated memory). * * @return The length of the filename extracted. */ -static int basename_no_ext(const char* in, char** out); +static int basename_no_ext(const char* in, char** out) +{ + TRACE_CALL(__func__); -/** - * - */ -void remmina_plugin_python_init(void) { - TRACE_CALL(__FUNC__); + assert(in); + assert(out); - remmina_plugin_python_module_init(); + const char* base = strrchr(in, '/'); + if (base) + { + base++; + } - Py_Initialize(); - - // Tell the Python engine where to look for Python scripts. - PyRun_SimpleString("import sys"); - PyRun_SimpleString("sys.path.append('" REMMINA_RUNTIME_PLUGINDIR "')"); - - remmina_plugin_python_protocol_widget_init(); -} + const char* base_end = strrchr(base, '.'); + if (!base_end) + { + base_end = base + strlen(base); + } -gboolean remmina_plugin_python_load(RemminaPluginService* service, const gchar* name) { - TRACE_CALL(__FUNC__); + const int length = base_end - base; + const int memsize = sizeof(char*) * ((length) + 1); - 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; - } + *out = (char*)remmina_plugin_python_malloc(memsize); - PyObject *plugin_name = PyUnicode_DecodeFSDefault(filename); - free(filename); + memset(*out, 0, memsize); + strncpy(*out, base, length); + (*out)[length] = '\0'; - if (!plugin_name) { - g_printerr("[%s:%s]: Error converting plugin file name to PyUnicode!\n", __FILE__, __LINE__); - return FALSE; - } + return length; +} - 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; - } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - PyUnicode_AsWideChar(plugin_name, program_name, len); +void remmina_plugin_python_init(void) +{ + TRACE_CALL(__func__); - // 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); + remmina_plugin_python_module_init(); + Py_Initialize(); - if (PyImport_Import(plugin_name)) { - return TRUE; - } + for (const char** ptr = python_init_commands; *ptr; ++ptr) + { + PyRun_SimpleString(*ptr); + } - g_print("Failed to load python plugin file: “%s”\n", name); - PyErr_Print(); - return FALSE; + remmina_plugin_python_protocol_widget_init(); } - -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; +gboolean remmina_plugin_python_load(RemminaPluginService* service, const char* name) +{ + TRACE_CALL(__func__); + + assert(service); + assert(name); + + remmina_plugin_python_set_service(service); + + char* filename = NULL; + if (basename_no_ext(name, &filename) == 0) + { + g_printerr("[%s:%d]: Can not extract filename from '%s'!\n", __FILE__, __LINE__, name); + return FALSE; + } + + PyObject* plugin_name = PyUnicode_DecodeFSDefault(filename); + + if (!plugin_name) + { + free(filename); + g_printerr("[%s:%d]: Error converting plugin filename to PyUnicode!\n", __FILE__, __LINE__); + return FALSE; + } + + wchar_t* program_name = NULL; + Py_ssize_t len = PyUnicode_AsWideChar(plugin_name, program_name, 0); + if (len <= 0) + { + free(filename); + g_printerr("[%s:%d]: Failed allocating %lu bytes!\n", __FILE__, __LINE__, (sizeof(wchar_t) * len)); + return FALSE; + } + + program_name = (wchar_t*)remmina_plugin_python_malloc(sizeof(wchar_t) * len); + if (!program_name) + { + free(filename); + g_printerr("[%s:%d]: Failed allocating %lu bytes!\n", __FILE__, __LINE__, (sizeof(wchar_t) * len)); + return FALSE; + } + + PyUnicode_AsWideChar(plugin_name, program_name, len); + + PySys_SetArgv(1, &program_name); + + if (PyImport_Import(plugin_name)) + { + free(filename); + return TRUE; + } + + g_print("[%s:%d]: Failed to load python plugin file: '%s'\n", __FILE__, __LINE__, name); + PyErr_Print(); + free(filename); + + return FALSE; } diff --git a/src/remmina_plugin_python.h b/src/remmina_plugin_python.h index 233a224ef..97f53768d 100644 --- a/src/remmina_plugin_python.h +++ b/src/remmina_plugin_python.h @@ -29,23 +29,46 @@ * 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.h + * + * @brief Declares the interface between the Python plugin implementation and Remmina covering the initialization of + * the implementation and the load function, that allows Remmina to load plugins into the application. * + * @details When Remmina discovers Python scripts in the plugin root folder the plugin manager passes the path to the + * Python plugin loader. There it gets executed and the plugin classes get mapped to "real" Remmina plugin + * instances. + * + * For the communication between Remmina and Python the python module called 'remmina' is initialized and + * loaded into the environment of the plugin script (@see remmina_plugin_python_module.c). + * + * @see http://www.remmina.org/wp for more information. */ #pragma once +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N C L U D E +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + #include "remmina/plugin.h" G_BEGIN_DECLS +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /** - * @brief Initializes the Python plugin loaders. - * @details This does not load any plugins but initializes globals and the Python engine itself. + * @brief Initializes the Python plugin loaders. + * @details This does not load any plugins but initializes the implementation (e.g. globals and the Python engine). */ void remmina_plugin_python_init(void); /** - * @brief Loads a plugin from the Remmina plugin folder with the given name. + * @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. diff --git a/src/remmina_plugin_python_common.c b/src/remmina_plugin_python_common.c new file mode 100644 index 000000000..527d96d6a --- /dev/null +++ b/src/remmina_plugin_python_common.c @@ -0,0 +1,312 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2014-2021 Antenore Gatta, Giovanni Panozzo, Mathias Winterhalter (ToolsDevler) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N C L U D E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "remmina_plugin_python_common.h" + +#include <assert.h> +#include <stdio.h> + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#include "pygobject.h" +#pragma GCC diagnostic pop + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// D E C L A R A T I O N S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * A cache to store the last result that has been returned by the Python code using CallPythonMethod + * (@see remmina_plugin_python_common.h) + */ +static PyObject* __last_result; +static GPtrArray* plugin_map = NULL; + +static RemminaPluginService* remmina_plugin_service; + +const char* ATTR_NAME = "name"; +const char* ATTR_ICON_NAME = "icon_name"; +const char* ATTR_DESCRIPTION = "description"; +const char* ATTR_VERSION = "version"; +const char* ATTR_ICON_NAME_SSH = "icon_name_ssh"; +const char* ATTR_FEATURES = "features"; +const char* ATTR_BASIC_SETTINGS = "basic_settings"; +const char* ATTR_ADVANCED_SETTINGS = "advanced_settings"; +const char* ATTR_SSH_SETTING = "ssh_setting"; +const char* ATTR_EXPORT_HINTS = "export_hints"; +const char* ATTR_PREF_LABEL = "pref_label"; +const char* ATTR_INIT_ORDER = "init_order"; + +/** + * To prevent some memory related attacks or accidental allocation of an excessive amount of byes, this limit should + * always be used to check for a sane amount of bytes to allocate. + */ +static const int REASONABLE_LIMIT_FOR_MALLOC = 1024 * 1024; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +PyObject* remmina_plugin_python_last_result(void) +{ + TRACE_CALL(__func__); + + return __last_result; +} + +PyObject* remmina_plugin_python_last_result_set(PyObject* last_result) +{ + TRACE_CALL(__func__); + + return __last_result = last_result; +} + +gboolean remmina_plugin_python_check_error(void) +{ + TRACE_CALL(__func__); + + if (PyErr_Occurred()) + { + PyErr_Print(); + return TRUE; + } + + return FALSE; +} + +void remmina_plugin_python_log_method_call(PyObject* instance, const char* method) +{ + TRACE_CALL(__func__); + + assert(instance); + assert(method); + g_print("Python@%ld: %s.%s(...) -> %s\n", + PyObject_Hash(instance), + instance->ob_type->tp_name, + method, + PyUnicode_AsUTF8(PyObject_Str(remmina_plugin_python_last_result()))); +} + +long remmina_plugin_python_get_attribute_long(PyObject* instance, const char* attr_name, long def) +{ + TRACE_CALL(__func__); + + assert(instance); + assert(attr_name); + PyObject* attr = PyObject_GetAttrString(instance, attr_name); + if (attr && PyLong_Check(attr)) + { + return PyLong_AsLong(attr); + } + + return def; +} + +gboolean remmina_plugin_python_check_attribute(PyObject* instance, const char* attr_name) +{ + TRACE_CALL(__func__); + + assert(instance); + assert(attr_name); + if (PyObject_HasAttrString(instance, attr_name)) + { + return TRUE; + } + + g_printerr("Python plugin instance is missing member: %s\n", attr_name); + return FALSE; +} + +void* remmina_plugin_python_malloc(int bytes) +{ + TRACE_CALL(__func__); + + assert(bytes > 0); + assert(bytes <= REASONABLE_LIMIT_FOR_MALLOC); + + void* result = malloc(bytes); + + if (!result) + { + g_printerr("Unable to allocate %d bytes in memory!\n", bytes); + perror("malloc"); + } + + return result; +} + +char* remmina_plugin_python_copy_string_from_python(PyObject* string, Py_ssize_t len) +{ + TRACE_CALL(__func__); + + char* result = NULL; + if (len <= 0 || string == NULL) + { + return NULL; + } + + const char* py_str = PyUnicode_AsUTF8(string); + if (py_str) + { + const int label_size = sizeof(char) * (len + 1); + result = (char*)remmina_plugin_python_malloc(label_size); + result[len] = '\0'; + memcpy(result, py_str, len); + } + + return result; +} + +void remmina_plugin_python_set_service(RemminaPluginService* service) +{ + remmina_plugin_service = service; +} + +RemminaPluginService* remmina_plugin_python_get_service(void) +{ + return remmina_plugin_service; +} + +void remmina_plugin_python_add_plugin(PyPlugin* plugin) +{ + TRACE_CALL(__func__); + + if (!plugin_map) + { + plugin_map = g_ptr_array_new(); + } + + PyPlugin* test = remmina_plugin_python_get_plugin(plugin->generic_plugin->name); + if (test) + { + g_printerr("A plugin named '%s' has already been registered! Skipping...", plugin->generic_plugin->name); + } + else + { + g_ptr_array_add(plugin_map, plugin); + } +} + +RemminaTypeHint remmina_plugin_python_to_generic(PyObject* field, gpointer* target) +{ + TRACE_CALL(__func__); + + if (PyUnicode_Check(field)) + { + Py_ssize_t len = PyUnicode_GetLength(field); + + if (len > 0) + { + *target = remmina_plugin_python_copy_string_from_python(field, len); + } + else + { + *target = ""; + } + + return REMMINA_TYPEHINT_STRING; + } + else if (PyBool_Check(field)) + { + *target = remmina_plugin_python_malloc(sizeof(long)); + long* long_target = (long*)target; + *long_target = PyLong_AsLong(field); + return REMMINA_TYPEHINT_BOOLEAN; + } + else if (PyLong_Check(field)) + { + *target = remmina_plugin_python_malloc(sizeof(long)); + long* long_target = (long*)target; + *long_target = PyLong_AsLong(field); + return REMMINA_TYPEHINT_SIGNED; + } + else if (PyTuple_Check(field)) + { + Py_ssize_t len = PyTuple_Size(field); + if (len) + { + gpointer* dest = (gpointer*)remmina_plugin_python_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); + remmina_plugin_python_to_generic(item, dest + i); + } + + *target = dest; + } + return REMMINA_TYPEHINT_TUPLE; + } + + *target = NULL; + return REMMINA_TYPEHINT_UNDEFINED; +} + +PyPlugin* remmina_plugin_python_get_plugin(const char* name) +{ + TRACE_CALL(__func__); + + assert(plugin_map); + assert(name); + + for (gint i = 0; i < plugin_map->len; ++i) + { + PyPlugin* plugin = (PyPlugin*)g_ptr_array_index(plugin_map, i); + if (plugin->generic_plugin && plugin->generic_plugin->name && g_str_equal(name, plugin->generic_plugin->name)) + { + return plugin; + } + } + + return NULL; +} + +void init_pygobject() +{ + pygobject_init(-1, -1, -1); +} + +GtkWidget* new_pywidget(GObject* obj) +{ + return (GtkWidget*)pygobject_new(obj); +} + +GtkWidget* get_pywidget(PyObject* obj) +{ + return (GtkWidget*)pygobject_get(obj); +} diff --git a/src/remmina_plugin_python_common.h b/src/remmina_plugin_python_common.h new file mode 100644 index 000000000..21557bb65 --- /dev/null +++ b/src/remmina_plugin_python_common.h @@ -0,0 +1,279 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2014-2021 Antenore Gatta, Giovanni Panozzo, Mathias Winterhalter (ToolsDevler) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +/** + * @file remmina_plugin_python_common.h + * + * @brief Contains functions and constants that are commonly used throughout the Python plugin implementation. + * + * @details These functions should not be used outside of the Python plugin implementation, since everything is intended + * to be used with the Python engine. + */ + +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N C L U D E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include <glib.h> +#include <gtk/gtk.h> + +#include <Python.h> +#include <glib.h> +#include <Python.h> +#include <structmember.h> + +#include "remmina/plugin.h" +#include "config.h" + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// D E C L A R A T I O N S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +G_BEGIN_DECLS + +// - Attribute names + +extern const char* ATTR_NAME; +extern const char* ATTR_ICON_NAME; +extern const char* ATTR_DESCRIPTION; +extern const char* ATTR_VERSION; +extern const char* ATTR_ICON_NAME_SSH; +extern const char* ATTR_FEATURES; +extern const char* ATTR_BASIC_SETTINGS; +extern const char* ATTR_ADVANCED_SETTINGS; +extern const char* ATTR_SSH_SETTING; +extern const char* ATTR_EXPORT_HINTS; +extern const char* ATTR_PREF_LABEL; +extern const char* ATTR_INIT_ORDER; + +// You can enable this for debuggin purposes or specify it in the build. +// #define WITH_PYTHON_TRACE_CALLS + +/** + * If WITH_PYTHON_TRACE_CALLS is defined, it logs the calls to the Python code and errors in case. + */ +#ifdef WITH_PYTHON_TRACE_CALLS +#define CallPythonMethod(instance, name, params, ...) \ + remmina_plugin_python_last_result_set(PyObject_CallMethod(instance, name, params, ##__VA_ARGS__)); \ + remmina_plugin_python_log_method_call(instance, name); \ + remmina_plugin_python_check_error() +#else +/** + * If WITH_TRACE_CALL is not defined, it still logs errors but doesn't print the call anymore. + */ +#define CallPythonMethod(instance, name, params, ...) \ + PyObject_CallMethod(instance, name, params, ##__VA_ARGS__); \ + remmina_plugin_python_check_error() +#endif // WITH_PYTHON_TRACE_CALLS + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// T Y P E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * @brief The Python abstraction of the protocol widget struct. + * + * @details This struct is responsible to provide the same accessibility to the protocol widget for Python as for + * native plugins. + */ +typedef struct +{ + PyObject_HEAD + RemminaProtocolWidget* gp; +} PyRemminaProtocolWidget; + +/** + * @brief Maps an instance of a Python plugin to a Remmina one. + * + * @details This is used to map a Python plugin instance to the Remmina plugin one. Also instance specific data as the + * protocol widget are stored in this struct. + */ +typedef struct +{ + RemminaProtocolPlugin* protocol_plugin; + RemminaFilePlugin* file_plugin; + RemminaSecretPlugin* secret_plugin; + RemminaToolPlugin* tool_plugin; + RemminaEntryPlugin* entry_plugin; + RemminaPrefPlugin* pref_plugin; + RemminaPlugin* generic_plugin; + PyRemminaProtocolWidget* gp; + PyObject* instance; +} PyPlugin; + +/** + * A struct used to communicate data between Python and C without strict data type. + */ +typedef struct +{ + PyObject_HEAD; + RemminaTypeHint type_hint; + gpointer raw; +} PyGeneric; + +/** + * Checks if self is set and returns NULL if not. + */ +#define SELF_CHECK() if (!self) { \ + g_printerr("[%s:%d]: self is null!\n", __FILE__, __LINE__); \ + PyErr_SetString(PyExc_RuntimeError, "Method is not called from an instance (self is null)!"); \ + return NULL; \ + } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Creates a new instance of PyGeneric. + */ +PyGeneric* remmina_plugin_python_generic_new(void); + +/** + * Registers the given plugin if no other plugin with the same name has been already registered. + */ +void remmina_plugin_python_add_plugin(PyPlugin* plugin); + +/** + * Sets the pointer to the plugin service of Remmina. + */ +void remmina_plugin_python_set_service(RemminaPluginService* service); + +/** + * Extracts data from a PyObject instance to a generic pointer and returns a type hint if it could be determined. + */ +RemminaTypeHint remmina_plugin_python_to_generic(PyObject* field, gpointer* target); + +/** + * Gets the result of the last python method call. + */ +PyObject* remmina_plugin_python_last_result(void); + +/** + * @brief Sets the result of the last python method call. + * + * @return Returns the passed result (it's done to be compatible with the CallPythonMethod macro). + */ +PyObject* remmina_plugin_python_last_result_set(PyObject* result); + +/** + * @brief Prints a log message to inform the user a python message has been called. + * + * @detail This method is called from the CALL_PYTHON macro if WITH_PYTHON_TRACE_CALLS is defined. + * + * @param instance The instance that contains the called method. + * @param method The name of the method called. + */ +void remmina_plugin_python_log_method_call(PyObject* instance, const char* method); + +/** + * @brief Checks if an error has occurred and prints it. + * + * @return Returns TRUE if an error has occurred. + */ +gboolean remmina_plugin_python_check_error(void); + +/** + * @brief Gets the attribute as long value. + * + * @param instance The instance of the object to get the attribute. + * @param constant_name The name of the attribute to get. + * @param def The value to return if the attribute doesn't exist or is not set. + * + * @return The value attribute as long. + */ +long remmina_plugin_python_get_attribute_long(PyObject* instance, const char* attr_name, long def); + +/** + * @brief Checks if a given attribute exists. + * + * @param instance The object to check for the attribute. + * @param attr_name The name of the attribute to check. + * + * @return Returns TRUE if the attribute exists. + */ +gboolean remmina_plugin_python_check_attribute(PyObject* instance, const char* attr_name); + +/** + * @brief Allocates memory and checks for errors before returning. + * + * @param bytes Amount of bytes to allocate. + * + * @return Address to the allocated memory. + */ +void* remmina_plugin_python_malloc(int bytes); + +/** + * @biref Copies a string from a Python object to a new point in memory. + * + * @param string The python object, containing the string to copy. + * @param len The length of the string to copy. + * + * @return A char pointer to the new copy of the string. + */ +char* remmina_plugin_python_copy_string_from_python(PyObject* string, Py_ssize_t len); + +/** + * @brief Tries to find the Python plugin matching to the given instance of RemminaPlugin. + * + * @param plugin_map An array of PyPlugin pointers to search. + * @param instance The RemminaPlugin instance to find the correct PyPlugin instance for. + * + * @return A pointer to a PyPlugin instance if successful. Otherwise NULL is returned. + */ +PyPlugin* remmina_plugin_python_get_plugin(const char* name); + +/** + * Creates a new GtkWidget + * @param obj + * @return + */ +GtkWidget* new_pywidget(GObject* obj); + +/** + * Extracts a GtkWidget from a PyObject instance. + * @param obj + * @return + */ +GtkWidget* get_pywidget(PyObject* obj); + +/** + * Initializes the pygobject library. This needs to be called before any Python plugin is being initialized. + */ +void init_pygobject(void); + +G_END_DECLS diff --git a/src/remmina_plugin_python_entry.c b/src/remmina_plugin_python_entry.c new file mode 100644 index 000000000..54a478b18 --- /dev/null +++ b/src/remmina_plugin_python_entry.c @@ -0,0 +1,101 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2014-2021 Antenore Gatta, Giovanni Panozzo, Mathias Winterhalter (ToolsDevler) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +/** + * @file remmina_plugin_python_entry.c + * @brief Contains the wiring of a Python pluing based on RemminaPluginProtocol. + * @author Mathias Winterhalter + * @date 07.04.2021 + */ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N C L U D E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "remmina_plugin_python_common.h" +#include "remmina_plugin_python_entry.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// D E C L A R A T I O N S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void remmina_plugin_python_entry_init(void) +{ + TRACE_CALL(__func__); +} + +void remmina_plugin_python_entry_entry_func_wrapper(RemminaEntryPlugin* instance) +{ + TRACE_CALL(__func__); + + PyPlugin* plugin = remmina_plugin_python_get_plugin(instance->name); + if (plugin) + { + CallPythonMethod(plugin->instance, "entry_func", NULL); + } +} + +RemminaPlugin* remmina_plugin_python_create_entry_plugin(PyPlugin* plugin) +{ + TRACE_CALL(__func__); + + PyObject* instance = plugin->instance; + + if (!remmina_plugin_python_check_attribute(instance, ATTR_NAME) + || !remmina_plugin_python_check_attribute(instance, ATTR_VERSION) + || !remmina_plugin_python_check_attribute(instance, ATTR_DESCRIPTION)) + { + g_printerr("Unable to create entry plugin. Aborting!\n"); + return NULL; + } + + RemminaEntryPlugin* remmina_plugin = (RemminaEntryPlugin*)remmina_plugin_python_malloc(sizeof(RemminaEntryPlugin)); + + remmina_plugin->type = REMMINA_PLUGIN_TYPE_ENTRY; + remmina_plugin->domain = GETTEXT_PACKAGE; + remmina_plugin->name = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_NAME)); + remmina_plugin->version = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_VERSION)); + remmina_plugin->description = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_DESCRIPTION)); + remmina_plugin->entry_func = remmina_plugin_python_entry_entry_func_wrapper; + + plugin->entry_plugin = remmina_plugin; + plugin->generic_plugin = (RemminaPlugin*)remmina_plugin; + + remmina_plugin_python_add_plugin(plugin); + + return (RemminaPlugin*)remmina_plugin; +} diff --git a/src/remmina_plugin_python_entry.h b/src/remmina_plugin_python_entry.h new file mode 100644 index 000000000..3b4c3ee38 --- /dev/null +++ b/src/remmina_plugin_python_entry.h @@ -0,0 +1,61 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2014-2021 Antenore Gatta, Giovanni Panozzo, Mathias Winterhalter (ToolsDevler) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +/** + * @file remmina_plugin_python_entry.h + * + * @brief Contains the specialisation of RemminaPluginEntry plugins in Python. + */ + +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +G_BEGIN_DECLS + +/** + * Initializes the Python plugin specialisation for entry plugins. + */ +void remmina_plugin_python_entry_init(void); + +/** + * @brief Creates a new instance of the RemminaPluginEntry, initializes its members and references the wrapper + * functions. + * @param instance The instance of the Python plugin. + * @return Returns a new instance of the RemminaPlugin (must be freed!). + */ +RemminaPlugin* remmina_plugin_python_create_entry_plugin(PyPlugin* instance); + +G_END_DECLS diff --git a/src/remmina_plugin_python_file.c b/src/remmina_plugin_python_file.c new file mode 100644 index 000000000..71a4dcd75 --- /dev/null +++ b/src/remmina_plugin_python_file.c @@ -0,0 +1,161 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2014-2021 Antenore Gatta, Giovanni Panozzo, Mathias Winterhalter (ToolsDevler) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N C L U D E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "remmina_plugin_python_common.h" +#include "remmina_plugin_python_file.h" +#include "remmina_plugin_python_remmina_file.h" +#include "remmina_file.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// D E C L A R A T I O N S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void remmina_plugin_python_file_init(void) +{ + TRACE_CALL(__func__); +} + +gboolean remmina_plugin_python_file_import_test_func_wrapper(RemminaFilePlugin* instance, const gchar* from_file) +{ + TRACE_CALL(__func__); + + PyObject* result = NULL; + + PyPlugin* plugin = remmina_plugin_python_get_plugin(instance->name); + + if (plugin) + { + result = CallPythonMethod(plugin->instance, "import_test_func", "s", from_file); + } + + return result == Py_None || result != Py_False; +} + +RemminaFile* remmina_plugin_python_file_import_func_wrapper(RemminaFilePlugin* instance, const gchar* from_file) +{ + TRACE_CALL(__func__); + + PyObject* result = NULL; + + PyPlugin* plugin = remmina_plugin_python_get_plugin(instance->name); + if (!plugin) + { + return NULL; + } + + result = CallPythonMethod(plugin->instance, "import_func", "s", from_file); + + if (result == Py_None || result == Py_False) + { + return NULL; + } + + return ((PyRemminaFile*)result)->file; +} + +gboolean remmina_plugin_python_file_export_test_func_wrapper(RemminaFilePlugin* instance, RemminaFile* file) +{ + TRACE_CALL(__func__); + + PyObject* result = NULL; + + PyPlugin* plugin = remmina_plugin_python_get_plugin(instance->name); + if (plugin) + { + result = CallPythonMethod(plugin->instance, + "export_test_func", + "O", + remmina_plugin_python_remmina_file_to_python(file)); + } + + return result == Py_None || result != Py_False; +} + +gboolean +remmina_plugin_python_file_export_func_wrapper(RemminaFilePlugin* instance, RemminaFile* file, const gchar* to_file) +{ + TRACE_CALL(__func__); + + PyObject* result = NULL; + + PyPlugin* plugin = remmina_plugin_python_get_plugin(instance->name); + if (plugin) + { + result = CallPythonMethod(plugin->instance, "export_func", "s", to_file); + } + + return result == Py_None || result != Py_False; +} + +RemminaPlugin* remmina_plugin_python_create_file_plugin(PyPlugin* plugin) +{ + TRACE_CALL(__func__); + + PyObject* instance = plugin->instance; + Py_IncRef(instance); + + if (!remmina_plugin_python_check_attribute(instance, ATTR_NAME)) + { + g_printerr("Unable to create file plugin. Aborting!\n"); + return NULL; + } + + RemminaFilePlugin* remmina_plugin = (RemminaFilePlugin*)remmina_plugin_python_malloc(sizeof(RemminaFilePlugin)); + + remmina_plugin->type = REMMINA_PLUGIN_TYPE_FILE; + remmina_plugin->domain = GETTEXT_PACKAGE; + remmina_plugin->name = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_NAME)); + remmina_plugin->version = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_VERSION)); + remmina_plugin->description = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_DESCRIPTION)); + remmina_plugin->export_hints = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_EXPORT_HINTS)); + + remmina_plugin->import_test_func = remmina_plugin_python_file_import_test_func_wrapper; + remmina_plugin->import_func = remmina_plugin_python_file_import_func_wrapper; + remmina_plugin->export_test_func = remmina_plugin_python_file_export_test_func_wrapper; + remmina_plugin->export_func = remmina_plugin_python_file_export_func_wrapper; + + plugin->file_plugin = remmina_plugin; + plugin->generic_plugin = (RemminaPlugin*)remmina_plugin; + + remmina_plugin_python_add_plugin(plugin); + + return (RemminaPlugin*)remmina_plugin; +} diff --git a/src/remmina_plugin_python_module.h b/src/remmina_plugin_python_file.h index ced1a83a0..b45326f7f 100644 --- a/src/remmina_plugin_python_module.h +++ b/src/remmina_plugin_python_file.h @@ -1,6 +1,6 @@ /* * Remmina - The GTK+ Remote Desktop Client - * Copyright (C) 2014-2022 Antenore Gatta, Giovanni Panozzo + * Copyright (C) 2014-2021 Antenore Gatta, Giovanni Panozzo, Mathias Winterhalter (ToolsDevler) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -29,14 +29,33 @@ * 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_file.h * + * @brief Contains the specialisation of RemminaPluginFile plugins in Python. */ #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); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +G_BEGIN_DECLS + +/** + * Initializes the Python plugin specialisation for file plugins. + */ +void remmina_plugin_python_file_init(void); + +/** + * @brief Creates a new instance of the RemminaPluginFile, initializes its members and references the wrapper + * functions. + * @param instance The instance of the Python plugin. + * @return Returns a new instance of the RemminaPlugin (must be freed!). + */ +RemminaPlugin* remmina_plugin_python_create_file_plugin(PyPlugin* instance); + +G_END_DECLS diff --git a/src/remmina_plugin_python_module.c b/src/remmina_plugin_python_module.c deleted file mode 100644 index d2daf7d10..000000000 --- a/src/remmina_plugin_python_module.c +++ /dev/null @@ -1,281 +0,0 @@ -/* - * Remmina - The GTK+ Remote Desktop Client - * Copyright (C) 2014-2022 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 "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_pref.c b/src/remmina_plugin_python_pref.c new file mode 100644 index 000000000..b799d105a --- /dev/null +++ b/src/remmina_plugin_python_pref.c @@ -0,0 +1,104 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2014-2021 Antenore Gatta, Giovanni Panozzo, Mathias Winterhalter (ToolsDevler) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N C L U D E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "remmina_plugin_python_common.h" +#include "remmina_plugin_python_pref.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// D E C L A R A T I O N S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void remmina_plugin_python_pref_init(void) +{ + TRACE_CALL(__func__); + +} + +/** + * @brief + */ +GtkWidget* remmina_plugin_python_pref_get_pref_body_wrapper(RemminaPrefPlugin* instance) +{ + TRACE_CALL(__func__); + + PyPlugin* plugin = remmina_plugin_python_get_plugin(instance->name); + + PyObject* result = CallPythonMethod(plugin->instance, "get_pref_body", NULL, NULL); + if (result == Py_None || result == NULL) + { + return NULL; + } + + return get_pywidget(result); +} + +RemminaPlugin* remmina_plugin_python_create_pref_plugin(PyPlugin* plugin) +{ + TRACE_CALL(__func__); + + PyObject* instance = plugin->instance; + + if (!remmina_plugin_python_check_attribute(instance, ATTR_NAME) + || !remmina_plugin_python_check_attribute(instance, ATTR_VERSION) + || !remmina_plugin_python_check_attribute(instance, ATTR_DESCRIPTION) + || !remmina_plugin_python_check_attribute(instance, ATTR_PREF_LABEL)) + { + g_printerr("Unable to create pref plugin. Aborting!\n"); + return NULL; + } + + RemminaPrefPlugin* remmina_plugin = (RemminaPrefPlugin*)remmina_plugin_python_malloc(sizeof(RemminaPrefPlugin)); + + remmina_plugin->type = REMMINA_PLUGIN_TYPE_PREF; + remmina_plugin->domain = GETTEXT_PACKAGE; + remmina_plugin->name = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_NAME)); + remmina_plugin->version = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_VERSION)); + remmina_plugin->description = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_DESCRIPTION)); + remmina_plugin->pref_label = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_PREF_LABEL)); + remmina_plugin->get_pref_body = remmina_plugin_python_pref_get_pref_body_wrapper; + + plugin->pref_plugin = remmina_plugin; + plugin->generic_plugin = (RemminaPlugin*)remmina_plugin; + + remmina_plugin_python_add_plugin(plugin); + + return (RemminaPlugin*)remmina_plugin; +} diff --git a/src/remmina_plugin_python_pref.h b/src/remmina_plugin_python_pref.h new file mode 100644 index 000000000..ede1357ab --- /dev/null +++ b/src/remmina_plugin_python_pref.h @@ -0,0 +1,61 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2014-2021 Antenore Gatta, Giovanni Panozzo, Mathias Winterhalter (ToolsDevler) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +/** + * @file remmina_plugin_python_pref.h + * + * @brief Contains the specialisation of RemminaPluginFile plugins in Python. + */ + +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +G_BEGIN_DECLS + +/** + * Initializes the Python plugin specialisation for preferences plugins. + */ +void remmina_plugin_python_pref_init(void); + +/** + * @brief Creates a new instance of the RemminaPluginPref, initializes its members and references the wrapper + * functions. + * @param instance The instance of the Python plugin. + * @return Returns a new instance of the RemminaPlugin (must be freed!). + */ +RemminaPlugin* remmina_plugin_python_create_pref_plugin(PyPlugin* instance); + +G_END_DECLS diff --git a/src/remmina_plugin_python_protocol.c b/src/remmina_plugin_python_protocol.c new file mode 100644 index 000000000..d01b2a657 --- /dev/null +++ b/src/remmina_plugin_python_protocol.c @@ -0,0 +1,317 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2014-2022 Antenore Gatta, Giovanni Panozzo, Mathias Winterhalter (ToolsDevler) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +/** + * @file remmina_plugin_python_common.c + * @brief + * @author Mathias Winterhalter + * @date 07.04.2021 + */ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N C L U D E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "remmina_plugin_python_common.h" +#include "remmina_plugin_python_protocol.h" +#include "remmina_plugin_python_remmina.h" + +#include "remmina_protocol_widget.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// D E C L A R A T I O N S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void remmina_plugin_python_protocol_init(void) +{ + TRACE_CALL(__func__); +} + +void remmina_protocol_init_wrapper(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + PyPlugin* py_plugin = remmina_plugin_python_get_plugin(gp->plugin->name); + py_plugin->gp->gp = gp; + CallPythonMethod(py_plugin->instance, "init", "O", py_plugin->gp); +} + +gboolean remmina_protocol_open_connection_wrapper(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + PyPlugin* py_plugin = remmina_plugin_python_get_plugin(gp->plugin->name); + if (py_plugin) + { + PyObject* result = CallPythonMethod(py_plugin->instance, "open_connection", "O", py_plugin->gp); + return result == Py_True; + } + else + { + return gtk_false(); + } +} + +gboolean remmina_protocol_close_connection_wrapper(RemminaProtocolWidget* gp) +{ + TRACE_CALL(__func__); + PyPlugin* py_plugin = remmina_plugin_python_get_plugin(gp->plugin->name); + PyObject* result = CallPythonMethod(py_plugin->instance, "close_connection", "O", py_plugin->gp); + return result == Py_True; +} + +gboolean remmina_protocol_query_feature_wrapper(RemminaProtocolWidget* gp, + const RemminaProtocolFeature* feature) +{ + TRACE_CALL(__func__); + PyPlugin* py_plugin = remmina_plugin_python_get_plugin(gp->plugin->name); + PyRemminaProtocolFeature* pyFeature = remmina_plugin_python_protocol_feature_new(); + pyFeature->type = (gint)feature->type; + pyFeature->id = feature->id; + pyFeature->opt1 = remmina_plugin_python_generic_new(); + pyFeature->opt1->raw = feature->opt1; + pyFeature->opt2 = remmina_plugin_python_generic_new(); + pyFeature->opt2->raw = feature->opt2; + pyFeature->opt3 = remmina_plugin_python_generic_new(); + pyFeature->opt3->raw = feature->opt3; + + PyObject* result = CallPythonMethod(py_plugin->instance, "query_feature", "OO", py_plugin->gp, pyFeature); + Py_DecRef((PyObject*)pyFeature); + Py_DecRef((PyObject*)pyFeature->opt1); + Py_DecRef((PyObject*)pyFeature->opt2); + Py_DecRef((PyObject*)pyFeature->opt3); + return result == Py_True; +} + +void remmina_protocol_call_feature_wrapper(RemminaProtocolWidget* gp, const RemminaProtocolFeature* feature) +{ + TRACE_CALL(__func__); + PyPlugin* py_plugin = remmina_plugin_python_get_plugin(gp->plugin->name); + PyRemminaProtocolFeature* pyFeature = remmina_plugin_python_protocol_feature_new(); + pyFeature->type = (gint)feature->type; + pyFeature->id = feature->id; + pyFeature->opt1 = remmina_plugin_python_generic_new(); + pyFeature->opt1->raw = feature->opt1; + pyFeature->opt1->type_hint = feature->opt1_type_hint; + pyFeature->opt2 = remmina_plugin_python_generic_new(); + pyFeature->opt2->raw = feature->opt2; + pyFeature->opt2->type_hint = feature->opt2_type_hint; + pyFeature->opt3 = remmina_plugin_python_generic_new(); + pyFeature->opt3->raw = feature->opt3; + pyFeature->opt3->type_hint = feature->opt3_type_hint; + + CallPythonMethod(py_plugin->instance, "call_feature", "OO", py_plugin->gp, pyFeature); + Py_DecRef((PyObject*)pyFeature); + Py_DecRef((PyObject*)pyFeature->opt1); + Py_DecRef((PyObject*)pyFeature->opt2); + Py_DecRef((PyObject*)pyFeature->opt3); +} + +void remmina_protocol_send_keytrokes_wrapper(RemminaProtocolWidget* gp, + const guint keystrokes[], + const gint keylen) +{ + TRACE_CALL(__func__); + PyPlugin* py_plugin = remmina_plugin_python_get_plugin(gp->plugin->name); + PyObject* obj = PyList_New(keylen); + Py_IncRef(obj); + for (int i = 0; i < keylen; ++i) + { + PyList_SetItem(obj, i, PyLong_FromLong(keystrokes[i])); + } + CallPythonMethod(py_plugin->instance, "send_keystrokes", "OO", py_plugin->gp, obj); + Py_DecRef(obj); +} + +gboolean remmina_protocol_get_plugin_screenshot_wrapper(RemminaProtocolWidget* gp, + RemminaPluginScreenshotData* rpsd) +{ + TRACE_CALL(__func__); + + PyPlugin* py_plugin = remmina_plugin_python_get_plugin(gp->plugin->name); + PyRemminaPluginScreenshotData* data = remmina_plugin_python_screenshot_data_new(); + Py_IncRef((PyObject*)data); + PyObject* result = CallPythonMethod(py_plugin->instance, "get_plugin_screenshot", "OO", py_plugin->gp, data); + if (result == Py_True) + { + if (!PyByteArray_Check((PyObject*)data->buffer)) + { + g_printerr("Unable to parse screenshot data. 'buffer' needs to be an byte array!"); + return 0; + } + Py_ssize_t buffer_len = PyByteArray_Size((PyObject*)data->buffer); + + // Is being freed by Remmina! + rpsd->buffer = (unsigned char*)remmina_plugin_python_malloc(sizeof(unsigned char) * buffer_len); + if (!rpsd->buffer) + { + return 0; + } + memcpy(rpsd->buffer, PyByteArray_AsString((PyObject*)data->buffer), sizeof(unsigned char) * buffer_len); + rpsd->bytesPerPixel = data->bytesPerPixel; + rpsd->bitsPerPixel = data->bitsPerPixel; + rpsd->height = data->height; + rpsd->width = data->width; + } + Py_DecRef((PyObject*)data->buffer); + Py_DecRef((PyObject*)data); + return result == Py_True; +} + +gboolean remmina_protocol_map_event_wrapper(RemminaProtocolWidget* gp) +{ + PyPlugin* plugin = remmina_plugin_python_get_plugin(gp->plugin->name); + PyObject* result = CallPythonMethod(plugin->instance, "map_event", "O", plugin->gp); + return PyBool_Check(result) && result == Py_True; +} + +gboolean remmina_protocol_unmap_event_wrapper(RemminaProtocolWidget* gp) +{ + PyPlugin* plugin = remmina_plugin_python_get_plugin(gp->plugin->name); + PyObject* result = CallPythonMethod(plugin->instance, "unmap_event", "O", plugin->gp); + return PyBool_Check(result) && result == Py_True; +} + +RemminaPlugin* remmina_plugin_python_create_protocol_plugin(PyPlugin* plugin) +{ + PyObject* instance = plugin->instance; + + if (!remmina_plugin_python_check_attribute(instance, ATTR_ICON_NAME_SSH) + || !remmina_plugin_python_check_attribute(instance, ATTR_ICON_NAME) + || !remmina_plugin_python_check_attribute(instance, ATTR_FEATURES) + || !remmina_plugin_python_check_attribute(instance, ATTR_BASIC_SETTINGS) + || !remmina_plugin_python_check_attribute(instance, ATTR_ADVANCED_SETTINGS) + || !remmina_plugin_python_check_attribute(instance, ATTR_SSH_SETTING)) + { + g_printerr("Unable to create protocol plugin. Aborting!\n"); + return NULL; + } + + RemminaProtocolPlugin* remmina_plugin = (RemminaProtocolPlugin*)remmina_plugin_python_malloc(sizeof(RemminaProtocolPlugin)); + + remmina_plugin->type = REMMINA_PLUGIN_TYPE_PROTOCOL; + remmina_plugin->domain = GETTEXT_PACKAGE; + remmina_plugin->basic_settings = NULL; + remmina_plugin->advanced_settings = NULL; + remmina_plugin->features = NULL; + + remmina_plugin->name = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_NAME)); + remmina_plugin->description = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_DESCRIPTION)); + remmina_plugin->version = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_VERSION)); + remmina_plugin->icon_name = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_ICON_NAME)); + remmina_plugin->icon_name_ssh = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_ICON_NAME_SSH)); + + PyObject* list = PyObject_GetAttrString(instance, "basic_settings"); + Py_ssize_t len = PyList_Size(list); + if (len) + { + RemminaProtocolSetting* basic_settings = (RemminaProtocolSetting*)remmina_plugin_python_malloc( + sizeof(RemminaProtocolSetting) * (len + 1)); + memset(basic_settings, 0, sizeof(RemminaProtocolSetting) * (len + 1)); + + for (Py_ssize_t i = 0; i < len; ++i) + { + RemminaProtocolSetting* dest = basic_settings + i; + remmina_plugin_python_to_protocol_setting(dest, PyList_GetItem(list, i)); + } + RemminaProtocolSetting* dest = basic_settings + len; + dest->type = REMMINA_PROTOCOL_SETTING_TYPE_END; + remmina_plugin->basic_settings = basic_settings; + } + + list = PyObject_GetAttrString(instance, "advanced_settings"); + len = PyList_Size(list); + if (len) + { + RemminaProtocolSetting* advanced_settings = (RemminaProtocolSetting*)remmina_plugin_python_malloc( + sizeof(RemminaProtocolSetting) * (len + 1)); + memset(advanced_settings, 0, sizeof(RemminaProtocolSetting) * (len + 1)); + + for (Py_ssize_t i = 0; i < len; ++i) + { + RemminaProtocolSetting* dest = advanced_settings + i; + remmina_plugin_python_to_protocol_setting(dest, PyList_GetItem(list, i)); + } + + RemminaProtocolSetting* dest = advanced_settings + len; + dest->type = REMMINA_PROTOCOL_SETTING_TYPE_END; + + remmina_plugin->advanced_settings = advanced_settings; + } + + list = PyObject_GetAttrString(instance, "features"); + len = PyList_Size(list); + if (len) + { + RemminaProtocolFeature* features = (RemminaProtocolFeature*)remmina_plugin_python_malloc( + sizeof(RemminaProtocolFeature) * (len + 1)); + memset(features, 0, sizeof(RemminaProtocolFeature) * (len + 1)); + + for (Py_ssize_t i = 0; i < len; ++i) + { + RemminaProtocolFeature* dest = features + i; + remmina_plugin_python_to_protocol_feature(dest, PyList_GetItem(list, i)); + } + + RemminaProtocolFeature* dest = features + len; + dest->type = REMMINA_PROTOCOL_FEATURE_TYPE_END; + + remmina_plugin->features = features; + } + + remmina_plugin->ssh_setting = (RemminaProtocolSSHSetting)remmina_plugin_python_get_attribute_long(instance, + ATTR_SSH_SETTING, + REMMINA_PROTOCOL_SSH_SETTING_NONE); + + remmina_plugin->init = remmina_protocol_init_wrapper; // Plugin initialization + remmina_plugin->open_connection = remmina_protocol_open_connection_wrapper; // Plugin open connection + remmina_plugin->close_connection = remmina_protocol_close_connection_wrapper; // Plugin close connection + remmina_plugin->query_feature = remmina_protocol_query_feature_wrapper; // Query for available features + remmina_plugin->call_feature = remmina_protocol_call_feature_wrapper; // Call a feature + remmina_plugin->send_keystrokes = + remmina_protocol_send_keytrokes_wrapper; // Send a keystroke + remmina_plugin->get_plugin_screenshot = + remmina_protocol_get_plugin_screenshot_wrapper; // Screenshot support unavailable + + remmina_plugin->map_event = remmina_protocol_map_event_wrapper; + remmina_plugin->unmap_event = remmina_protocol_unmap_event_wrapper; + + plugin->protocol_plugin = remmina_plugin; + plugin->generic_plugin = (RemminaPlugin*)remmina_plugin; + + remmina_plugin_python_add_plugin(plugin); + + return (RemminaPlugin*)remmina_plugin; +}
\ No newline at end of file diff --git a/src/remmina_plugin_python_protocol.h b/src/remmina_plugin_python_protocol.h new file mode 100644 index 000000000..42724dcc6 --- /dev/null +++ b/src/remmina_plugin_python_protocol.h @@ -0,0 +1,107 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2014-2022 Antenore Gatta, Giovanni Panozzo, Mathias Winterhalter (ToolsDevler) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +/** + * @file remmina_plugin_python_protocol.h + * + * @brief Contains the specialisation of RemminaPluginFile plugins in Python. + */ + +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N C L U D E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "remmina/plugin.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +G_BEGIN_DECLS + +/** + * Wrapper for a Python object that contains a pointer to an instance of RemminaProtocolFeature. + */ +typedef struct +{ + PyObject_HEAD + RemminaProtocolFeatureType type; + gint id; + PyGeneric* opt1; + PyGeneric* opt2; + PyGeneric* opt3; +} PyRemminaProtocolFeature; + +/** + * + */ +typedef struct +{ + PyObject_HEAD + PyByteArrayObject* buffer; + int bitsPerPixel; + int bytesPerPixel; + int width; + int height; +} PyRemminaPluginScreenshotData; + +/** + * Initializes the Python plugin specialisation for protocol plugins. + */ +void remmina_plugin_python_protocol_init(void); + +/** + * @brief Creates a new instance of the RemminaPluginProtocol, initializes its members and references the wrapper + * functions. + * + * @param instance The instance of the Python plugin. + * + * @return Returns a new instance of the RemminaPlugin (must be freed!). + */ +RemminaPlugin* remmina_plugin_python_create_protocol_plugin(PyPlugin* plugin); + +/** + * + * @return + */ +PyRemminaProtocolFeature* remmina_plugin_python_protocol_feature_new(void); + +/** + * + * @return + */ +PyRemminaPluginScreenshotData* remmina_plugin_python_screenshot_data_new(void); + +G_END_DECLS diff --git a/src/remmina_plugin_python_protocol_widget.c b/src/remmina_plugin_python_protocol_widget.c index 7ae208883..eceb22422 100644 --- a/src/remmina_plugin_python_protocol_widget.c +++ b/src/remmina_plugin_python_protocol_widget.c @@ -29,7 +29,6 @@ * 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. - * */ /** @@ -83,49 +82,47 @@ * @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> +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N L U C E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#include "config.h" -#include "pygobject.h" -#include "remmina_plugin_manager.h" +#include "remmina_plugin_python_common.h" #include "remmina/plugin.h" #include "remmina_protocol_widget.h" -#include "remmina_file.h" -#include "remmina_plugin_python_remmina.h" +#include "remmina/types.h" #include "remmina_plugin_python_remmina_file.h" - #include "remmina_plugin_python_protocol_widget.h" +#include "remmina_plugin_python_protocol.h" // -- Python Type -> RemminaWidget +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// D E C L A R A T I O N S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + static PyObject* protocol_widget_get_viewport(PyRemminaProtocolWidget* self, PyObject* args); static PyObject* protocol_widget_get_width(PyRemminaProtocolWidget* self, PyObject* args); -static PyObject* protocol_widget_set_width(PyRemminaProtocolWidget* self, PyObject* args); +static PyObject* protocol_widget_set_width(PyRemminaProtocolWidget* self, PyObject* var_width); static PyObject* protocol_widget_get_height(PyRemminaProtocolWidget* self, PyObject* args); -static PyObject* protocol_widget_set_height(PyRemminaProtocolWidget* self, PyObject* args); +static PyObject* protocol_widget_set_height(PyRemminaProtocolWidget* self, PyObject* var_height); static PyObject* protocol_widget_get_current_scale_mode(PyRemminaProtocolWidget* self, PyObject* args); static PyObject* protocol_widget_get_expand(PyRemminaProtocolWidget* self, PyObject* args); -static PyObject* protocol_widget_set_expand(PyRemminaProtocolWidget* self, PyObject* args); +static PyObject* protocol_widget_set_expand(PyRemminaProtocolWidget* self, PyObject* var_expand); static PyObject* protocol_widget_has_error(PyRemminaProtocolWidget* self, PyObject* args); -static PyObject* protocol_widget_set_error(PyRemminaProtocolWidget* self, PyObject* args); +static PyObject* protocol_widget_set_error(PyRemminaProtocolWidget* self, PyObject* var_msg); static PyObject* protocol_widget_is_closed(PyRemminaProtocolWidget* self, PyObject* args); static PyObject* protocol_widget_get_file(PyRemminaProtocolWidget* self, PyObject* args); -static PyObject* protocol_widget_emit_signal(PyRemminaProtocolWidget* self, PyObject* args); -static PyObject* protocol_widget_register_hostkey(PyRemminaProtocolWidget* self, PyObject* args); +static PyObject* protocol_widget_emit_signal(PyRemminaProtocolWidget* self, PyObject* var_signal); +static PyObject* protocol_widget_register_hostkey(PyRemminaProtocolWidget* self, PyObject* var_widget); static PyObject* protocol_widget_start_direct_tunnel(PyRemminaProtocolWidget* self, PyObject* args); -static PyObject* protocol_widget_start_reverse_tunnel(PyRemminaProtocolWidget* self, PyObject* args); +static PyObject* protocol_widget_start_reverse_tunnel(PyRemminaProtocolWidget* self, PyObject* var_local_port); static PyObject* protocol_widget_start_xport_tunnel(PyRemminaProtocolWidget* self, PyObject* args); -static PyObject* protocol_widget_set_display(PyRemminaProtocolWidget* self, PyObject* args); +static PyObject* protocol_widget_set_display(PyRemminaProtocolWidget* self, PyObject* var_display); static PyObject* protocol_widget_signal_connection_closed(PyRemminaProtocolWidget* self, PyObject* args); static PyObject* protocol_widget_signal_connection_opened(PyRemminaProtocolWidget* self, PyObject* args); static PyObject* protocol_widget_update_align(PyRemminaProtocolWidget* self, PyObject* args); static PyObject* protocol_widget_unlock_dynres(PyRemminaProtocolWidget* self, PyObject* args); static PyObject* protocol_widget_desktop_resize(PyRemminaProtocolWidget* self, PyObject* args); -static PyObject* protocol_widget_panel_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); @@ -143,90 +140,126 @@ static PyObject* protocol_widget_panel_show_retry(PyRemminaProtocolWidget* self, 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_open(PyRemminaProtocolWidget* self, PyObject* var_name); static PyObject* protocol_widget_chat_close(PyRemminaProtocolWidget* self, PyObject* args); static PyObject* protocol_widget_chat_receive(PyRemminaProtocolWidget* self, PyObject* args); static PyObject* protocol_widget_send_keys_signals(PyRemminaProtocolWidget* self, PyObject* args); -static 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 struct PyMethodDef python_protocol_widget_type_methods[] = + {{ "get_viewport", (PyCFunction)protocol_widget_get_viewport, METH_NOARGS, "" }, + { "get_width", (PyCFunction)protocol_widget_get_width, METH_NOARGS, "" }, + { "set_width", (PyCFunction)protocol_widget_set_width, METH_VARARGS, "" }, + { "get_height", (PyCFunction)protocol_widget_get_height, METH_VARARGS, "" }, + { "set_height", (PyCFunction)protocol_widget_set_height, METH_VARARGS, "" }, + { "get_current_scale_mode", (PyCFunction)protocol_widget_get_current_scale_mode, METH_VARARGS, "" }, + { "get_expand", (PyCFunction)protocol_widget_get_expand, METH_VARARGS, "" }, + { "set_expand", (PyCFunction)protocol_widget_set_expand, METH_VARARGS, "" }, + { "has_error", (PyCFunction)protocol_widget_has_error, METH_VARARGS, "" }, + { "set_error", (PyCFunction)protocol_widget_set_error, METH_VARARGS, "" }, + { "is_closed", (PyCFunction)protocol_widget_is_closed, METH_VARARGS, "" }, + { "get_file", (PyCFunction)protocol_widget_get_file, METH_NOARGS, "" }, + { "emit_signal", (PyCFunction)protocol_widget_emit_signal, METH_VARARGS, "" }, + { "register_hostkey", (PyCFunction)protocol_widget_register_hostkey, METH_VARARGS, "" }, + { "start_direct_tunnel", (PyCFunction)protocol_widget_start_direct_tunnel, METH_VARARGS | METH_KEYWORDS, "" }, + { "start_reverse_tunnel", (PyCFunction)protocol_widget_start_reverse_tunnel, METH_VARARGS, "" }, + { "start_xport_tunnel", (PyCFunction)protocol_widget_start_xport_tunnel, METH_VARARGS, "" }, + { "set_display", (PyCFunction)protocol_widget_set_display, METH_VARARGS, "" }, + { "signal_connection_closed", (PyCFunction)protocol_widget_signal_connection_closed, METH_VARARGS, "" }, + { "signal_connection_opened", (PyCFunction)protocol_widget_signal_connection_opened, METH_VARARGS, "" }, + { "update_align", (PyCFunction)protocol_widget_update_align, METH_VARARGS, "" }, + { "unlock_dynres", (PyCFunction)protocol_widget_unlock_dynres, METH_VARARGS, "" }, + { "desktop_resize", (PyCFunction)protocol_widget_desktop_resize, METH_VARARGS, "" }, + { "panel_new_certificate", (PyCFunction)protocol_widget_panel_new_certificate, METH_VARARGS | METH_KEYWORDS, "" }, + { "panel_changed_certificate", (PyCFunction)protocol_widget_panel_changed_certificate, + METH_VARARGS | METH_KEYWORDS, "" }, + { "get_username", (PyCFunction)protocol_widget_get_username, METH_VARARGS, "" }, + { "get_password", (PyCFunction)protocol_widget_get_password, METH_VARARGS, "" }, + { "get_domain", (PyCFunction)protocol_widget_get_domain, METH_VARARGS, "" }, + { "get_savepassword", (PyCFunction)protocol_widget_get_savepassword, METH_VARARGS, "" }, + { "panel_authx509", (PyCFunction)protocol_widget_panel_authx509, METH_VARARGS, "" }, + { "get_cacert", (PyCFunction)protocol_widget_get_cacert, METH_VARARGS, "" }, + { "get_cacrl", (PyCFunction)protocol_widget_get_cacrl, METH_VARARGS, "" }, + { "get_clientcert", (PyCFunction)protocol_widget_get_clientcert, METH_VARARGS, "" }, + { "get_clientkey", (PyCFunction)protocol_widget_get_clientkey, METH_VARARGS, "" }, + { "save_cred", (PyCFunction)protocol_widget_save_cred, METH_VARARGS, "" }, + { "panel_show_listen", (PyCFunction)protocol_widget_panel_show_listen, METH_VARARGS, "" }, + { "panel_show_retry", (PyCFunction)protocol_widget_panel_show_retry, METH_VARARGS, "" }, + { "panel_show", (PyCFunction)protocol_widget_panel_show, METH_VARARGS, "" }, + { "panel_hide", (PyCFunction)protocol_widget_panel_hide, METH_VARARGS, "" }, + { "ssh_exec", (PyCFunction)protocol_widget_ssh_exec, METH_VARARGS | METH_KEYWORDS, "" }, + { "chat_open", (PyCFunction)protocol_widget_chat_open, METH_VARARGS, "" }, + { "chat_close", (PyCFunction)protocol_widget_chat_close, METH_VARARGS, "" }, + { "chat_receive", (PyCFunction)protocol_widget_chat_receive, METH_VARARGS, "" }, + { "send_keys_signals", (PyCFunction)protocol_widget_send_keys_signals, METH_VARARGS | METH_KEYWORDS, "" }, + { NULL }}; + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +static PyObject* python_protocol_feature_new(PyTypeObject* type, PyObject* kws, PyObject* args) +{ + TRACE_CALL(__func__); + PyRemminaProtocolWidget* self; + self = (PyRemminaProtocolWidget*)type->tp_alloc(type, 0); + if (!self) + { + return NULL; + } + + return (PyObject*)self; +} + +static int python_protocol_feature_init(PyObject* self, PyObject* args, PyObject* kwds) +{ + return 0; +} static PyTypeObject python_protocol_widget_type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "remmina.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 + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "remmina.RemminaProtocolWidget", + .tp_doc = "RemminaProtocolWidget", + .tp_basicsize = sizeof(PyRemminaProtocolWidget), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = python_protocol_feature_new, + .tp_init = python_protocol_feature_init, + .tp_methods = python_protocol_widget_type_methods }; -typedef struct { - PyObject_HEAD - PyDictObject* settings; - PyDictObject* spsettings; -} PyRemminaFile; +PyRemminaProtocolWidget* remmina_plugin_python_protocol_widget_create(void) +{ + TRACE_CALL(__func__); + + PyRemminaProtocolWidget* result = PyObject_NEW(PyRemminaProtocolWidget, &python_protocol_widget_type); + assert(result); -#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; \ - } + PyErr_Print(); + Py_INCREF(result); + result->gp = NULL; + return result; +} -void remmina_plugin_python_protocol_widget_init(void) { - pygobject_init(-1, -1, -1); +void remmina_plugin_python_protocol_widget_init(void) +{ + init_pygobject(); +} + +void remmina_plugin_python_protocol_widget_type_ready(void) +{ + TRACE_CALL(__func__); + if (PyType_Ready(&python_protocol_widget_type) < 0) + { + g_printerr("Error initializing remmina.RemminaWidget!\n"); + PyErr_Print(); + } } static PyObject* protocol_widget_get_viewport(PyRemminaProtocolWidget* self, PyObject* args) { TRACE_CALL(__func__); - return pygobject_new(G_OBJECT(remmina_protocol_widget_gtkviewport(self->gp))); + SELF_CHECK(); + return (PyObject*)new_pywidget(G_OBJECT(remmina_protocol_widget_gtkviewport(self->gp))); } static PyObject* protocol_widget_get_width(PyRemminaProtocolWidget* self, PyObject* args) @@ -242,14 +275,21 @@ static PyObject* protocol_widget_set_width(PyRemminaProtocolWidget* self, PyObje 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(); + if (!var_width) + { + g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__); + return NULL; + } + + if (PyLong_Check(var_width)) + { + g_printerr("[%s:%d@%s]: Argument is not of type Long!\n", __FILE__, __LINE__, __func__); + return NULL; } + gint width = (gint)PyLong_AsLong(var_width); + remmina_protocol_widget_set_height(self->gp, width); + return Py_None; } @@ -266,14 +306,21 @@ static PyObject* protocol_widget_set_height(PyRemminaProtocolWidget* self, PyObj 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(); + if (!var_height) + { + g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__); + return NULL; } + if (PyLong_Check(var_height)) + { + g_printerr("[%s:%d@%s]: Argument is not of type Long!\n", __FILE__, __LINE__, __func__); + return NULL; + } + + gint height = (gint)PyLong_AsLong(var_height); + remmina_protocol_widget_set_height(self->gp, height); + return Py_None; } @@ -298,12 +345,20 @@ static PyObject* protocol_widget_set_expand(PyRemminaProtocolWidget* self, PyObj 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(); + if (!var_expand) + { + g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__); + return NULL; + } + + if (PyBool_Check(var_expand)) + { + g_printerr("[%s:%d@%s]: Argument is not of type Boolean!\n", __FILE__, __LINE__, __func__); + return NULL; } + + remmina_protocol_widget_set_expand(self->gp, PyObject_IsTrue(var_expand)); + return Py_None; } @@ -320,13 +375,21 @@ static PyObject* protocol_widget_set_error(PyRemminaProtocolWidget* self, PyObje 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(); + if (!var_msg) + { + g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__); + return NULL; + } + + if (PyUnicode_Check(var_msg)) + { + g_printerr("[%s:%d@%s]: Argument is not of type String!\n", __FILE__, __LINE__, __func__); + return NULL; } + + const gchar* msg = PyUnicode_AsUTF8(var_msg); + remmina_protocol_widget_set_error(self->gp, msg); + return Py_None; } @@ -344,7 +407,7 @@ static PyObject* protocol_widget_get_file(PyRemminaProtocolWidget* self, PyObjec SELF_CHECK(); RemminaFile* file = remmina_protocol_widget_get_file(self->gp); - return remmina_plugin_python_remmina_file_to_python(file); + return (PyObject*)remmina_plugin_python_remmina_file_to_python(file); } static PyObject* protocol_widget_emit_signal(PyRemminaProtocolWidget* self, PyObject* var_signal) @@ -352,12 +415,20 @@ static PyObject* protocol_widget_emit_signal(PyRemminaProtocolWidget* self, PyOb 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(); + if (!var_signal) + { + g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__); + return NULL; + } + + if (PyUnicode_Check(var_signal)) + { + g_printerr("[%s:%d@%s]: Argument is not of type String!\n", __FILE__, __LINE__, __func__); + return NULL; } + + remmina_protocol_widget_set_error(self->gp, PyUnicode_AsUTF8(var_signal)); + return Py_None; } @@ -366,12 +437,14 @@ static PyObject* protocol_widget_register_hostkey(PyRemminaProtocolWidget* self, 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(); + if (!var_widget) + { + g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__); + return NULL; } + + remmina_protocol_widget_register_hostkey(self->gp, get_pywidget(var_widget)); + return Py_None; } @@ -383,11 +456,19 @@ static PyObject* protocol_widget_start_direct_tunnel(PyRemminaProtocolWidget* se gint default_port; gboolean port_plus; - if (args && PyArg_ParseTuple(args, "ii", &default_port, &port_plus)) { + if (!args) + { + g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__); + } + + if (PyArg_ParseTuple(args, "ii", &default_port, &port_plus)) + { return Py_BuildValue("s", 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"); + } + else + { PyErr_Print(); + return NULL; } return Py_None; } @@ -397,19 +478,26 @@ static PyObject* protocol_widget_start_reverse_tunnel(PyRemminaProtocolWidget* s 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(); + if (!PyLong_Check(var_local_port)) + { + g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__); + return NULL; } - return Py_None; + + if (!PyLong_Check(var_local_port)) + { + g_printerr("[%s:%d@%s]: Argument is not of type Long!\n", __FILE__, __LINE__, __func__); + return NULL; + } + + return Py_BuildValue("p", remmina_protocol_widget_start_reverse_tunnel(self->gp, (gint)PyLong_AsLong(var_local_port))); } -static gboolean xport_tunnel_init(RemminaProtocolWidget *gp, gint remotedisplay, const gchar *server, gint port) +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); + TRACE_CALL(__func__); + PyPlugin* plugin = remmina_plugin_python_get_plugin(gp->plugin->name); + PyObject* result = PyObject_CallMethod(plugin->instance, "xport_tunnel_init", "Oisi", gp, remotedisplay, server, port); return PyObject_IsTrue(result); } @@ -426,12 +514,20 @@ static PyObject* protocol_widget_set_display(PyRemminaProtocolWidget* self, PyOb 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(); + if (!var_display) + { + g_printerr("[%s:%d@%s]: Argument is null!\n", __FILE__, __LINE__, __func__); + return NULL; + } + + if (!PyLong_Check(var_display)) + { + g_printerr("[%s:%d@%s]: Argument is not of type Long!\n", __FILE__, __LINE__, __func__); + return NULL; } + + remmina_protocol_widget_set_display(self->gp, (gint)PyLong_AsLong(var_display)); + return Py_None; } @@ -480,42 +576,20 @@ static PyObject* protocol_widget_desktop_resize(PyRemminaProtocolWidget* self, P 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; + gchar* subject, * issuer, * fingerprint; - if (PyArg_ParseTuple(args, "sss", &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"); + } + else + { PyErr_Print(); + return NULL; } return Py_None; } @@ -524,13 +598,16 @@ static PyObject* protocol_widget_panel_changed_certificate(PyRemminaProtocolWidg { TRACE_CALL(__func__); SELF_CHECK(); - gchar* subject, issuer, new_fingerprint, old_fingerprint; + gchar* subject, * issuer, * new_fingerprint, * old_fingerprint; - if (PyArg_ParseTuple(args, "sss", &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"); + } + else + { PyErr_Print(); + return NULL; } return Py_None; } @@ -622,11 +699,14 @@ static PyObject* protocol_widget_panel_show_listen(PyRemminaProtocolWidget* self SELF_CHECK(); gint port = 0; - if (PyArg_ParseTuple(args, "i", &port)) { + 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"); + } + else + { PyErr_Print(); + return NULL; } return Py_None; } @@ -665,27 +745,28 @@ static PyObject* protocol_widget_ssh_exec(PyRemminaProtocolWidget* self, PyObjec gboolean wait; gchar* cmd; - if (PyArg_ParseTuple(args, "ps", &wait, &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"); + } + else + { PyErr_Print(); + return NULL; } return Py_None; } -static gboolean _on_send_callback_wrapper(RemminaProtocolWidget *gp, const gchar *text) +static void _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); + PyPlugin* plugin = remmina_plugin_python_get_plugin(gp->plugin->name); + PyObject_CallMethod(plugin->instance, "on_send", "Os", gp, text); } -static gboolean _on_destroy_callback_wrapper(RemminaProtocolWidget *gp) +static void _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); + PyPlugin* plugin = remmina_plugin_python_get_plugin(gp->plugin->name); + PyObject_CallMethod(plugin->instance, "on_destroy", "O", gp); } static PyObject* protocol_widget_chat_open(PyRemminaProtocolWidget* self, PyObject* var_name) @@ -693,13 +774,16 @@ static PyObject* protocol_widget_chat_open(PyRemminaProtocolWidget* self, PyObje 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(); + if (!PyUnicode_Check(var_name)) + { + g_printerr("[%s:%d@%s]: Argument is not of type String!\n", __FILE__, __LINE__, __func__); } + remmina_protocol_widget_chat_open(self->gp, + PyUnicode_AsUTF8(var_name), + _on_send_callback_wrapper, + _on_destroy_callback_wrapper); + return Py_None; } @@ -718,11 +802,14 @@ static PyObject* protocol_widget_chat_receive(PyRemminaProtocolWidget* self, PyO SELF_CHECK(); gchar* text; - if (PyArg_ParseTuple(args, "s", &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"); + } + else + { PyErr_Print(); + return NULL; } return Py_None; @@ -732,22 +819,28 @@ static PyObject* protocol_widget_send_keys_signals(PyRemminaProtocolWidget* self { TRACE_CALL(__func__); SELF_CHECK(); - gchar* keyvals; + guint* 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); + PyObject* widget; + + if (PyArg_ParseTuple(args, "Osii", &widget, &keyvals, &length, &event_type) && widget && keyvals) + { + if (event_type < GDK_NOTHING || event_type >= GDK_EVENT_LAST) + { + g_printerr("[%s:%d@%s]: %d is not a known value for GdkEventType!\n", __FILE__, __LINE__, __func__, event_type); + return NULL; + } + else + { + remmina_protocol_widget_send_keys_signals((GtkWidget*)widget, keyvals, length, event_type); } - } else { - g_printerr("send_keys_signals(keyvals): Error parsing arguments!\n"); + } + else + { PyErr_Print(); + return NULL; } return Py_None; } - diff --git a/src/remmina_plugin_python_protocol_widget.h b/src/remmina_plugin_python_protocol_widget.h index 93bd38fbb..dcbbc8093 100644 --- a/src/remmina_plugin_python_protocol_widget.h +++ b/src/remmina_plugin_python_protocol_widget.h @@ -29,10 +29,35 @@ * 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.h * + * @brief Contains the implementation of the widget handling used from the protocol plugin. */ #pragma once +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +G_BEGIN_DECLS + +/** + * Initializes the widget backend of the protocol plugin implementation. + */ void remmina_plugin_python_protocol_widget_init(void); +/** + * Initializes Python types used for protocol widgets. + */ +void remmina_plugin_python_protocol_widget_type_ready(void); + +/** + * Creates a new instance of PyRemminaProtocolWidget and initializes its fields. + */ +PyRemminaProtocolWidget* remmina_plugin_python_protocol_widget_create(); + +G_END_DECLS diff --git a/src/remmina_plugin_python_remmina.c b/src/remmina_plugin_python_remmina.c index 453421605..ad0ebc7a4 100644 --- a/src/remmina_plugin_python_remmina.c +++ b/src/remmina_plugin_python_remmina.c @@ -29,280 +29,213 @@ * 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. - * */ -#include <glib.h> -#include <gtk/gtk.h> -#define PY_SSIZE_T_CLEAN -#include <Python.h> -#include <structmember.h> +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N C L U D E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -#include "config.h" +#include "remmina_plugin_python_common.h" +#include "remmina_main.h" #include "remmina_plugin_manager.h" #include "remmina/plugin.h" +#include "remmina/types.h" #include "remmina_protocol_widget.h" +#include "remmina_log.h" #include "remmina_plugin_python_remmina.h" +#include "remmina_plugin_python_protocol_widget.h" -/** - * @brief Holds pairs of Python and Remmina plugin instances (PyPlugin). - */ -GPtrArray *remmina_plugin_registry = NULL; +#include "remmina_plugin_python_entry.h" +#include "remmina_plugin_python_file.h" +#include "remmina_plugin_python_protocol.h" +#include "remmina_plugin_python_tool.h" +#include "remmina_plugin_python_secret.h" +#include "remmina_plugin_python_pref.h" -/** - * - */ -gboolean remmina_plugin_python_check_mandatory_member(PyObject* instance, const gchar* member); +#include "remmina_pref.h" +#include "remmina_ssh.h" +#include "remmina_file_manager.h" +#include "remmina_widget_pool.h" +#include "remmina_public.h" +#include "remmina_masterthread_exec.h" -/** - * @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); +#include "rcw.h" +#include "remmina_plugin_python_remmina_file.h" -/** - * @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); +#include <string.h> -/** - * @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); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// D E C L A R A T I O N S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -/** - * @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 + * Util function to check if a specific member is define in a Python object. */ -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); +gboolean remmina_plugin_python_check_mandatory_member(PyObject* instance, const gchar* member); -/** - * @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_plugin_python_debug_wrapper(PyObject* self, PyObject* msg); +static PyObject* remmina_register_plugin_wrapper(PyObject* self, PyObject* plugin); +static PyObject* remmina_file_get_datadir_wrapper(PyObject* self, PyObject* plugin); +static PyObject* remmina_file_new_wrapper(PyObject* self, PyObject* args, PyObject* kwargs); +static PyObject* remmina_pref_set_value_wrapper(PyObject* self, PyObject* args, PyObject* kwargs); +static PyObject* remmina_pref_get_value_wrapper(PyObject* self, PyObject* args, PyObject* kwargs); static PyObject* remmina_pref_get_scale_quality_wrapper(PyObject* self, PyObject* plugin); - -/** - * @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_pref_keymap_get_keyval_wrapper(PyObject* self, PyObject* args, PyObject* kwargs); +static PyObject* remmina_plugin_python_log_print_wrapper(PyObject* self, PyObject* arg); +static PyObject* remmina_widget_pool_register_wrapper(PyObject* self, PyObject* args, PyObject* kwargs); +static PyObject* rcw_open_from_file_full_wrapper(PyObject* self, PyObject* args, PyObject* kwargs); +static PyObject* remmina_public_get_server_port_wrapper(PyObject* self, PyObject* args, PyObject* kwargs); static PyObject* remmina_masterthread_exec_is_main_thread_wrapper(PyObject* self, PyObject* plugin); - -/** - * @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); +static PyObject* +remmina_protocol_widget_get_profile_remote_height_wrapper(PyObject* self, PyObject* args, PyObject* kwargs); +static PyObject* +remmina_protocol_widget_get_profile_remote_width_wrapper(PyObject* self, PyObject* args, PyObject* kwargs); +static PyObject* remmina_plugin_python_show_dialog_wrapper(PyObject* self, PyObject* args, PyObject* kwargs); +static PyObject* remmina_plugin_python_get_mainwindow_wrapper(PyObject* self, PyObject* args); +static PyObject* remmina_protocol_plugin_signal_connection_opened_wrapper(PyObject* self, PyObject* args); +static PyObject* remmina_protocol_plugin_signal_connection_closed_wrapper(PyObject* self, PyObject* args); +static PyObject* remmina_protocol_plugin_init_auth_wrapper(PyObject* self, PyObject* args, PyObject* kwargs); /** - * @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. + * Declares functions for the Remmina module. These functions can be called from Python and are wired to one of the + * functions here in this file. */ static PyMethodDef remmina_python_module_type_methods[] = { - {"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 */ + /** + * The first function that need to be called from the plugin code, since it registers the Python class acting as + * a Remmina plugin. Without this call the plugin will not be recognized. + */ + { "register_plugin", remmina_register_plugin_wrapper, METH_O, NULL }, + + /** + * Prints a string into the Remmina log infrastructure. + */ + { "log_print", remmina_plugin_python_log_print_wrapper, METH_VARARGS, NULL }, + + /** + * Prints a debug message if enabled. + */ + { "debug", remmina_plugin_python_debug_wrapper, METH_VARARGS, NULL }, + + /** + * Shows a GTK+ dialog. + */ + { "show_dialog", (PyCFunction)remmina_plugin_python_show_dialog_wrapper, METH_VARARGS | METH_KEYWORDS, + NULL }, + + /** + * Returns the GTK+ object of the main window of Remmina. + */ + { "get_main_window", remmina_plugin_python_get_mainwindow_wrapper, METH_NOARGS, NULL }, + + /** + * Calls remmina_file_get_datadir and returns its result. + */ + { "get_datadir", remmina_file_get_datadir_wrapper, METH_VARARGS, NULL }, + + /** + * Calls remmina_file_new and returns its result. + */ + { "file_new", (PyCFunction)remmina_file_new_wrapper, METH_VARARGS | METH_KEYWORDS, NULL }, + + /** + * Calls remmina_pref_set_value and returns its result. + */ + { "pref_set_value", (PyCFunction)remmina_pref_set_value_wrapper, METH_VARARGS | METH_KEYWORDS, NULL }, + + /** + * Calls remmina_pref_get_value and returns its result. + */ + { "pref_get_value", (PyCFunction)remmina_pref_get_value_wrapper, METH_VARARGS | METH_KEYWORDS, NULL }, + + /** + * Calls remmina_pref_get_scale_quality and returns its result. + */ + { "pref_get_scale_quality", remmina_pref_get_scale_quality_wrapper, METH_VARARGS, NULL }, + + /** + * Calls remmina_pref_get_sshtunnel_port and returns its result. + */ + { "pref_get_sshtunnel_port", remmina_pref_get_sshtunnel_port_wrapper, METH_VARARGS, NULL }, + + /** + * Calls remmina_pref_get_ssh_loglevel and returns its result. + */ + { "pref_get_ssh_loglevel", remmina_pref_get_ssh_loglevel_wrapper, METH_VARARGS, NULL }, + + /** + * Calls remmina_pref_get_ssh_parseconfig and returns its result. + */ + { "pref_get_ssh_parseconfig", remmina_pref_get_ssh_parseconfig_wrapper, METH_VARARGS, NULL }, + + /** + * Calls remmina_pref_keymap_get_keyval and returns its result. + */ + { "pref_keymap_get_keyval", (PyCFunction)remmina_pref_keymap_get_keyval_wrapper, METH_VARARGS | METH_KEYWORDS, + NULL }, + + /** + * Calls remmina_widget_pool_register and returns its result. + */ + { "widget_pool_register", (PyCFunction)remmina_widget_pool_register_wrapper, METH_VARARGS | METH_KEYWORDS, NULL }, + + /** + * Calls rcw_open_from_file_full and returns its result. + */ + { "rcw_open_from_file_full", (PyCFunction)rcw_open_from_file_full_wrapper, METH_VARARGS | METH_KEYWORDS, NULL }, + + /** + * Calls remmina_public_get_server_port and returns its result. + */ + { "public_get_server_port", (PyCFunction)remmina_public_get_server_port_wrapper, METH_VARARGS | METH_KEYWORDS, + NULL }, + + /** + * Calls remmina_masterthread_exec_is_main_thread and returns its result. + */ + { "masterthread_exec_is_main_thread", remmina_masterthread_exec_is_main_thread_wrapper, METH_VARARGS, NULL }, + + /** + * Calls remmina_gtksocket_available and returns its result. + */ + { "gtksocket_available", remmina_gtksocket_available_wrapper, METH_VARARGS, NULL }, + + /** + * Calls remmina_protocol_widget_get_profile_remote_width and returns its result. + */ + { "protocol_widget_get_profile_remote_width", + (PyCFunction)remmina_protocol_widget_get_profile_remote_width_wrapper, METH_VARARGS | METH_KEYWORDS, NULL }, + + /** + * Calls remmina_protocol_widget_get_profile_remote_height and returns its result. + */ + { "protocol_widget_get_profile_remote_height", + (PyCFunction)remmina_protocol_widget_get_profile_remote_height_wrapper, METH_VARARGS | METH_KEYWORDS, NULL }, + { "protocol_plugin_signal_connection_opened", (PyCFunction)remmina_protocol_plugin_signal_connection_opened_wrapper, + METH_VARARGS, NULL }, + + { "protocol_plugin_signal_connection_closed", (PyCFunction)remmina_protocol_plugin_signal_connection_closed_wrapper, + METH_VARARGS, NULL }, + + { "protocol_plugin_init_auth", (PyCFunction)remmina_protocol_plugin_init_auth_wrapper, METH_VARARGS | METH_KEYWORDS, + NULL }, + + /* Sentinel */ + { NULL } }; -typedef struct { - PyObject_HEAD +/** + * Adapter struct to handle Remmina protocol settings. + */ +typedef struct +{ + PyObject_HEAD RemminaProtocolSettingType settingType; gchar* name; gchar* label; @@ -311,70 +244,93 @@ typedef struct { PyObject* opt2; } PyRemminaProtocolSetting; - /** - * @brief The definition of the Python module 'remmina'. + * 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 + 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) +/** + * Initializes the memory and the fields of the remmina.Setting Python type. + * @details This function is callback for the Python engine. + */ +static PyObject* python_protocol_setting_new(PyTypeObject* type, PyObject* args, PyObject* kwargs) { - 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; + TRACE_CALL(__func__); - return self; -} + PyRemminaProtocolSetting* self = (PyRemminaProtocolSetting*)type->tp_alloc(type, 0); -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 (!self) + { + return NULL; + } - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|lOOpOO", kwlist - ,&self->settingType - ,&name - ,&label - ,&self->compact - ,&self->opt1 - ,&self->opt2)) - return -1; + self->name = ""; + self->label = ""; + self->compact = FALSE; + self->opt1 = NULL; + self->opt2 = NULL; + self->settingType = 0; - 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); - } + return (PyObject*)self; +} - 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); - } +/** + * Constructor of the remmina.Setting Python type. + * @details This function is callback for the Python engine. + */ +static int python_protocol_setting_init(PyRemminaProtocolSetting* self, PyObject* args, PyObject* kwargs) +{ + TRACE_CALL(__func__); - return 0; + static gchar* kwlist[] = { "type", "name", "label", "compact", "opt1", "opt2", NULL }; + PyObject* name; + PyObject* label; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|lOOpOO", kwlist, + &self->settingType, &name, &label, &self->compact, &self->opt1, &self->opt2)) + { + return -1; + } + + Py_ssize_t len = PyUnicode_GetLength(label); + if (len == 0) + { + self->label = ""; + } + else + { + self->label = remmina_plugin_python_copy_string_from_python(label, len); + if (!self->label) + { + g_printerr("Unable to extract label during initialization of Python settings module!\n"); + remmina_plugin_python_check_error(); + } + } + + len = PyUnicode_GetLength(name); + if (len == 0) + { + self->name = ""; + } + else + { + self->name = remmina_plugin_python_copy_string_from_python(label, len); + if (!self->name) + { + g_printerr("Unable to extract name during initialization of Python settings module!\n"); + remmina_plugin_python_check_error(); + } + } + + return 0; } static PyMemberDef python_protocol_setting_type_members[] = { @@ -384,108 +340,335 @@ static PyMemberDef python_protocol_setting_type_members[] = { { "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} + { 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 + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "remmina.Setting", + .tp_doc = "Remmina Setting information", + .tp_basicsize = sizeof(PyRemminaProtocolSetting), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = python_protocol_setting_new, + .tp_init = (initproc)python_protocol_setting_init, + .tp_members = python_protocol_setting_type_members }; // -- Python Type -> Feature -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} + { "type", T_INT, offsetof(PyRemminaProtocolFeature, type), 0, NULL }, + { "id", T_INT, offsetof(PyRemminaProtocolFeature, id), 0, NULL }, + { "opt1", T_OBJECT, offsetof(PyRemminaProtocolFeature, opt1), 0, NULL }, + { "opt2", T_OBJECT, offsetof(PyRemminaProtocolFeature, opt2), 0, NULL }, + { "opt3", T_OBJECT, offsetof(PyRemminaProtocolFeature, opt3), 0, NULL }, + { NULL } }; -static PyObject* python_protocol_feature_new(PyTypeObject * type, PyObject* kws, PyObject* args) +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; + TRACE_CALL(__func__); - return (PyObject*)self; + PyRemminaProtocolFeature* self; + self = (PyRemminaProtocolFeature*)type->tp_alloc(type, 0); + if (!self) + return NULL; + + self->id = 0; + self->type = 0; + self->opt1 = (PyGeneric*)Py_None; + self->opt1->raw = NULL; + self->opt1->type_hint = REMMINA_TYPEHINT_UNDEFINED; + self->opt2 = (PyGeneric*)Py_None; + self->opt2->raw = NULL; + self->opt2->type_hint = REMMINA_TYPEHINT_UNDEFINED; + self->opt3 = (PyGeneric*)Py_None; + self->opt3->raw = NULL; + self->opt3->type_hint = REMMINA_TYPEHINT_UNDEFINED; + + return (PyObject*)self; } -static int python_protocol_feature_init(PyRemminaProtocolFeature *self, PyObject *args, PyObject *kwds) +static int python_protocol_feature_init(PyRemminaProtocolFeature* self, PyObject* args, PyObject* kwargs) { - static char *kwlist[] = { "type", "id", "opt1", "opt2", "opt3", NULL }; + TRACE_CALL(__func__); + + 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; + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|llOOO", kwlist, &self->type, &self->id, &self->opt1, &self + ->opt2, &self->opt3)) + return -1; - return 0; + 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 + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "remmina.ProtocolFeature", + .tp_doc = "Remmina Setting information", + .tp_basicsize = sizeof(PyRemminaProtocolFeature), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = python_protocol_feature_new, + .tp_init = (initproc)python_protocol_feature_init, + .tp_members = python_protocol_feature_members +}; + +PyRemminaProtocolFeature* remmina_plugin_python_protocol_feature_new(void) +{ + PyRemminaProtocolFeature* feature = (PyRemminaProtocolFeature*)PyObject_New(PyRemminaProtocolFeature, &python_protocol_feature_type); + feature->id = 0; + feature->opt1 = remmina_plugin_python_generic_new(); + feature->opt1->raw = NULL; + feature->opt1->type_hint = REMMINA_TYPEHINT_UNDEFINED; + feature->opt2 = remmina_plugin_python_generic_new(); + feature->opt2->raw = NULL; + feature->opt2->type_hint = REMMINA_TYPEHINT_UNDEFINED; + feature->opt3 = remmina_plugin_python_generic_new(); + feature->opt3->raw = NULL; + feature->opt3->type_hint = REMMINA_TYPEHINT_UNDEFINED; + feature->type = 0; + Py_IncRef((PyObject*)feature); + return feature; +} + + +// -- Python Type -> Screenshot Data + +static PyMemberDef python_screenshot_data_members[] = { + { "buffer", T_OBJECT, offsetof(PyRemminaPluginScreenshotData, buffer), 0, NULL }, + { "width", T_INT, offsetof(PyRemminaPluginScreenshotData, width), 0, NULL }, + { "height", T_INT, offsetof(PyRemminaPluginScreenshotData, height), 0, NULL }, + { "bitsPerPixel", T_INT, offsetof(PyRemminaPluginScreenshotData, bitsPerPixel), 0, NULL }, + { "bytesPerPixel", T_INT, offsetof(PyRemminaPluginScreenshotData, bytesPerPixel), 0, NULL }, + { NULL } +}; + +PyObject* python_screenshot_data_new(PyTypeObject* type, PyObject* kws, PyObject* args) +{ + TRACE_CALL(__func__); + + PyRemminaPluginScreenshotData* self; + self = (PyRemminaPluginScreenshotData*)type->tp_alloc(type, 0); + if (!self) + return NULL; + + self->buffer = (PyByteArrayObject*)PyObject_New(PyByteArrayObject, &PyByteArray_Type); + self->height = 0; + self->width = 0; + self->bitsPerPixel = 0; + self->bytesPerPixel = 0; + + return (PyObject*)self; +} + +static int python_screenshot_data_init(PyRemminaPluginScreenshotData* self, PyObject* args, PyObject* kwargs) +{ + TRACE_CALL(__func__); + + g_printerr("Not to be initialized within Python!"); + return -1; +} + +static PyTypeObject python_screenshot_data_type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "remmina.RemminaScreenshotData", + .tp_doc = "Remmina Screenshot Data", + .tp_basicsize = sizeof(PyRemminaPluginScreenshotData), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = python_screenshot_data_new, + .tp_init = (initproc)python_screenshot_data_init, + .tp_members = python_screenshot_data_members }; +PyRemminaPluginScreenshotData* remmina_plugin_python_screenshot_data_new(void) +{ + PyRemminaPluginScreenshotData* data = (PyRemminaPluginScreenshotData*)PyObject_New(PyRemminaPluginScreenshotData, &python_screenshot_data_type); + data->buffer = PyObject_New(PyByteArrayObject, &PyByteArray_Type); + Py_IncRef((PyObject*)data->buffer); + data->height = 0; + data->width = 0; + data->bitsPerPixel = 0; + data->bytesPerPixel = 0; + return data; +} + +static PyObject* remmina_plugin_python_generic_to_int(PyGeneric* self, PyObject* args); +static PyObject* remmina_plugin_python_generic_to_bool(PyGeneric* self, PyObject* args); +static PyObject* remmina_plugin_python_generic_to_string(PyGeneric* self, PyObject* args); + +static void remmina_plugin_python_generic_dealloc(PyObject* self) +{ + PyObject_Del(self); +} + +static PyMethodDef remmina_plugin_python_generic_methods[] = { + { "to_int", (PyCFunction)remmina_plugin_python_generic_to_int, METH_NOARGS, "" }, + { "to_bool", (PyCFunction)remmina_plugin_python_generic_to_bool, METH_NOARGS, "" }, + { "to_string", (PyCFunction)remmina_plugin_python_generic_to_string, METH_NOARGS, "" }, + { NULL } +}; + +static PyMemberDef remmina_plugin_python_generic_members[] = { + { "raw", T_OBJECT, offsetof(PyGeneric, raw), 0, "" }, + { NULL } +}; + +PyObject* remmina_plugin_python_generic_type_new(PyTypeObject* type, PyObject* kws, PyObject* args) +{ + TRACE_CALL(__func__); + + PyGeneric* self; + self = (PyGeneric*)type->tp_alloc(type, 0); + if (!self) + return NULL; + + self->raw = Py_None; + + return (PyObject*)self; +} + +static int remmina_plugin_python_generic_init(PyGeneric* self, PyObject* args, PyObject* kwargs) +{ + TRACE_CALL(__func__); + + static char* kwlist[] = { "raw", NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|O", kwlist, &self->raw)) + return -1; + + return 0; +} + +static PyTypeObject python_generic_type = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "remmina.Generic", + .tp_doc = "", + .tp_basicsize = sizeof(PyGeneric), + .tp_itemsize = 0, + .tp_dealloc = remmina_plugin_python_generic_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_new = remmina_plugin_python_generic_type_new, + .tp_init = (initproc)remmina_plugin_python_generic_init, + .tp_members = remmina_plugin_python_generic_members, + .tp_methods = remmina_plugin_python_generic_methods, +}; + +PyGeneric* remmina_plugin_python_generic_new(void) +{ + PyGeneric* generic = (PyGeneric*)PyObject_New(PyGeneric, &python_generic_type); + generic->raw = PyLong_FromLongLong(0LL); + Py_IncRef((PyObject*)generic); + return generic; +} + +static PyObject* remmina_plugin_python_generic_to_int(PyGeneric* self, PyObject* args) +{ + SELF_CHECK(); + + if (self->raw == NULL) + { + return Py_None; + } + else if (self->type_hint == REMMINA_TYPEHINT_SIGNED) + { + return PyLong_FromLongLong((long long)self->raw); + } + else if (self->type_hint == REMMINA_TYPEHINT_UNSIGNED) + { + return PyLong_FromUnsignedLongLong((unsigned long long)self->raw); + } + + return Py_None; +} +static PyObject* remmina_plugin_python_generic_to_bool(PyGeneric* self, PyObject* args) +{ + SELF_CHECK(); + + if (self->raw == NULL) + { + return Py_None; + } + else if (self->type_hint == REMMINA_TYPEHINT_BOOLEAN) + { + return PyBool_FromLong((long)self->raw); + } + + return Py_None; +} +static PyObject* remmina_plugin_python_generic_to_string(PyGeneric* self, PyObject* args) +{ + SELF_CHECK(); + + if (self->raw == NULL) + { + return Py_None; + } + else if (self->type_hint == REMMINA_TYPEHINT_STRING) + { + return PyUnicode_FromString((const char*)self->raw); + } + + return Py_None; +} /** - * @brief Is called from the Python engine when it initializes the 'remmina' module. + * 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; - } +PyMODINIT_FUNC remmina_plugin_python_module_initialize(void) +{ + TRACE_CALL(__func__); + + if (PyType_Ready(&python_screenshot_data_type) < 0) + { + PyErr_Print(); + return NULL; + } + + if (PyType_Ready(&python_generic_type) < 0) + { + PyErr_Print(); + return NULL; + } + + if (PyType_Ready(&python_protocol_setting_type) < 0) + { + PyErr_Print(); + return NULL; + } + + if (PyType_Ready(&python_protocol_feature_type) < 0) + { + PyErr_Print(); + return NULL; + } + + remmina_plugin_python_protocol_widget_type_ready(); + remmina_plugin_python_remmina_init_types(); + + PyObject* module = PyModule_Create(&remmina_python_module_type); + if (!module) + { + PyErr_Print(); + return NULL; + } + + PyModule_AddIntConstant(module, "BUTTONS_CLOSE", (long)GTK_BUTTONS_CLOSE); + PyModule_AddIntConstant(module, "BUTTONS_NONE", (long)GTK_BUTTONS_NONE); + PyModule_AddIntConstant(module, "BUTTONS_OK", (long)GTK_BUTTONS_OK); + PyModule_AddIntConstant(module, "BUTTONS_CLOSE", (long)GTK_BUTTONS_CLOSE); + PyModule_AddIntConstant(module, "BUTTONS_CANCEL", (long)GTK_BUTTONS_CANCEL); + PyModule_AddIntConstant(module, "BUTTONS_YES_NO", (long)GTK_BUTTONS_YES_NO); + PyModule_AddIntConstant(module, "BUTTONS_OK_CANCEL", (long)GTK_BUTTONS_OK_CANCEL); + + PyModule_AddIntConstant(module, "MESSAGE_INFO", (long)GTK_MESSAGE_INFO); + PyModule_AddIntConstant(module, "MESSAGE_WARNING", (long)GTK_MESSAGE_WARNING); + PyModule_AddIntConstant(module, "MESSAGE_QUESTION", (long)GTK_MESSAGE_QUESTION); + PyModule_AddIntConstant(module, "MESSAGE_ERROR", (long)GTK_MESSAGE_ERROR); + PyModule_AddIntConstant(module, "MESSAGE_OTHER", (long)GTK_MESSAGE_OTHER); PyModule_AddIntConstant(module, "PROTOCOL_SETTING_TYPE_SERVER", (long)REMMINA_PROTOCOL_SETTING_TYPE_SERVER); PyModule_AddIntConstant(module, "PROTOCOL_SETTING_TYPE_PASSWORD", (long)REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD); @@ -497,6 +680,7 @@ static PyMODINIT_FUNC remmina_plugin_python_module_initialize(void) PyModule_AddIntConstant(module, "PROTOCOL_SETTING_TYPE_CHECK", (long)REMMINA_PROTOCOL_SETTING_TYPE_CHECK); PyModule_AddIntConstant(module, "PROTOCOL_SETTING_TYPE_FILE", (long)REMMINA_PROTOCOL_SETTING_TYPE_FILE); PyModule_AddIntConstant(module, "PROTOCOL_SETTING_TYPE_FOLDER", (long)REMMINA_PROTOCOL_SETTING_TYPE_FOLDER); + PyModule_AddIntConstant(module, "PROTOCOL_FEATURE_TYPE_MULTIMON", (long)REMMINA_PROTOCOL_FEATURE_TYPE_MULTIMON); PyModule_AddIntConstant(module, "PROTOCOL_FEATURE_TYPE_PREF", (long)REMMINA_PROTOCOL_FEATURE_TYPE_PREF); PyModule_AddIntConstant(module, "PROTOCOL_FEATURE_TYPE_TOOL", (long)REMMINA_PROTOCOL_FEATURE_TYPE_TOOL); @@ -511,311 +695,531 @@ static PyMODINIT_FUNC remmina_plugin_python_module_initialize(void) 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; + PyModule_AddIntConstant(module, "PROTOCOL_FEATURE_PREF_RADIO", (long)REMMINA_PROTOCOL_FEATURE_PREF_RADIO); + PyModule_AddIntConstant(module, "PROTOCOL_FEATURE_PREF_CHECK", (long)REMMINA_PROTOCOL_FEATURE_PREF_CHECK); + + PyModule_AddIntConstant(module, "MESSAGE_PANEL_FLAG_USERNAME", REMMINA_MESSAGE_PANEL_FLAG_USERNAME); + PyModule_AddIntConstant(module, "MESSAGE_PANEL_FLAG_USERNAME_READONLY", REMMINA_MESSAGE_PANEL_FLAG_USERNAME_READONLY); + PyModule_AddIntConstant(module, "MESSAGE_PANEL_FLAG_DOMAIN", REMMINA_MESSAGE_PANEL_FLAG_DOMAIN); + PyModule_AddIntConstant(module, "MESSAGE_PANEL_FLAG_SAVEPASSWORD", REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD); + + if (PyModule_AddObject(module, "Setting", (PyObject*)&python_protocol_setting_type) < 0) + { + Py_DECREF(&python_protocol_setting_type); + Py_DECREF(module); + PyErr_Print(); + return NULL; + } + + Py_INCREF(&python_protocol_feature_type); + if (PyModule_AddObject(module, "Feature", (PyObject*)&python_protocol_feature_type) < 0) + { + Py_DECREF(&python_protocol_setting_type); + Py_DECREF(&python_protocol_feature_type); + Py_DECREF(module); + PyErr_Print(); + return NULL; + } + + return module; } /** - * @brief Initializes all globals and registers the 'remmina' module in the Python engine. + * 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; + if (PyImport_AppendInittab("remmina", remmina_plugin_python_module_initialize)) + { + PyErr_Print(); + exit(1); + } + + remmina_plugin_python_entry_init(); + remmina_plugin_python_protocol_init(); + remmina_plugin_python_tool_init(); + remmina_plugin_python_pref_init(); + remmina_plugin_python_secret_init(); + remmina_plugin_python_file_init(); } -static PyObject* remmina_plugin_python_log_printf_wrapper(PyObject* self, PyObject* msg) +gboolean remmina_plugin_python_check_mandatory_member(PyObject* instance, const gchar* member) { 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; - } + if (PyObject_HasAttrString(instance, member)) + { + return TRUE; + } - return Py_None; + g_printerr("Missing mandatory member '%s' in Python plugin instance!\n", member); + return FALSE; } static PyObject* remmina_register_plugin_wrapper(PyObject* self, PyObject* plugin_instance) { TRACE_CALL(__func__); - if (plugin_instance) { - if (!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; + 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 */ + const gchar* pluginType = PyUnicode_AsUTF8(PyObject_GetAttrString(plugin_instance, "type")); + + RemminaPlugin* remmina_plugin = NULL; + + PyPlugin* plugin = (PyPlugin*)remmina_plugin_python_malloc(sizeof(PyPlugin)); + plugin->instance = plugin_instance; + Py_INCREF(plugin_instance); + plugin->protocol_plugin = NULL; + plugin->entry_plugin = NULL; + plugin->file_plugin = NULL; + plugin->pref_plugin = NULL; + plugin->secret_plugin = NULL; + plugin->tool_plugin = NULL; + g_print("New Python plugin registered: %ld\n", PyObject_Hash(plugin_instance)); + + if (g_str_equal(pluginType, "protocol")) + { + remmina_plugin = remmina_plugin_python_create_protocol_plugin(plugin); + } + else if (g_str_equal(pluginType, "entry")) + { + remmina_plugin = remmina_plugin_python_create_entry_plugin(plugin); + } + else if (g_str_equal(pluginType, "file")) + { + remmina_plugin = remmina_plugin_python_create_file_plugin(plugin); + } + else if (g_str_equal(pluginType, "tool")) + { + remmina_plugin = remmina_plugin_python_create_tool_plugin(plugin); + } + else if (g_str_equal(pluginType, "pref")) + { + remmina_plugin = remmina_plugin_python_create_pref_plugin(plugin); + } + else if (g_str_equal(pluginType, "secret")) + { + remmina_plugin = remmina_plugin_python_create_secret_plugin(plugin); + } + else + { + g_printerr("Unknown plugin type: %s\n", pluginType); + } + + if (remmina_plugin) + { + if (remmina_plugin->type == REMMINA_PLUGIN_TYPE_PROTOCOL) + { + plugin->gp = remmina_plugin_python_protocol_widget_create(); + } + + remmina_plugin_manager_service.register_plugin((RemminaPlugin*)remmina_plugin); + } + } + + return Py_None; } static PyObject* remmina_file_get_datadir_wrapper(PyObject* self, PyObject* plugin) { - return Py_None; + TRACE_CALL(__func__); + + PyObject* result = Py_None; + const gchar* datadir = remmina_file_get_datadir(); + + if (datadir) + { + result = PyUnicode_FromFormat("%s", datadir); + } + + remmina_plugin_python_check_error(); + return result; } -static PyObject* remmina_file_new_wrapper(PyObject* self, PyObject* plugin) +static PyObject* remmina_file_new_wrapper(PyObject* self, PyObject* args, PyObject* kwargs) { - return Py_None; + TRACE_CALL(__func__); + + RemminaFile* file = remmina_file_new(); + if (file) + { + return (PyObject*)remmina_plugin_python_remmina_file_to_python(file); + } + + remmina_plugin_python_check_error(); + return Py_None; } -static PyObject* remmina_pref_set_value_wrapper(PyObject* self, PyObject* plugin) +static PyObject* remmina_pref_set_value_wrapper(PyObject* self, PyObject* args, PyObject* kwargs) { - return Py_None; + TRACE_CALL(__func__); + + static char* kwlist[] = { "key", "value", NULL }; + gchar* key, * value; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ss", kwlist, &key, &value)) + { + return Py_None; + } + + if (key) + { + remmina_pref_set_value(key, value); + } + + remmina_plugin_python_check_error(); + return Py_None; } -static PyObject* remmina_pref_get_value_wrapper(PyObject* self, PyObject* plugin) +static PyObject* remmina_pref_get_value_wrapper(PyObject* self, PyObject* args, PyObject* kwargs) { - return Py_None; -} + TRACE_CALL(__func__); + static char* kwlist[] = { "key", NULL }; + gchar* key; + PyObject* result = Py_None; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s", kwlist, &key)) + { + return Py_None; + } + + if (key) + { + const gchar* value = remmina_pref_get_value(key); + if (value) + { + result = PyUnicode_FromFormat("%s", result); + } + } + + remmina_plugin_python_check_error(); + return result; +} static PyObject* remmina_pref_get_scale_quality_wrapper(PyObject* self, PyObject* plugin) { - return Py_None; -} + TRACE_CALL(__func__); + PyObject* result = PyLong_FromLong(remmina_pref_get_scale_quality()); + remmina_plugin_python_check_error(); + return result; +} static PyObject* remmina_pref_get_sshtunnel_port_wrapper(PyObject* self, PyObject* plugin) { - return Py_None; -} + TRACE_CALL(__func__); + PyObject* result = PyLong_FromLong(remmina_pref_get_sshtunnel_port()); + remmina_plugin_python_check_error(); + return result; +} static PyObject* remmina_pref_get_ssh_loglevel_wrapper(PyObject* self, PyObject* plugin) { - return Py_None; -} + TRACE_CALL(__func__); + PyObject* result = PyLong_FromLong(remmina_pref_get_ssh_loglevel()); + remmina_plugin_python_check_error(); + return result; +} static PyObject* remmina_pref_get_ssh_parseconfig_wrapper(PyObject* self, PyObject* plugin) { - return Py_None; -} + TRACE_CALL(__func__); + PyObject* result = PyLong_FromLong(remmina_pref_get_ssh_parseconfig()); + remmina_plugin_python_check_error(); + return result; +} -static PyObject* remmina_pref_keymap_get_keyval_wrapper(PyObject* self, PyObject* plugin) +static PyObject* remmina_pref_keymap_get_keyval_wrapper(PyObject* self, PyObject* args, PyObject* kwargs) { - return Py_None; -} + TRACE_CALL(__func__); + static char* kwlist[] = { "keymap", "keyval", NULL }; + gchar* keymap; + guint keyval; + PyObject* result = Py_None; -static PyObject* remmina_log_print_wrapper(PyObject* self, PyObject* plugin) -{ - return Py_None; -} + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "sl", kwlist, &keymap, &keyval)) + { + return PyLong_FromLong(-1); + } + if (keymap) + { + const guint value = remmina_pref_keymap_get_keyval(keymap, keyval); + result = PyLong_FromUnsignedLong(value); + } -static PyObject* remmina_log_printf_wrapper(PyObject* self, PyObject* plugin) -{ - return Py_None; + remmina_plugin_python_check_error(); + return result; } - -static PyObject* remmina_widget_pool_register_wrapper(PyObject* self, PyObject* plugin) +static PyObject* remmina_plugin_python_log_print_wrapper(PyObject* self, PyObject* args) { - return Py_None; -} + TRACE_CALL(__func__); + gchar* text; + if (!PyArg_ParseTuple(args, "s", &text) || !text) + { + return Py_None; + } + + remmina_log_print(text); + return Py_None; +} -static PyObject* rcw_open_from_file_full_wrapper(PyObject* self, PyObject* plugin) +static PyObject* remmina_plugin_python_debug_wrapper(PyObject* self, PyObject* args) { - return Py_None; + TRACE_CALL(__func__); + + gchar* text; + if (!PyArg_ParseTuple(args, "s", &text) || !text) + { + return Py_None; + } + + remmina_plugin_manager_service._remmina_debug("python", "%s", text); + return Py_None; } +static PyObject* remmina_widget_pool_register_wrapper(PyObject* self, PyObject* args, PyObject* kwargs) +{ + TRACE_CALL(__func__); + + static char* kwlist[] = { "widget", NULL }; + PyObject* widget; + + if (PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &widget) && widget) + { + remmina_widget_pool_register(get_pywidget(widget)); + } + + return Py_None; +} -static PyObject* remmina_public_get_server_port_wrapper(PyObject* self, PyObject* plugin) +static PyObject* rcw_open_from_file_full_wrapper(PyObject* self, PyObject* args, PyObject* kwargs) { - return Py_None; + TRACE_CALL(__func__); + + static char* kwlist[] = { "remminafile", "data", "handler", NULL }; + PyObject* pyremminafile; + PyObject* data; + + if (PyArg_ParseTupleAndKeywords(args, kwargs, "OOO", kwlist, &pyremminafile, &data) && pyremminafile && data) + { + rcw_open_from_file_full((RemminaFile*)pyremminafile, NULL, (void*)data, NULL); + } + + return Py_None; } +static PyObject* remmina_public_get_server_port_wrapper(PyObject* self, PyObject* args, PyObject* kwargs) +{ + TRACE_CALL(__func__); + + static char* kwlist[] = { "server", "defaultport", "host", "port", NULL }; + gchar* server; + gint defaultport; + + if (PyArg_ParseTupleAndKeywords(args, kwargs, "slsl", kwlist, &server, &defaultport) && server) + { + gchar* host; + gint port; + remmina_public_get_server_port(server, defaultport, &host, &port); + + PyObject* result = PyList_New(2); + PyList_Append(result, PyUnicode_FromString(host)); + PyList_Append(result, PyLong_FromLong(port)); + return PyList_AsTuple(result); + } + + return Py_None; +} static PyObject* remmina_masterthread_exec_is_main_thread_wrapper(PyObject* self, PyObject* plugin) { - return Py_None; -} + TRACE_CALL(__func__); + return PyBool_FromLong(remmina_masterthread_exec_is_main_thread()); +} static PyObject* remmina_gtksocket_available_wrapper(PyObject* self, PyObject* plugin) { - return Py_None; -} + TRACE_CALL(__func__); + return PyBool_FromLong(remmina_gtksocket_available()); +} -static PyObject* remmina_protocol_widget_get_profile_remote_heigh_wrapper(PyObject* self, PyObject* plugin) +static PyObject* +remmina_protocol_widget_get_profile_remote_height_wrapper(PyObject* self, PyObject* args, PyObject* kwargs) { - return Py_None; + TRACE_CALL(__func__); + + static char* kwlist[] = { "widget", NULL }; + PyPlugin* plugin; + + if (PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &plugin) && plugin && plugin->gp) + { + remmina_protocol_widget_get_profile_remote_height(plugin->gp->gp); + } + + return Py_None; } +static PyObject* +remmina_protocol_widget_get_profile_remote_width_wrapper(PyObject* self, PyObject* args, PyObject* kwargs) +{ + TRACE_CALL(__func__); + + static char* kwlist[] = { "widget", NULL }; + PyPlugin* plugin; + + if (PyArg_ParseTupleAndKeywords(args, kwargs, "O", kwlist, &plugin) && plugin && plugin->gp) + { + remmina_protocol_widget_get_profile_remote_width(plugin->gp->gp); + } + + return Py_None; +} -static PyObject* remmina_protocol_widget_get_profile_remote_width_wrapper(PyObject* self, PyObject* plugin) +void remmina_plugin_python_to_protocol_setting(RemminaProtocolSetting* dest, PyObject* setting) { - return Py_None; + TRACE_CALL(__func__); + + PyRemminaProtocolSetting* src = (PyRemminaProtocolSetting*)setting; + Py_INCREF(setting); + dest->name = src->name; + dest->label = src->label; + dest->compact = src->compact; + dest->type = src->settingType; + dest->validator = NULL; + dest->validator_data = NULL; + remmina_plugin_python_to_generic(src->opt1, &dest->opt1); + remmina_plugin_python_to_generic(src->opt2, &dest->opt2); } -static gboolean remmina_plugin_equal(gconstpointer lhs, gconstpointer rhs) +void remmina_plugin_python_to_protocol_feature(RemminaProtocolFeature* dest, PyObject* feature) { - if (lhs && ((PyPlugin*)lhs)->generic_plugin && rhs) - return g_str_equal(((PyPlugin*)lhs)->generic_plugin->name, ((gchar*)rhs)); - else - return lhs == rhs; + TRACE_CALL(__func__); + + PyRemminaProtocolFeature* src = (PyRemminaProtocolFeature*)feature; + Py_INCREF(feature); + dest->id = src->id; + dest->type = src->type; + dest->opt1 = src->opt1->raw; + dest->opt1_type_hint = src->opt1->type_hint; + dest->opt2 = src->opt2->raw; + dest->opt2_type_hint = src->opt2->type_hint; + dest->opt3 = src->opt3->raw; + dest->opt3_type_hint = src->opt3->type_hint; } -PyPlugin* remmina_plugin_python_module_get_plugin(RemminaProtocolWidget* gp) +PyObject* remmina_plugin_python_show_dialog_wrapper(PyObject* self, PyObject* args, PyObject* kwargs) { - 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; + TRACE_CALL(__func__); + + static char* kwlist[] = { "type", "buttons", "message", NULL }; + GtkMessageType msgType; + GtkButtonsType btnType; + gchar* message; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "lls", kwlist, &msgType, &btnType, &message)) + { + return PyLong_FromLong(-1); + } + + remmina_main_show_dialog(msgType, btnType, message); + + return Py_None; } -static void GetGeneric(PyObject* field, gpointer* target) +PyObject* remmina_plugin_python_get_mainwindow_wrapper(PyObject* self, PyObject* args) { - if (!field || field == Py_None) { - *target = NULL; - return; - } - Py_INCREF(field); - if (PyUnicode_Check(field)) { - Py_ssize_t len = PyUnicode_GetLength(field); + TRACE_CALL(__func__); + + GtkWindow* result = remmina_main_get_window(); - if (len == 0) { - *target = ""; - } else { - gchar* tmp = g_malloc(sizeof(char) * (len + 1)); - *(tmp + len) = 0; - memcpy(tmp, PyUnicode_AsUTF8(field), len); - *target = tmp; - } + if (!result) + { + return Py_None; + } - } 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)); + return (PyObject*)new_pywidget((GObject*)result); +} - for (Py_ssize_t i = 0; i < len; ++i) { - PyObject* item = PyTuple_GetItem(field, i); - GetGeneric(item, dest + i); - } +static PyObject* remmina_protocol_plugin_signal_connection_closed_wrapper(PyObject* self, PyObject* args) +{ + TRACE_CALL(__func__); - *target = dest; - } - } - Py_DECREF(field); + PyObject* pygp = NULL; + if (!PyArg_ParseTuple(args, "O", &pygp) || !pygp) + { + g_printerr("Please provide the Remmina protocol widget instance!"); + return Py_None; + } + + remmina_plugin_manager_service.protocol_plugin_signal_connection_closed(((PyRemminaProtocolWidget*)pygp)->gp); + return Py_None; } -void ToRemminaProtocolSetting(RemminaProtocolSetting* dest, PyObject* setting) +static PyObject* remmina_protocol_plugin_init_auth_wrapper(PyObject* module, PyObject* args, PyObject* kwds) { - 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); + TRACE_CALL(__func__); + + static gchar* keyword_list[] = { "widget", "flags", "title", "default_username", "default_password", + "default_domain", "password_prompt" }; + + PyRemminaProtocolWidget* self; + gint pflags = 0; + gchar* title, * default_username, * default_password, * default_domain, * password_prompt; + + if (PyArg_ParseTupleAndKeywords(args, kwds, "Oisssss", keyword_list, &self, &pflags, &title, &default_username, + &default_password, &default_domain, &password_prompt)) + { + if (pflags != 0 && !(pflags & REMMINA_MESSAGE_PANEL_FLAG_USERNAME) + && !(pflags & REMMINA_MESSAGE_PANEL_FLAG_USERNAME_READONLY) + && !(pflags & REMMINA_MESSAGE_PANEL_FLAG_DOMAIN) + && !(pflags & REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD)) + { + g_printerr("panel_auth(pflags, title, default_username, default_password, default_domain, password_prompt): " + "%d is not a known value for RemminaMessagePanelFlags!\n", pflags); + } + else + { + return Py_BuildValue("i", 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; } -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); +static PyObject* remmina_protocol_plugin_signal_connection_opened_wrapper(PyObject* self, PyObject* args) +{ + PyObject* pygp = NULL; + if (!PyArg_ParseTuple(args, "O", &pygp) || !pygp) + { + g_printerr("Please provide the Remmina protocol widget instance!"); + return Py_None; + } + + remmina_plugin_manager_service.protocol_plugin_signal_connection_opened(((PyRemminaProtocolWidget*)pygp)->gp); + return Py_None; } diff --git a/src/remmina_plugin_python_remmina.h b/src/remmina_plugin_python_remmina.h index d3bfbbd7b..25869d58a 100644 --- a/src/remmina_plugin_python_remmina.h +++ b/src/remmina_plugin_python_remmina.h @@ -29,55 +29,64 @@ * 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. + * @file remmina_plugin_python_remmina.h * - * @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. + * @brief Contains the implementation of the Python module 'remmina', provided to interface with the application from + * the Python plugin source. * - * @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. + * @detail In contrast to the wrapper functions that exist in the plugin specialisation files (e.g. + * remmina_plugin_python_protocol.c or remmina_plugin_python_entry.c), this file contains the API for the + * communication in the direction, from Python to Remmina. This means, if in the Python plugin a function + * is called, that is defined in Remmina, C code, at least in this file, is executed. */ -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; +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N C L U D E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - PyRemminaProtocolWidget *gp; -} PyPlugin; +#include "remmina_plugin_python_protocol_widget.h" +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +G_BEGIN_DECLS /** - * @brief Initializes the 'remmina' module in the Python engine. + * 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. + * @brief Returns a pointer to the Python instance, mapped to the RemminaProtocolWidget or null if not found. + * + * @param gp The widget that is owned by the plugin that should be found. * - * @details Remmina expects this callback function to be part of one plugin, which is the reason no instance information is explicitly passed. To bridge - * that, this function can be used to retrieve the very plugin instance owning the given RemminaProtocolWidget. + * @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); +/** + * @brief Converts the PyObject to RemminaProtocolSetting. + * + * @param dest A target for the converted value. + * @param setting The source value to convert. + */ +void remmina_plugin_python_to_protocol_setting(RemminaProtocolSetting* dest, PyObject* setting); + +/** + * @brief Converts the PyObject to RemminaProtocolFeature. + * + * @param dest A target for the converted value. + * @param setting The source value to convert. + */ +void remmina_plugin_python_to_protocol_feature(RemminaProtocolFeature* dest, PyObject* feature); + +G_END_DECLS diff --git a/src/remmina_plugin_python_remmina_file.c b/src/remmina_plugin_python_remmina_file.c index 6de367818..1c417129a 100644 --- a/src/remmina_plugin_python_remmina_file.c +++ b/src/remmina_plugin_python_remmina_file.c @@ -29,132 +29,177 @@ * 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. - * */ -#include <glib.h> -#include <gtk/gtk.h> -#define PY_SSIZE_T_CLEAN -#include <Python.h> -#include <structmember.h> +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N C L U D E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #include "remmina/remmina_trace_calls.h" #include "remmina_file.h" +#include "remmina_plugin_python_common.h" #include "remmina_plugin_python_remmina_file.h" -typedef struct { - PyObject_HEAD - RemminaFile* file; -} PyRemminaFile; +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// D E C L A R A T I O N S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// static PyObject* file_get_path(PyRemminaFile* self, PyObject* args); static PyObject* file_set_setting(PyRemminaFile* self, PyObject* args, PyObject* kwds); static PyObject* file_get_setting(PyRemminaFile* self, PyObject* args, PyObject* kwds); static PyObject* file_get_secret(PyRemminaFile* self, PyObject* setting); static PyObject* file_unsave_passwords(PyRemminaFile* self, PyObject* args); -static void file_dealloc(PyObject* self) { PyObject_Del(self); } + +static 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} + { "get_path", (PyCFunction)file_get_path, METH_NOARGS, "" }, + { "set_setting", (PyCFunction)file_set_setting, METH_VARARGS | METH_KEYWORDS, "Set file setting" }, + { "get_setting", (PyCFunction)file_get_setting, METH_VARARGS | METH_KEYWORDS, "Get file setting" }, + { "get_secret", (PyCFunction)file_get_secret, METH_VARARGS, "Get secret file setting" }, + { "unsave_passwords", (PyCFunction)file_unsave_passwords, METH_NOARGS, "" }, + { NULL } }; /** * @brief The definition of the Python module 'remmina'. */ static PyTypeObject python_remmina_file_type = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "remmina.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 + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "remmina.RemminaFile", + .tp_doc = "", + .tp_basicsize = sizeof(PyRemminaFile), + .tp_itemsize = 0, + .tp_flags = Py_TPFLAGS_DEFAULT, + .tp_methods = python_remmina_file_type_methods, + .tp_dealloc = file_dealloc }; -PyObject* remmina_plugin_python_remmina_file_to_python(RemminaFile* file) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void remmina_plugin_python_remmina_init_types(void) +{ + PyType_Ready(&python_remmina_file_type); +} + +PyRemminaFile* 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; + + 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)); + TRACE_CALL(__func__); + + 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; - } + TRACE_CALL(__func__); + + static char* keyword_list[] = { "key", "value", NULL }; + gchar* key; + PyObject* value; + + if (PyArg_ParseTupleAndKeywords(args, kwds, "s|O", keyword_list, &key, &value)) + { + if (PyUnicode_Check(value)) + { + remmina_file_set_string(self->file, key, PyUnicode_AsUTF8(value)); + } + else if (PyLong_Check(value)) + { + remmina_file_set_int(self->file, key, PyLong_AsLong(value)); + } + else + { + g_printerr("%s: Not a string or int value\n", PyUnicode_AsUTF8(PyObject_Str(value))); + } + return Py_None; + } + else + { + g_printerr("file.set_setting(key, value): Error parsing arguments!\n"); + PyErr_Print(); + return NULL; + } } static PyObject* file_get_setting(PyRemminaFile* self, PyObject* args, PyObject* kwds) { - 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; - } + TRACE_CALL(__func__); + + static gchar* keyword_list[] = { "key", "default" }; + gchar* key; + PyObject* def; + + if (PyArg_ParseTupleAndKeywords(args, kwds, "sO", keyword_list, &key, &def)) + { + if (PyUnicode_Check(def)) + { + return Py_BuildValue("s", remmina_file_get_string(self->file, key)); + } + else if (PyBool_Check(def)) + { + return remmina_file_get_int(self->file, key, (gint)PyLong_AsLong(def)) ? Py_True : Py_False; + } + 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 Py_None; + } } 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; - } + TRACE_CALL(__func__); + + 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; - } + TRACE_CALL(__func__); + + 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 index 7290364ec..a2e7a53d3 100644 --- a/src/remmina_plugin_python_remmina_file.h +++ b/src/remmina_plugin_python_remmina_file.h @@ -29,9 +29,33 @@ * 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.h * + * @brief Contains the implementation of the Python type remmina.RemminaFile. */ #pragma once -PyObject* remmina_plugin_python_remmina_file_to_python(RemminaFile* file); +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Wrapper for a Python object that contains a pointer to an instance of RemminaFile. + */ +typedef struct +{ + PyObject_HEAD + RemminaFile* file; +} PyRemminaFile; + +void remmina_plugin_python_remmina_init_types(void); + +/** + * Converts the instance of RemminaFile to a Python object that can be passed to the Python engine. + */ +PyRemminaFile* remmina_plugin_python_remmina_file_to_python(RemminaFile* file); diff --git a/src/remmina_plugin_python_secret.c b/src/remmina_plugin_python_secret.c new file mode 100644 index 000000000..be659fdbb --- /dev/null +++ b/src/remmina_plugin_python_secret.c @@ -0,0 +1,138 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2014-2021 Antenore Gatta, Giovanni Panozzo, Mathias Winterhalter (ToolsDevler) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N L U C E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "remmina_plugin_python_common.h" +#include "remmina_plugin_python_secret.h" +#include "remmina_plugin_python_remmina_file.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void remmina_plugin_python_secret_init(void) +{ + TRACE_CALL(__func__); +} + +gboolean remmina_plugin_python_secret_init_wrapper(RemminaSecretPlugin* instance) +{ + TRACE_CALL(__func__); + + PyPlugin* plugin = remmina_plugin_python_get_plugin(instance->name); + PyObject* result = CallPythonMethod(plugin->instance, "init", NULL); + return result == Py_None || result != Py_False; +} + +gboolean remmina_plugin_python_secret_is_service_available_wrapper(RemminaSecretPlugin* instance) +{ + TRACE_CALL(__func__); + + PyPlugin* plugin = remmina_plugin_python_get_plugin(instance->name); + PyObject* result = CallPythonMethod(plugin->instance, "is_service_available", NULL); + return result == Py_None || result != Py_False; +} + +void +remmina_plugin_python_secret_store_password_wrapper(RemminaSecretPlugin* instance, RemminaFile* file, const gchar* key, const gchar* password) +{ + TRACE_CALL(__func__); + + PyPlugin* plugin = remmina_plugin_python_get_plugin(instance->name); + CallPythonMethod(plugin + ->instance, "store_password", "Oss", (PyObject*)remmina_plugin_python_remmina_file_to_python(file), key, password); +} + +gchar* +remmina_plugin_python_secret_get_password_wrapper(RemminaSecretPlugin* instance, RemminaFile* file, const gchar* key) +{ + TRACE_CALL(__func__); + + PyPlugin* plugin = remmina_plugin_python_get_plugin(instance->name); + PyObject* result = CallPythonMethod(plugin + ->instance, "get_password", "Os", (PyObject*)remmina_plugin_python_remmina_file_to_python(file), key); + Py_ssize_t len = PyUnicode_GetLength(result); + if (len == 0) + { + return NULL; + } + + return remmina_plugin_python_copy_string_from_python(result, len); +} + +void +remmina_plugin_python_secret_delete_password_wrapper(RemminaSecretPlugin* instance, RemminaFile* file, const gchar* key) +{ + TRACE_CALL(__func__); + + PyPlugin* plugin = remmina_plugin_python_get_plugin(instance->name); + CallPythonMethod(plugin + ->instance, "delete_password", "Os", (PyObject*)remmina_plugin_python_remmina_file_to_python(file), key); +} + +RemminaPlugin* remmina_plugin_python_create_secret_plugin(PyPlugin* plugin) +{ + TRACE_CALL(__func__); + + PyObject* instance = plugin->instance; + + if (!remmina_plugin_python_check_attribute(instance, ATTR_NAME)) + { + return NULL; + } + + RemminaSecretPlugin* remmina_plugin = (RemminaSecretPlugin*)remmina_plugin_python_malloc(sizeof(RemminaSecretPlugin)); + + remmina_plugin->type = REMMINA_PLUGIN_TYPE_SECRET; + remmina_plugin->domain = GETTEXT_PACKAGE; + remmina_plugin->name = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_NAME)); + remmina_plugin->version = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_VERSION)); + remmina_plugin->description = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_DESCRIPTION)); + remmina_plugin->init_order = PyLong_AsLong(PyObject_GetAttrString(instance, ATTR_INIT_ORDER)); + + remmina_plugin->init = remmina_plugin_python_secret_init_wrapper; + remmina_plugin->is_service_available = remmina_plugin_python_secret_is_service_available_wrapper; + remmina_plugin->store_password = remmina_plugin_python_secret_store_password_wrapper; + remmina_plugin->get_password = remmina_plugin_python_secret_get_password_wrapper; + remmina_plugin->delete_password = remmina_plugin_python_secret_delete_password_wrapper; + + plugin->secret_plugin = remmina_plugin; + plugin->generic_plugin = (RemminaPlugin*)remmina_plugin; + + remmina_plugin_python_add_plugin(plugin); + + return (RemminaPlugin*)remmina_plugin; +}
\ No newline at end of file diff --git a/src/remmina_plugin_python_secret.h b/src/remmina_plugin_python_secret.h new file mode 100644 index 000000000..c627d2a49 --- /dev/null +++ b/src/remmina_plugin_python_secret.h @@ -0,0 +1,67 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2014-2021 Antenore Gatta, Giovanni Panozzo, Mathias Winterhalter (ToolsDevler) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +/** + * @file remmina_plugin_python_entry.h + * + * @brief Contains the specialisation of RemminaPluginEntry plugins in Python. + */ + +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N L U C E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "remmina/plugin.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +G_BEGIN_DECLS + +/** + * Initializes the Python plugin specialisation for secret plugins. + */ +void remmina_plugin_python_secret_init(void); + +/** + * @brief Creates a new instance of the RemminaPluginSecret, initializes its members and references the wrapper + * functions. + * @param instance The instance of the Python plugin. + * @return Returns a new instance of the RemminaPlugin (must be freed!). + */ +RemminaPlugin* remmina_plugin_python_create_secret_plugin(PyPlugin* instance); + +G_END_DECLS diff --git a/src/remmina_plugin_python_tool.c b/src/remmina_plugin_python_tool.c new file mode 100644 index 000000000..a951291f2 --- /dev/null +++ b/src/remmina_plugin_python_tool.c @@ -0,0 +1,84 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2014-2021 Antenore Gatta, Giovanni Panozzo, Mathias Winterhalter (ToolsDevler) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N L U C E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "remmina_plugin_python_common.h" +#include "remmina_plugin_python_tool.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +void remmina_plugin_python_tool_init(void) +{ + TRACE_CALL(__func__); +} + +void remmina_plugin_python_tool_exec_func_wrapper(GtkMenuItem* self, RemminaToolPlugin* instance) +{ + TRACE_CALL(__func__); + + PyPlugin* plugin = remmina_plugin_python_get_plugin(instance->name); + CallPythonMethod(plugin->instance, "exec_func", NULL); +} + +RemminaPlugin* remmina_plugin_python_create_tool_plugin(PyPlugin* plugin) +{ + TRACE_CALL(__func__); + + PyObject* instance = plugin->instance; + + if (!remmina_plugin_python_check_attribute(instance, ATTR_NAME)) + { + return NULL; + } + + RemminaToolPlugin* remmina_plugin = (RemminaToolPlugin*)remmina_plugin_python_malloc(sizeof(RemminaToolPlugin)); + + remmina_plugin->type = REMMINA_PLUGIN_TYPE_TOOL; + remmina_plugin->domain = GETTEXT_PACKAGE; + remmina_plugin->name = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_NAME)); + remmina_plugin->version = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_VERSION)); + remmina_plugin->description = PyUnicode_AsUTF8(PyObject_GetAttrString(instance, ATTR_DESCRIPTION)); + remmina_plugin->exec_func = remmina_plugin_python_tool_exec_func_wrapper; + + plugin->tool_plugin = remmina_plugin; + plugin->generic_plugin = (RemminaPlugin*)remmina_plugin; + + remmina_plugin_python_add_plugin(plugin); + + return (RemminaPlugin*)remmina_plugin; +} diff --git a/src/remmina_plugin_python_tool.h b/src/remmina_plugin_python_tool.h new file mode 100644 index 000000000..08852bdc4 --- /dev/null +++ b/src/remmina_plugin_python_tool.h @@ -0,0 +1,68 @@ +/* + * Remmina - The GTK+ Remote Desktop Client + * Copyright (C) 2014-2021 Antenore Gatta, Giovanni Panozzo, Mathias Winterhalter (ToolsDevler) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + */ + +/** + * @file remmina_plugin_python_entry.h + * + * @brief Contains the specialisation of RemminaPluginEntry plugins in Python. + */ + +#pragma once + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// I N L U C E S +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "remmina/plugin.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// A P I +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +G_BEGIN_DECLS + +/** + * Initializes the Python plugin specialisation for tool plugins. + */ +void remmina_plugin_python_tool_init(void); + +/** + * @brief Creates a new instance of the RemminaPluginTool, initializes its members and references the wrapper + * functions. + * @param instance The instance of the Python plugin. + * @return Returns a new instance of the RemminaPlugin (must be freed!). + */ +RemminaPlugin* remmina_plugin_python_create_tool_plugin(PyPlugin* instance); + +G_END_DECLS + diff --git a/src/remmina_pref_dialog.c b/src/remmina_pref_dialog.c index b49eeccdd..eadbedeb5 100644 --- a/src/remmina_pref_dialog.c +++ b/src/remmina_pref_dialog.c @@ -408,7 +408,7 @@ static gboolean remmina_pref_dialog_add_pref_plugin(gchar *name, RemminaPlugin * gtk_widget_show(vbox); gtk_notebook_append_page(GTK_NOTEBOOK(remmina_pref_dialog->notebook_preferences), vbox, widget); - widget = pref_plugin->get_pref_body(); + widget = pref_plugin->get_pref_body(pref_plugin); gtk_box_pack_start(GTK_BOX(vbox), widget, FALSE, FALSE, 0); return FALSE; diff --git a/src/remmina_protocol_widget.c b/src/remmina_protocol_widget.c index 070b05c0e..9703d4cc5 100644 --- a/src/remmina_protocol_widget.c +++ b/src/remmina_protocol_widget.c @@ -282,15 +282,21 @@ void remmina_protocol_widget_open_connection_real(gpointer data) feature->type = REMMINA_PROTOCOL_FEATURE_TYPE_TOOL; feature->id = REMMINA_PROTOCOL_FEATURE_TOOL_SSH; feature->opt1 = _("Connect via SSH from a new terminal"); + feature->opt1_type_hint = REMMINA_TYPEHINT_STRING; feature->opt2 = "utilities-terminal"; + feature->opt2_type_hint = REMMINA_TYPEHINT_STRING; feature->opt3 = NULL; + feature->opt3_type_hint = REMMINA_TYPEHINT_UNDEFINED; feature++; feature->type = REMMINA_PROTOCOL_FEATURE_TYPE_TOOL; feature->id = REMMINA_PROTOCOL_FEATURE_TOOL_SFTP; feature->opt1 = _("Open SFTP transfer…"); + feature->opt1_type_hint = REMMINA_TYPEHINT_STRING; feature->opt2 = "folder-remote"; + feature->opt2_type_hint = REMMINA_TYPEHINT_STRING; feature->opt3 = NULL; + feature->opt3_type_hint = REMMINA_TYPEHINT_UNDEFINED; feature++; } feature->type = REMMINA_PROTOCOL_FEATURE_TYPE_END; |