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

gitlab.com/Remmina/Remmina.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntenore Gatta (tmow) <antenore@simbiosi.org>2021-11-09 17:05:38 +0300
committerAntenore Gatta (tmow) <antenore@simbiosi.org>2021-11-09 17:05:38 +0300
commite7c12fe745b2e809895d9c1c507088d95892b32d (patch)
treed45e991b7d043f8031e89376cdb0d58e753d985f /plugins
parentbf9db08cf74b2c6fcb11a06947aa705738a2c95e (diff)
parentfb281d6adf69d76eefe4110cc8d0691836a76de4 (diff)
Merge branch 'feature/x2go-resuming-session-dialog' into 'master'
X2Go: Rewrite dialog-system; Ask users which session to resume... See merge request Remmina/Remmina!2328
Diffstat (limited to 'plugins')
-rw-r--r--plugins/x2go/x2go_plugin.c1405
1 files changed, 1172 insertions, 233 deletions
diff --git a/plugins/x2go/x2go_plugin.c b/plugins/x2go/x2go_plugin.c
index 8af7b3c85..f0f31f653 100644
--- a/plugins/x2go/x2go_plugin.c
+++ b/plugins/x2go/x2go_plugin.c
@@ -60,39 +60,63 @@
gpdata->available_features ? (g_list_find_custom( \
gpdata->available_features, \
feature, \
- rmplugin_x2go_safe_strcmp \
+ (GCompareFunc) g_strcmp0 \
) ? TRUE : FALSE) : FALSE
+#define FEATURE_NOT_AVAIL_STR(feature) \
+ g_strdup_printf(_("The command-line feature '%s' is not available! Attempting " \
+ "to start PyHoca-CLI without using this feature…"), feature)
+
#define GET_PLUGIN_DATA(gp) \
(RemminaPluginX2GoData*) g_object_get_data(G_OBJECT(gp), "plugin-data")
+// --------- SESSIONS ------------
+#define SET_RESUME_SESSION(gp, resume_data) \
+ g_object_set_data_full(G_OBJECT(gp), "resume-session-data", \
+ resume_data, \
+ g_free)
+
+#define GET_RESUME_SESSION(gp) \
+ (gchar*) g_object_get_data(G_OBJECT(gp), "resume-session-data")
+
+// A session is selected if the returning value is something other than 0.
+#define IS_SESSION_SELECTED(gp) \
+ g_object_get_data(G_OBJECT(gp), "session-selected") ? TRUE : FALSE
+
+// We don't use the function as a real pointer but rather as a boolean value.
+#define SET_SESSION_SELECTED(gp, is_session_selected) \
+ g_object_set_data_full(G_OBJECT(gp), "session-selected", \
+ is_session_selected, \
+ NULL)
+// -------------------
+
#define SET_DIALOG_DATA(gp, ddata) \
g_object_set_data_full(G_OBJECT(gp), "dialog-data", ddata, g_free);
#define GET_DIALOG_DATA(gp) \
- (DialogData*) g_object_get_data(G_OBJECT(gp), "dialog-data");
+ (struct _DialogData*) g_object_get_data(G_OBJECT(gp), "dialog-data");
-#define REMMINA_PLUGIN_INFO(fmt, ...)\
+#define REMMINA_PLUGIN_INFO(fmt, ...) \
rm_plugin_service->_remmina_info("[%s] " fmt, \
PLUGIN_NAME, ##__VA_ARGS__)
-#define REMMINA_PLUGIN_MESSAGE(fmt, ...)\
+#define REMMINA_PLUGIN_MESSAGE(fmt, ...) \
rm_plugin_service->_remmina_message("[%s] " fmt, \
PLUGIN_NAME, ##__VA_ARGS__)
-#define REMMINA_PLUGIN_DEBUG(fmt, ...)\
+#define REMMINA_PLUGIN_DEBUG(fmt, ...) \
rm_plugin_service->_remmina_debug(__func__, "[%s] " fmt, \
PLUGIN_NAME, ##__VA_ARGS__)
-#define REMMINA_PLUGIN_WARNING(fmt, ...)\
+#define REMMINA_PLUGIN_WARNING(fmt, ...) \
rm_plugin_service->_remmina_warning(__func__, "[%s] " fmt, \
PLUGIN_NAME, ##__VA_ARGS__)
-#define REMMINA_PLUGIN_ERROR(fmt, ...)\
+#define REMMINA_PLUGIN_ERROR(fmt, ...) \
rm_plugin_service->_remmina_error(__func__, "[%s] " fmt, \
PLUGIN_NAME, ##__VA_ARGS__)
-#define REMMINA_PLUGIN_CRITICAL(fmt, ...)\
+#define REMMINA_PLUGIN_CRITICAL(fmt, ...) \
rm_plugin_service->_remmina_critical(__func__, "[%s] " fmt, \
PLUGIN_NAME, ##__VA_ARGS__)
@@ -110,9 +134,41 @@
static RemminaPluginService *rm_plugin_service = NULL;
+/**
+ * @brief Can be used to pass custom user data between functions and threads.
+ * *AND* pass the useful RemminaProtocolWidget with it along.
+ */
+typedef struct _X2GoCustomUserData {
+ RemminaProtocolWidget* gp;
+ gpointer user_data;
+} X2GoCustomUserData;
+
+/**
+ * @brief Used for the session chooser dialog (GtkListStore)
+ * See the example at: https://docs.gtk.org/gtk3/class.ListStore.html
+ * The order is the exact same as the user sees in the dialog.
+ * SESSION_NUM_PROPERTIES is used to keep count of the properties
+ * and it must be the last object.
+ */
+enum SESSION_PROPERTIES {
+ SESSION_DISPLAY = 0,
+ SESSION_STATUS,
+ SESSION_SESSION_ID,
+ SESSION_CREATE_DATE,
+ SESSION_SUSPENDED_SINCE,
+ SESSION_AGENT_PID,
+ SESSION_USERNAME,
+ SESSION_HOSTNAME,
+ SESSION_COOKIE,
+ SESSION_GRAPHIC_PORT,
+ SESSION_SND_PORT,
+ SESSION_SSHFS_PORT,
+ SESSION_NUM_PROPERTIES // Must be last. Counts all enum elements.
+};
+
// Following str2int code was adapted from Stackoverflow:
// https://stackoverflow.com/questions/7021725/how-to-convert-a-string-to-integer-in-c
-typedef enum {
+typedef enum _str2int_errno {
STR2INT_SUCCESS,
STR2INT_OVERFLOW,
STR2INT_UNDERFLOW,
@@ -157,140 +213,102 @@ str2int_errno str2int(gint *out, gchar *s, gint base)
}
/**
- * @param data Actual string to split
- * @param delim Used as delimeter character for splitting string
- * @param occurences How many times did the delimeter occur
- * @returns gchar**, so a gchar* list of all occurences.
- *
- * @brief Splits a string into a gchar* list using delim as a single-character delimeter.
- *
- */
-static gchar** rmplugin_x2go_split_string(gchar* data, gchar delim, guint *occurences)
-{
- // Counts the occurence of 'delim', so the amount of numbers passed.
- guint delim_occurence = 0;
- // work on a copy of the string, because strchr alters the string.
- gchar *pch = strchr(g_strdup(data), delim);
- while (pch != NULL) {
- delim_occurence++;
- pch = strchr(pch + 1, delim);
- }
-
- gchar **returning_string_list = NULL;
- // We are just storing gchar pointers not actual gchars.
- returning_string_list = malloc(sizeof(gchar*) * (delim_occurence + 1));
- if (!returning_string_list) {
- REMMINA_PLUGIN_CRITICAL("Could not allocate memory!");
- return NULL;
- }
-
- (*occurences) = 0;
- // Split 'data' into array 'returning_string_list' using 'delim' as delimiter.
- gchar *ptr = strtok(g_strdup(data), &delim);
- for(gint j = 0; (j <= delim_occurence && ptr != NULL); j++) {
- // Add occurence to list
- returning_string_list[j] = g_strdup(ptr);
-
- // Get next occurence
- ptr = strtok(NULL, &delim);
-
- (*occurences)++;
- }
-
- if (*occurences <= 0) {
- return NULL;
- }
-
- return returning_string_list;
-}
-
-/**
- * @brief Wrapper for strcmp which doesn't throw an exception
- * when 'a' or 'b' are a nullpointer.
- */
-static gint rmplugin_x2go_safe_strcmp(gconstpointer a, gconstpointer b) {
- if (a && b) return strcmp(a, b);
- return -1;
-}
-
-/**
* DialogData:
* @param flags see GtkDialogFlags
* @param type see GtkMessageType
* @param buttons see GtkButtonsType
* @param title Title of the Dialog
* @param message Message of the Dialog
- * @param callbackfunc A GCallback function like: \n
- * callback(RemminaProtocolWidget *gp, GtkWidget *dialog) \n
- * which will be executed on the dialogs 'response' signal.
- * The callback function is obliged to destroy the dialog widget.
+ * @param callbackfunc A GCallback function which will be executed on the dialogs
+ * 'response' signal. Allowed to be NULL. \n
+ * The callback function is obliged to destroy the dialog widget. \n
+ * @param dialog_factory A user-defined callback function that is called when it is time
+ * to build the actual GtkDialog. \n
+ * Can be used to build custom dialogs. Allowed to be NULL.
*
- * The `DialogData` structure contains all info needed to open a
- * GTK dialog with rmplugin_x2go_open_dialog() \n
+ *
+ * The `DialogData` structure contains all info needed to open a GTK dialog with
+ * rmplugin_x2go_open_dialog()
*
* Quick example of a callback function: \n
- * static void rmplugin_x2go_test_callback(RemminaProtocolWidget *gp, gint response_id, \n
- * GtkDialog *self) { \n
+ * static gboolean rmplugin_x2go_test_callback(RemminaProtocolWidget *gp, gint response_id, \n
+ * GtkDialog *self) { \n
* REMMINA_PLUGIN_DEBUG("response: %i", response_id); \n
* if (response_id == GTK_RESPONSE_OK) { \n
* REMMINA_PLUGIN_DEBUG("OK!"); \n
* } \n
* gtk_widget_destroy(self); \n
+ * return G_SOURCE_REMOVE; \n
* }
*
*/
-struct _DialogData {
- /** see GtkWindow */
- GtkWindow* parent;
- /** see GtkDialogFlags */
- GtkDialogFlags flags;
- /** see GtkMessageType */
- GtkMessageType type;
- /** see GtkButtonsType */
- GtkButtonsType buttons;
- /** Title of the Dialog */
- gchar* title;
- /** Message of the Dialog */
- gchar* message;
- /** Calls this function if
- * the user pressed a button. */
- GCallback callbackfunc;
+struct _DialogData
+{
+ GtkWindow *parent;
+ GtkDialogFlags flags;
+ GtkMessageType type;
+ GtkButtonsType buttons;
+ gchar *title;
+ gchar *message;
+ GCallback callbackfunc;
+
+ // If the dialog needs to be custom.
+ GCallback dialog_factory_func;
+ gpointer dialog_factory_data;
};
-typedef struct _DialogData DialogData;
/**
* @param gp getting DialogData via dialog-data saved in gp.
- * See define GET_DIALOG_DATA
+ * See define GET_DIALOG_DATA
+ * @returns: FALSE. This source should be removed from main loop.
+ * #G_SOURCE_CONTINUE and #G_SOURCE_REMOVE are more memorable
+ * names for the return value.
*/
-static void rmplugin_x2go_open_dialog(RemminaProtocolWidget *gp)
+static gboolean rmplugin_x2go_open_dialog(RemminaProtocolWidget *gp)
{
REMMINA_PLUGIN_DEBUG("Function entry.");
- DialogData *ddata = GET_DIALOG_DATA(gp);
+ struct _DialogData* ddata = GET_DIALOG_DATA(gp);
if (ddata) {
// Can't check type, flags or buttons
// because they are enums and '0' is a valid value
if (!ddata->title || !ddata->message) {
REMMINA_PLUGIN_CRITICAL("%s", _("Broken `DialogData`! Aborting…"));
- return;
+ return G_SOURCE_REMOVE;
}
} else {
REMMINA_PLUGIN_CRITICAL("%s", _("Can't retrieve `DialogData`! Aborting…"));
- return;
+ return G_SOURCE_REMOVE;
}
REMMINA_PLUGIN_DEBUG("`DialogData` checks passed. Now showing dialog…");
- GtkWidget *widget_gtk_dialog;
- widget_gtk_dialog = gtk_message_dialog_new(ddata->parent,
- ddata->flags,
- ddata->type,
- ddata->buttons,
- ddata->title);
+ GtkWidget* widget_gtk_dialog = NULL;
- gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG (widget_gtk_dialog),
- ddata->message);
+ if (ddata->dialog_factory_func != NULL) {
+ REMMINA_PLUGIN_DEBUG("Calling *custom* dialog factory function…");
+ GCallback dialog_factory_func = G_CALLBACK(ddata->dialog_factory_func);
+ gpointer dialog_factory_data = ddata->dialog_factory_data;
+
+ // Calling dialog_factory_func(gp, dialog_factory_data);
+ widget_gtk_dialog = ((GtkWidget* (*)(RemminaProtocolWidget*, gpointer))
+ dialog_factory_func)(gp, dialog_factory_data);
+ } else {
+ widget_gtk_dialog = gtk_message_dialog_new(ddata->parent,
+ ddata->flags,
+ ddata->type,
+ ddata->buttons,
+ ddata->title);
+
+ gtk_message_dialog_format_secondary_text(
+ GTK_MESSAGE_DIALOG(widget_gtk_dialog), ddata->message);
+ }
+
+ if (!widget_gtk_dialog) {
+ REMMINA_PLUGIN_CRITICAL("Error! Aborting.");
+ return G_SOURCE_REMOVE;
+ }
if (ddata->callbackfunc) {
g_signal_connect_swapped(G_OBJECT(widget_gtk_dialog), "response",
@@ -306,6 +324,368 @@ static void rmplugin_x2go_open_dialog(RemminaProtocolWidget *gp)
// Delete ddata object and reference 'dialog-data' in gp.
g_object_set_data(G_OBJECT(gp), "dialog-data", NULL);
+
+ return G_SOURCE_REMOVE;
+}
+
+/**
+ * @brief These define the responses of session-chooser-dialog's buttons.
+ */
+enum SESSION_CHOOSER_RESPONSE_TYPE {
+ SESSION_CHOOSER_RESPONSE_NEW = 0,
+ SESSION_CHOOSER_RESPONSE_CHOOSE,
+};
+
+/**
+ * @brief Finds a child GtkWidget of a parent GtkWidget.
+ * Copied from https://stackoverflow.com/a/23497087 ;)
+ *
+ * @param parent Parent GtkWidget*
+ * @param name Name string of child. (Must be set before, er else it will be a
+ * default string)
+ * @return GtkWidget*
+ */
+static GtkWidget* rmplugin_x2go_find_child(GtkWidget* parent, const gchar* name)
+{
+ const gchar* parent_name = gtk_widget_get_name((GtkWidget*) parent);
+ if (g_ascii_strcasecmp(parent_name, (gchar*) name) == 0) {
+ return parent;
+ }
+
+ if (GTK_IS_BIN(parent)) {
+ GtkWidget *child = gtk_bin_get_child(GTK_BIN(parent));
+ return rmplugin_x2go_find_child(child, name);
+ }
+
+ if (GTK_IS_CONTAINER(parent)) {
+ GList *children = gtk_container_get_children(GTK_CONTAINER(parent));
+ while (children != NULL) {
+ GtkWidget *widget = rmplugin_x2go_find_child(children->data, name);
+ if (widget != NULL) {
+ return widget;
+ }
+
+ children = g_list_next(children);
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * @brief Gets executed on "row-activated" signal. It is emitted when the method when
+ * the user double clicks a treeview row. It is also emitted when a non-editable
+ * row is selected and one of the keys: Space, Shift+Space, Return or Enter is
+ * pressed.
+ */
+static gboolean rmplugin_x2go_session_chooser_row_activated(GtkTreeView *treeview,
+ GtkTreePath *path,
+ GtkTreeViewColumn *column,
+ X2GoCustomUserData *custom_data)
+{
+ REMMINA_PLUGIN_DEBUG("Function entry.");
+
+ // Safety first.
+ g_assert(custom_data);
+ g_assert(custom_data->gp);
+ g_assert(custom_data->user_data);
+
+ GtkWidget* dialog = GTK_WIDGET(custom_data->user_data);
+ gchar *session_id;
+ GtkTreeIter iter;
+ GtkTreeModel *model = gtk_tree_view_get_model(treeview);
+
+ if (gtk_tree_model_get_iter(model, &iter, path)) {
+ gtk_tree_model_get(GTK_TREE_MODEL(model), &iter,
+ SESSION_SESSION_ID, &session_id, -1);
+
+ // Silent bail out.
+ if (!session_id || strlen(session_id) <= 0) return G_SOURCE_REMOVE;
+
+ SET_RESUME_SESSION(custom_data->gp, session_id);
+
+ // Unstucking main process. Telling it that a session has been selected.
+ // We use a trick here. As long as there is something other than 0
+ // stored, a session is selected. So we use the gpointer as a gboolean.
+ SET_SESSION_SELECTED(custom_data->gp, (gpointer) TRUE);
+ gtk_widget_hide(GTK_WIDGET(dialog));
+ gtk_widget_destroy(GTK_WIDGET(dialog));
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+/**
+ * @brief Builds a dialog which contains all found X2Go-Sessions. of the remote server
+ * And gives the user the option to choose between an existing session or
+ * to create a new one.
+ *
+ * @param gp Gets used to get the struct _Dialogdata.
+ * @param sessions_list The GList* Should contain all found X2Go-Sessions.
+ * Sessions are string arrays of properties.
+ * The type of the GList is gchar**.
+ *
+ * @returns GtkWidget* custom dialog.
+ */
+static GtkWidget* rmplugin_x2go_choose_session_dialog_factory(RemminaProtocolWidget* gp,
+ GList *sessions_list)
+{
+ REMMINA_PLUGIN_DEBUG("Function entry.");
+
+ struct _DialogData* ddata = GET_DIALOG_DATA(gp);
+
+ if (!ddata || !sessions_list || !ddata->title) {
+ REMMINA_PLUGIN_CRITICAL("%s", _("Couldn't retrieve valid `DialogData` or "
+ "`sessions_list`! Aborting…"));
+ return FALSE;
+ }
+
+ GtkWidget *widget_gtk_dialog = NULL;
+ widget_gtk_dialog = gtk_dialog_new_with_buttons(ddata->title, ddata->parent,
+ ddata->flags, _("_New"),
+ SESSION_CHOOSER_RESPONSE_NEW,
+ _("_Select session"),
+ SESSION_CHOOSER_RESPONSE_CHOOSE,
+ NULL);
+
+ #define DEFAULT_DIALOG_WIDTH 500
+ #define DEFAULT_DIALOG_HEIGHT (DEFAULT_DIALOG_WIDTH * 9) / 16
+
+ gtk_widget_set_size_request(GTK_WIDGET(widget_gtk_dialog),
+ DEFAULT_DIALOG_WIDTH, DEFAULT_DIALOG_HEIGHT);
+ gtk_window_set_default_size(GTK_WINDOW(widget_gtk_dialog),
+ DEFAULT_DIALOG_WIDTH, DEFAULT_DIALOG_HEIGHT);
+
+ gtk_window_set_resizable(GTK_WINDOW(widget_gtk_dialog), TRUE);
+
+ GtkWidget *scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+ //gtk_widget_show(scrolled_window);
+
+ gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(
+ GTK_DIALOG(widget_gtk_dialog))
+ ), GTK_WIDGET(scrolled_window), TRUE, TRUE, 5);
+
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW (scrolled_window),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+
+
+ GType types[SESSION_NUM_PROPERTIES];
+
+ // First to last in SESSION_PROPERTIES.
+ for (gint i = 0; i < SESSION_NUM_PROPERTIES; ++i) {
+ // Everything is a String.
+ // If that changes one day, you could implement a switch case here.
+ // But you would propably need a *lot* of refactoring.
+ // Especially in the session parser.
+ types[i] = G_TYPE_STRING;
+ }
+
+ // create tree view
+ GtkListStore *store = gtk_list_store_newv(SESSION_NUM_PROPERTIES, types);
+ GtkWidget *tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
+ g_object_unref (G_OBJECT (store)); // tree now holds reference
+ gtk_widget_set_size_request(tree_view, -1, 300);
+ gtk_widget_set_name(GTK_WIDGET(tree_view), "session_chooser_treeview");
+
+ //create list view columns
+ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree_view), TRUE);
+ gtk_tree_view_set_headers_clickable (GTK_TREE_VIEW(tree_view), FALSE);
+ gtk_tree_view_set_enable_search(GTK_TREE_VIEW(tree_view), TRUE);
+ gtk_widget_show (tree_view);
+ gtk_container_add (GTK_CONTAINER(scrolled_window), tree_view);
+
+ GtkTreeViewColumn *tree_view_col = NULL;
+ GtkCellRenderer *cell_renderer = NULL;
+ gchar *header_title = NULL;
+
+ // First to last in SESSION_PROPERTIES.
+ for (gint i = 0; i < SESSION_NUM_PROPERTIES; ++i) {
+ switch (i) {
+ // I think we can close one eye here regarding max line-length.
+ case SESSION_DISPLAY: header_title = g_strdup(_("Display")); break;
+ case SESSION_STATUS: header_title = g_strdup(_("Status")); break;
+ case SESSION_SESSION_ID: header_title = g_strdup(_("Session ID")); break;
+ case SESSION_CREATE_DATE: header_title = g_strdup(_("Create date")); break;
+ case SESSION_SUSPENDED_SINCE: header_title = g_strdup(_("Suspended since")); break;
+ case SESSION_AGENT_PID: header_title = g_strdup(_("Agent PID")); break;
+ case SESSION_USERNAME: header_title = g_strdup(_("Username")); break;
+ case SESSION_HOSTNAME: header_title = g_strdup(_("Hostname")); break;
+ case SESSION_COOKIE: header_title = g_strdup(_("Cookie")); break;
+ case SESSION_GRAPHIC_PORT: header_title = g_strdup(_("Graphic port")); break;
+ case SESSION_SND_PORT: header_title = g_strdup(_("SND port")); break;
+ case SESSION_SSHFS_PORT: header_title = g_strdup(_("SSHFS port")); break;
+ default: {
+ header_title = g_strdup_printf(_("Internal error: %s"),
+ _("Unknown property"));
+ break;
+ }
+ }
+ tree_view_col = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(tree_view_col, header_title);
+ gtk_tree_view_column_set_clickable(tree_view_col, FALSE);
+ gtk_tree_view_column_set_sizing (tree_view_col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
+ gtk_tree_view_column_set_resizable(tree_view_col, TRUE);
+
+ cell_renderer = gtk_cell_renderer_text_new();
+ gtk_tree_view_column_pack_start(tree_view_col, cell_renderer, TRUE);
+ gtk_tree_view_column_add_attribute(tree_view_col, cell_renderer, "text", i);
+ gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), tree_view_col);
+ }
+
+ GList *elem = NULL;
+ GtkTreeIter iter;
+
+ for (elem = sessions_list; elem; elem = elem->next) {
+ gchar** session = (gchar**) elem->data;
+ g_assert(session != NULL);
+
+ gtk_list_store_append(store, &iter);
+
+ for (gint i = 0; i < SESSION_NUM_PROPERTIES; i++) {
+ gchar* property = session[i];
+ GValue a = G_VALUE_INIT;
+ g_value_init(&a, G_TYPE_STRING);
+ g_assert (G_VALUE_HOLDS_STRING (&a));
+ g_value_set_static_string (&a, property);
+
+ gtk_list_store_set_value(store, &iter, i, &a);
+ }
+ }
+
+ X2GoCustomUserData *user_data = g_new0(X2GoCustomUserData, 1);
+ user_data->gp = gp;
+ user_data->user_data = widget_gtk_dialog;
+
+ g_signal_connect(tree_view, "row-activated",
+ G_CALLBACK(rmplugin_x2go_session_chooser_row_activated),
+ user_data);
+
+ return widget_gtk_dialog;
+}
+
+/**
+ * @brief Finds the GtkTreeView inside of the session chooser dialog,
+ * determines the selected row and extracts a property.
+ *
+ * @param dialog GtkWidget* the dialog itself.
+ * @param property_index Index of property.
+ *
+ * @return gchar* The value of property.
+ */
+static gchar* rmplugin_x2go_session_chooser_get_property(GtkWidget* dialog,
+ gint property_index)
+{
+ REMMINA_PLUGIN_DEBUG("Function entry.");
+
+ GtkWidget *treeview = rmplugin_x2go_find_child(GTK_WIDGET(dialog),
+ "session_chooser_treeview");
+ if (!treeview) {
+ REMMINA_PLUGIN_CRITICAL("%s", g_strdup_printf(
+ _("Internal error: %s"),
+ _("Couldn't find child GtkTreeView of session chooser dialog.")
+ ));
+ return NULL;
+ }
+
+ GtkTreeSelection *selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
+ GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
+ GList *selected_rows = gtk_tree_selection_get_selected_rows(selection, &model);
+
+ // We only support single selection.
+ gint selected_rows_num = gtk_tree_selection_count_selected_rows(selection);
+ if (selected_rows_num != 1) {
+ REMMINA_PLUGIN_CRITICAL("%s", g_strdup_printf(
+ _("Internal error: %s"),
+ _("only one session should be able to be selected.")
+ ));
+ return NULL;
+ }
+
+ // This would be very dangerous if we hadn't just
+ // checked that only one row is selected.
+ GtkTreePath *path = selected_rows->data;
+
+ GtkTreeIter iter;
+ gboolean success = gtk_tree_model_get_iter_from_string(model, &iter,
+ gtk_tree_path_to_string(path));
+ if (!success) {
+ REMMINA_PLUGIN_CRITICAL("%s", g_strdup_printf(
+ _("Internal error: %s"),
+ _("Failed to fill 'GtkTreeIter'.")
+ ));
+
+ return NULL;
+ }
+
+ gchar *property = NULL;
+ gtk_tree_model_get(model, &iter, property_index, &property, -1);
+
+ if (!property || strlen(property) <= 0) {
+ REMMINA_PLUGIN_CRITICAL("%s", g_strdup_printf(
+ _("Internal error: %s"),
+ _("Couldn't get session ID out of selected row.")
+ ));
+
+ return NULL;
+ }
+
+ return property;
+}
+
+/**
+ * @brief Gets executed on dialog's 'response' signal
+ *
+ * @param gp Needed by SET_RESUME_SESSION to set the session id of the selected session.
+ * @param response_id See GTK 'response' signal.
+ * @param self The dialog itself.
+ *
+ * @return gboolean Used by GTK.
+ */
+static gboolean rmplugin_x2go_session_chooser_callback(RemminaProtocolWidget* gp,
+ gint response_id,
+ GtkDialog *self)
+{
+ REMMINA_PLUGIN_DEBUG("Function entry.");
+
+ if (response_id == SESSION_CHOOSER_RESPONSE_CHOOSE) {
+ gchar* session_id = rmplugin_x2go_session_chooser_get_property(
+ GTK_WIDGET(self),
+ SESSION_SESSION_ID
+ );
+
+ if (!session_id || strlen(session_id) <= 0) {
+ REMMINA_PLUGIN_DEBUG(
+ "%s",
+ _("Couldn't get session ID from session chooser dialog.")
+ );
+ SET_RESUME_SESSION(gp, NULL);
+ } else {
+ SET_RESUME_SESSION(gp, session_id);
+
+ REMMINA_PLUGIN_INFO("%s", g_strdup_printf(
+ _("Resuming session: '%s'"),
+ session_id
+ ));
+ }
+ } else if (response_id == SESSION_CHOOSER_RESPONSE_NEW) {
+ REMMINA_PLUGIN_DEBUG("User explicitly wishes a new session. "
+ "Creating a new session then.");
+ SET_RESUME_SESSION(gp, NULL);
+ } else {
+ REMMINA_PLUGIN_DEBUG("User clicked dialog away. "
+ "Creating a new session then.");
+ SET_RESUME_SESSION(gp, NULL);
+ }
+
+ // Unstucking main process. Telling it that a session has been selected.
+ // We use a trick here. As long as there is something other
+ // than 0 stored, a session is selected. So we use the gpointer as a gboolean.
+ SET_SESSION_SELECTED(gp, (gpointer) TRUE);
+
+ gtk_widget_destroy(GTK_WIDGET(self));
+
+ return G_SOURCE_REMOVE;
}
typedef struct _RemminaPluginX2GoData {
@@ -431,6 +811,11 @@ static void rmplugin_x2go_remove_window_id (Window window_id)
pthread_mutex_unlock(&remmina_x2go_init_mutex);
}
+/**
+ * @returns: FALSE. This source should be removed from main loop.
+ * #G_SOURCE_CONTINUE and #G_SOURCE_REMOVE are more memorable
+ * names for the return value.
+ */
static gboolean rmplugin_x2go_cleanup(RemminaProtocolWidget *gp)
{
REMMINA_PLUGIN_DEBUG("Function entry.");
@@ -438,7 +823,7 @@ static gboolean rmplugin_x2go_cleanup(RemminaProtocolWidget *gp)
RemminaPluginX2GoData *gpdata = GET_PLUGIN_DATA(gp);
if (gpdata == NULL) {
REMMINA_PLUGIN_DEBUG("Exiting since gpdata is already 'NULL'…");
- return FALSE;
+ return G_SOURCE_REMOVE;
}
if (gpdata->thread) {
@@ -465,7 +850,7 @@ static gboolean rmplugin_x2go_cleanup(RemminaProtocolWidget *gp)
g_object_steal_data(G_OBJECT(gp), "plugin-data");
rm_plugin_service->protocol_plugin_signal_connection_closed(gp);
- return FALSE;
+ return G_SOURCE_REMOVE;
}
static gboolean rmplugin_x2go_close_connection(RemminaProtocolWidget *gp)
@@ -477,23 +862,25 @@ static gboolean rmplugin_x2go_close_connection(RemminaProtocolWidget *gp)
if (gpdata->disconnected) {
REMMINA_PLUGIN_DEBUG("Doing nothing since the plugin is already disconnected.");
- return FALSE;
+ return G_SOURCE_REMOVE;
}
rmplugin_x2go_cleanup(gp);
- return TRUE;
+ // Try again.
+ return G_SOURCE_CONTINUE;
}
static void rmplugin_x2go_pyhoca_cli_exited(GPid pid,
gint status,
- struct _RemminaProtocolWidget *gp)
+ RemminaProtocolWidget *gp)
{
REMMINA_PLUGIN_DEBUG("Function entry.");
RemminaPluginX2GoData *gpdata = GET_PLUGIN_DATA(gp);
if (!gpdata) {
- REMMINA_PLUGIN_DEBUG("Doing nothing since gpdata is already 'NULL'.");
+ REMMINA_PLUGIN_DEBUG("Doing nothing as the disconnection "
+ "has already been handled.");
return;
}
@@ -505,7 +892,7 @@ static void rmplugin_x2go_pyhoca_cli_exited(GPid pid,
REMMINA_PLUGIN_CRITICAL("%s", _("PyHoca-CLI exited unexpectedly. "
"This connection will now be closed."));
- DialogData *ddata = g_new0(DialogData, 1);
+ struct _DialogData *ddata = g_new0(struct _DialogData, 1);
SET_DIALOG_DATA(gp, ddata);
ddata->parent = NULL;
ddata->flags = GTK_DIALOG_MODAL;
@@ -527,63 +914,115 @@ static void rmplugin_x2go_pyhoca_cli_exited(GPid pid,
}
/**
- * @brief Get all available pyhoca-cli features by
- * executing `pyhoca-cli --list-cmdline-features`.
+ * @brief This function synchronously spawns a pyhoca-cli process with argv as arguments.
+ * @param argc Number of arguments.
+ * @param argv Arguments as string array. \n
+ * Last elements has to be NULL. \n
+ * Strings will get freed automatically.
+ * @param error Will be filled with an error message on fail.
+ * @param env String array of enviroment variables. \n
+ * The list is NULL terminated and each item in
+ * the list is of the form ‘NAME=VALUE’.
*
- * @returns Returns either a gchar* with all features,
- * separated by a '\n' or NULL if it failed.
+ * @returns Returns either standard output string or NULL if it failed.
*/
-static gchar* rmplugin_x2go_get_pyhoca_features()
+static gchar* rmplugin_x2go_spawn_pyhoca_process(guint argc, gchar* argv[],
+ GError** error, gchar** env)
{
REMMINA_PLUGIN_DEBUG("Function entry.");
- // We will now start pyhoca-cli with only the '--list-cmdline-features' option
- // and depending on the exit code and stdout output we will determine if some
- // features are available or not.
+ if (!argv) {
+ gchar* errmsg = g_strdup_printf(
+ _("Internal error: %s"),
+ _("parameter 'argv' is 'NULL'.")
+ );
+ REMMINA_PLUGIN_CRITICAL("%s", errmsg);
+ g_set_error(error, 1, 1, errmsg);
+ return NULL;
+ }
+
+ if (!error) {
+ // Can't report error message back since 'error' is NULL.
+ REMMINA_PLUGIN_CRITICAL("%s", g_strdup_printf(
+ _("Internal error: %s"),
+ _("parameter 'error' is 'NULL'.")
+ ));
+ return NULL;
+ }
+
+ if (!env || !env[0]) {
+ gchar* errmsg = g_strdup_printf(
+ _("Internal error: %s"),
+ _("parameter 'env' is either invalid or uninitialized.")
+ );
+ REMMINA_PLUGIN_CRITICAL("%s", errmsg);
+ g_set_error(error, 1, 1, errmsg);
+ return NULL;
+ }
- gchar *argv[50];
- gint argc = 0;
- GError *error = NULL;
gint exit_code = 0;
gchar *standard_out;
- // just supresses pyhoca-cli help message. (When pyhoca-cli has old version)
+ // Just supresses pyhoca-cli's help message when pyhoca-cli's version is too old.
gchar *standard_err;
- argv[argc++] = g_strdup("pyhoca-cli");
- argv[argc++] = g_strdup("--list-cmdline-features");
- argv[argc++] = NULL;
-
- gchar **envp = g_get_environ();
- gboolean success_ret = g_spawn_sync (NULL, argv, envp, G_SPAWN_SEARCH_PATH,
- NULL, NULL, &standard_out, &standard_err,
- &exit_code, &error);
+ gboolean success_ret = g_spawn_sync(NULL, argv, env, G_SPAWN_SEARCH_PATH, NULL,
+ NULL, &standard_out, &standard_err,
+ &exit_code, error);
REMMINA_PLUGIN_INFO("%s", _("Started PyHoca-CLI with the following arguments:"));
// Print every argument except passwords. Free all arg strings.
for (gint i = 0; i < argc - 1; i++) {
- if (strcmp(argv[i], "--password") == 0) {
+ if (g_strcmp0(argv[i], "--password") == 0) {
g_printf("%s ", argv[i]);
g_printf("XXXXXX ");
- g_free (argv[i]);
- g_free (argv[++i]);
+ g_free(argv[i]);
+ g_free(argv[++i]);
continue;
} else {
g_printf("%s ", argv[i]);
- g_free (argv[i]);
+ g_free(argv[i]);
}
}
g_printf("\n");
- if (!success_ret || error || strcmp(standard_out, "") == 0 || exit_code) {
- if (!error) {
- REMMINA_PLUGIN_WARNING("%s",
- g_strdup_printf(_("Could not retrieve "
- "PyHoca-CLI's command-line features! Exit code: %i"),
- exit_code));
+ REMMINA_PLUGIN_DEBUG("%s", _("Started PyHoca-CLI with the "
+ "following environment variables:"));
+ REMMINA_PLUGIN_DEBUG("%s", g_strjoinv("\n", env));
+
+ if (standard_err && strlen(standard_err) > 0) {
+ if (g_str_has_prefix(standard_err, "pyhoca-cli: error: a socket error "
+ "occured while establishing the connection:")) {
+ // Log error into GUI.
+ gchar* errmsg = g_strdup_printf(
+ _("The necessary PyHoca-CLI process has encountered a "
+ "internet connection problem.")
+ );
+
+ // Log error into debug window and stdout
+ REMMINA_PLUGIN_CRITICAL("%s:\n%s", errmsg, standard_err);
+ g_set_error(error, 1, 1, errmsg);
+ return NULL;
+ } else {
+ gchar* errmsg = g_strdup_printf(
+ _("An unknown error occured while trying to start "
+ "PyHoca-CLI.")
+ );
+ REMMINA_PLUGIN_CRITICAL("%s:\n%s", errmsg, standard_err);
+ g_set_error(error, 1, 1, errmsg);
+ return NULL;
+ }
+ } else if (!success_ret || (*error) || g_strcmp0(standard_out, "") == 0 || exit_code) {
+ if (!(*error)) {
+ REMMINA_PLUGIN_WARNING("%s", g_strdup_printf(
+ _("An unknown error occured while trying to start "
+ "PyHoca-CLI. Exit code: %i"), exit_code)
+ );
} else {
- REMMINA_PLUGIN_WARNING("%s",
- g_strdup_printf(_("Error: '%s'"), error->message));
- g_error_free(error);
+ REMMINA_PLUGIN_WARNING("%s", g_strdup_printf(
+ _("An unknown error occured while trying to start "
+ "PyHoca-CLI. Exit code: %i. Error: '%s'"),
+ exit_code, (*error)->message)
+ );
}
return NULL;
@@ -592,10 +1031,9 @@ static gchar* rmplugin_x2go_get_pyhoca_features()
return standard_out;
}
-
/**
* @brief Saves s_password and s_username if set.
- * @returns either TRUE or FALSE. If FALSE gets returned `errmsg` is set.
+ * @returns either TRUE or FALSE. If FALSE gets returned, `errmsg` is set.
*/
static gboolean rmplugin_x2go_save_credentials(RemminaFile* remminafile,
gchar* s_username, gchar* s_password,
@@ -605,7 +1043,7 @@ static gboolean rmplugin_x2go_save_credentials(RemminaFile* remminafile,
// into remminafile->settings. They will be saved later, on successful
// connection, by rcw.c
if (s_password && s_username) {
- if (strcmp(s_username, "") == 0) {
+ if (g_strcmp0(s_username, "") == 0) {
g_strlcpy(errmsg, _("Can't save empty username!"), 512);
//REMMINA_PLUGIN_CRITICAL("%s", errmsg); // No need.
return FALSE;
@@ -618,11 +1056,14 @@ static gboolean rmplugin_x2go_save_credentials(RemminaFile* remminafile,
rm_plugin_service->file_set_string(remminafile, "username",
s_username);
} else {
- g_strlcpy(errmsg, _("Internal error: Could not save new credentials."), 512);
-
- REMMINA_PLUGIN_CRITICAL("%s", _("An error occured while trying to save "
- "new credentials: 's_password' or "
- "'s_username' strings were not set."));
+ g_strlcpy(errmsg, g_strdup_printf(
+ _("Internal error: %s"),
+ _("Could not save new credentials.")
+ ), 512);
+
+ REMMINA_PLUGIN_CRITICAL("%s", _("An error occured while trying to save "
+ "new credentials: 's_password' or "
+ "'s_username' strings were not set."));
return FALSE;
}
@@ -632,15 +1073,38 @@ static gboolean rmplugin_x2go_save_credentials(RemminaFile* remminafile,
/**
* @brief Asks the user for a username and password.
*
- * @param errmsg Error message if function failed.
- * @param username Default username. Gets set to new username on success.
- * @param password Default password. Gets set to new password on success.
+ * @param errmsg Pointer to error message string (set if function failed).
+ * @param username Pointer to default username. Gets set to new username on success.
+ * @param password Pointer to default password. Gets set to new password on success.
*
* @returns FALSE if auth failed and TRUE on success.
*/
-static gboolean rmplugin_x2go_get_auth(RemminaProtocolWidget *gp, gchar* errmsg,
- gchar* username, gchar* password)
+static gboolean rmplugin_x2go_get_auth(RemminaProtocolWidget *gp, gchar** errmsg,
+ gchar** default_username, gchar** default_password)
{
+ REMMINA_PLUGIN_DEBUG("Function entry.");
+
+ g_assert(errmsg != NULL);
+ g_assert(gp != NULL);
+ g_assert(default_username != NULL);
+ g_assert(default_password != NULL);
+
+ if (!(*default_username)) {
+ (*errmsg) = g_strdup_printf(
+ _("Internal error: %s"),
+ _("Parameter 'default_username' is uninitialized.")
+ );
+ REMMINA_PLUGIN_CRITICAL("%s", errmsg);
+ return FALSE;
+ }
+
+ // We can handle ((*default_password) == NULL).
+ // Password is probably NULL because something did go wrong at the secret-plugin.
+ // For example: The user didn't input a password for keyring.
+ if ((*default_password) == NULL) {
+ (*default_password) = g_strdup("");
+ }
+
gchar *s_username, *s_password;
gint ret;
gboolean save;
@@ -649,55 +1113,409 @@ static gboolean rmplugin_x2go_get_auth(RemminaProtocolWidget *gp, gchar* errmsg,
remminafile = rm_plugin_service->protocol_plugin_get_file(gp);
- disable_password_storing = rm_plugin_service->file_get_int(remminafile,
- "disablepasswordstoring",
- FALSE);
+ disable_password_storing = rm_plugin_service->file_get_int(
+ remminafile, "disablepasswordstoring", FALSE
+ );
+
ret = rm_plugin_service->protocol_plugin_init_auth(
gp, (disable_password_storing ? 0 :
REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD |
REMMINA_MESSAGE_PANEL_FLAG_USERNAME),
_("Enter X2Go credentials"),
- username, // function arg 'username' is default username
- password, // function arg 'password' is default password
- NULL,
- NULL);
-
+ (*default_username), (*default_password), NULL, NULL
+ );
if (ret == GTK_RESPONSE_OK) {
s_username = rm_plugin_service->protocol_plugin_init_get_username(gp);
s_password = rm_plugin_service->protocol_plugin_init_get_password(gp);
if (rm_plugin_service->protocol_plugin_init_get_savepassword(gp))
- rm_plugin_service->file_set_string(remminafile, "password",
- s_password);
+ rm_plugin_service->file_set_string(
+ remminafile, "password", s_password
+ );
// Should be renamed to protocol_plugin_init_get_savecredentials()?!
save = rm_plugin_service->protocol_plugin_init_get_savepassword(gp);
if (save) {
- if (!rmplugin_x2go_save_credentials(remminafile,
- s_username, s_password,
- errmsg)) {
-
+ if (!rmplugin_x2go_save_credentials(remminafile, s_username,
+ s_password, (*errmsg))) {
+ return FALSE;
}
}
if (s_username) {
- g_stpcpy(username, s_username);
+ (*default_username) = g_strdup(s_username);
g_free(s_username);
}
if (s_password) {
- g_stpcpy(password, s_password);
+ (*default_password) = g_strdup(s_password);
g_free(s_password);
}
} else {
- g_strlcpy(errmsg, "Authentication cancelled. Aborting…", 512);
- REMMINA_PLUGIN_DEBUG("%s", errmsg);
+ (*errmsg) = g_strdup("Authentication cancelled. Aborting…");
return FALSE;
}
return TRUE;
}
+/**
+ * @brief Stores all necessary information needed for retrieving sessions from
+ * a X2Go server.
+ */
+struct _ConnectionData {
+ gchar* host;
+ gchar* username;
+ gchar* password;
+};
+
+/**
+ * @brief Executes 'pyhoca-cli --list-sessions' for username@host.
+ *
+ * @param gp RemminaProtocolWidget* is used to get the x2go-plugin data.
+ * @param error This is where a error message will be when NULL gets returned.
+ * @param connect_data struct _ConnectionData* which stores all necessary information
+ * needed for retrieving sessions from a X2Go server.
+ *
+ * @returns Standard output of pyhoca-cli command.
+ * If NULL then errmsg is set to user-friendly error message.
+ */
+static gchar* rmplugin_x2go_get_pyhoca_sessions(RemminaProtocolWidget* gp, GError **error,
+ struct _ConnectionData* connect_data)
+{
+ REMMINA_PLUGIN_DEBUG("Function entry.");
+ RemminaPluginX2GoData* gpdata = GET_PLUGIN_DATA(gp);
+
+ gchar *host = NULL;
+ gchar *username = NULL;
+ gchar *password = NULL;
+
+ if (!connect_data ||
+ !connect_data->host ||
+ !connect_data->username ||
+ !connect_data->password ||
+ strlen(connect_data->host) <= 0 ||
+ strlen(connect_data->username) <= 0)
+ // Allow empty passwords. Maybe the user wants to connect via public key?
+ {
+ g_set_error(error, 1, 1, g_strdup_printf(
+ _("Internal error: %s"),
+ _("'Invalid connection data.'")
+ ));
+ return NULL;
+ } else {
+ host = connect_data->host;
+ username = connect_data->username;
+ password = connect_data->password;
+ }
+
+ // We will now start pyhoca-cli with only the '--list-sessions' option.
+
+ gchar *argv[50];
+ gint argc = 0;
+
+ argv[argc++] = g_strdup("pyhoca-cli");
+ argv[argc++] = g_strdup("--list-sessions");
+
+ argv[argc++] = g_strdup("--server"); // Not listed as feature.
+ argv[argc++] = g_strdup_printf("%s", host);
+
+ if (FEATURE_AVAILABLE(gpdata, "USERNAME")) {
+ argv[argc++] = g_strdup("-u");
+ if (username) {
+ argv[argc++] = g_strdup_printf("%s", username);
+ } else {
+ argv[argc++] = g_strdup_printf("%s", g_get_user_name());
+ }
+ } else {
+ g_set_error(error, 1, 1, FEATURE_NOT_AVAIL_STR("USERNAME"));
+ REMMINA_PLUGIN_WARNING("%s", FEATURE_NOT_AVAIL_STR("USERNAME"));
+ return NULL;
+ }
+
+ if (password && FEATURE_AVAILABLE(gpdata, "PASSWORD")) {
+ if (FEATURE_AVAILABLE(gpdata, "AUTH_ATTEMPTS")) {
+ argv[argc++] = g_strdup("--auth-attempts");
+ argv[argc++] = g_strdup_printf ("%i", 0);
+ } else {
+ REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("AUTH_ATTEMPTS"));
+ }
+ argv[argc++] = g_strdup("--force-password");
+ argv[argc++] = g_strdup("--password");
+ argv[argc++] = g_strdup_printf("%s", password);
+ } else if (!password) {
+ g_set_error(error, 1, 1, FEATURE_NOT_AVAIL_STR("PASSWORD"));
+ REMMINA_PLUGIN_WARNING("%s", FEATURE_NOT_AVAIL_STR("PASSWORD"));
+ return NULL;
+ }
+
+ // No need to catch feature-not-available error.
+ // `--quiet` is not that important.
+ if (FEATURE_AVAILABLE(gpdata, "QUIET")) {
+ argv[argc++] = g_strdup("--quiet");
+ }
+
+ argv[argc++] = NULL;
+
+ //#ifndef GLIB_AVAILABLE_IN_2_68
+ gchar** envp = g_get_environ();
+ gchar* envp_splitted = g_strjoinv(";", envp);
+ envp_splitted = g_strconcat(envp_splitted, ";LANG=C", (void*) NULL);
+ envp = g_strsplit(envp_splitted, ";", 0);
+ /*
+ * #else
+ * // Only available after glib version 2.68.
+ * // TODO: FIXME: NOT TESTED!
+ * GStrvBuilder* builder = g_strv_builder_new();
+ * g_strv_builder_add(builder, "LANG=C");
+ * GStrv envp = g_strv_builder_end(builder);
+ * #endif
+ */
+
+ gchar* std_out = rmplugin_x2go_spawn_pyhoca_process(argc, argv, error, envp);
+ g_strfreev(envp);
+
+ if (!std_out || *error) {
+ // If no error is set but std_out is NULL
+ // then something is not right at all.
+ // Most likely the developer forgot to add an error message. Crash.
+ g_assert((*error) != NULL);
+ return NULL;
+ }
+
+ return std_out;
+}
+
+/**
+ * @brief This function is used to parse the output of
+ * rmplugin_x2go_get_pyhoca_sessions().
+ *
+ * @param gp RemminaProtocolWidget* is used to get the x2go-plugin data.
+ * @param error This is where a error message will be when NULL gets returned.
+ * @param connect_data struct _ConnectionData* which stores all necessary information
+ * needed for retrieving sessions from a X2Go server.
+ *
+ * @returns Returns either a GList containing the IDs of every already existing session
+ * found or if the function failes, NULL.
+ *
+ * TODO: If pyhoca-cli (python-x2go) implements `--json` or similar option -> Replace
+ * entire function with JSON parsing.
+ */
+static GList* rmplugin_x2go_parse_pyhoca_sessions(RemminaProtocolWidget* gp,
+ GError **error,
+ struct _ConnectionData* connect_data)
+{
+ REMMINA_PLUGIN_DEBUG("Function entry.");
+
+ gchar *pyhoca_output = NULL;
+
+ pyhoca_output = rmplugin_x2go_get_pyhoca_sessions(gp, error, connect_data);
+ if (!pyhoca_output || *error) {
+ // If no error is set but pyhoca_output is NULL
+ // then something is not right at all.
+ // Most likely the developer forgot to add an error message. Crash.
+ g_assert((*error) != NULL);
+
+ return NULL;
+ }
+
+ gchar **lines_list = g_strsplit(pyhoca_output, "\n", -1);
+ // Assume at least two lines of output.
+ if (lines_list == NULL || lines_list[0] == NULL || lines_list[1] == NULL) {
+ g_set_error(error, 1, 1, _("Couldn't parse the output of PyHoca-CLI's "
+ "--list-sessions option. Creating a new "
+ "session now."));
+ return NULL;
+ }
+
+ gboolean found_session = FALSE;
+ GList* sessions = NULL;
+ gchar** session = NULL;
+
+ for (guint i = 0; lines_list[i] != NULL; i++) {
+ gchar* current_line = lines_list[i];
+
+ // TOO VERBOSE:
+ //REMMINA_PLUGIN_DEBUG("pyhoca-cli: %s", current_line);
+
+ // Hardcoded string "Session Name: " comes from python-x2go.
+ if (!g_str_has_prefix(current_line, "Session Name: ") && !found_session) {
+ // Doesn't begin with "Session Name: " and
+ // the current line doesn't come after that either. Skipping.
+ continue;
+ }
+
+ if (g_str_has_prefix(current_line, "Session Name: ")) {
+ gchar* session_id = NULL;
+ gchar** line_list = g_strsplit(current_line, ": ", 0);
+
+ if (line_list == NULL ||
+ line_list[0] == NULL ||
+ line_list[1] == NULL ||
+ strlen(line_list[0]) <= 0 ||
+ strlen(line_list[1]) <= 0)
+ {
+ found_session = FALSE;
+ continue;
+ }
+
+ session = malloc(sizeof(gchar*) * (SESSION_NUM_PROPERTIES+1));
+ if (!session) {
+ REMMINA_PLUGIN_CRITICAL("%s", _("Couldn't allocate "
+ "enough memory!"));
+ }
+ session[SESSION_NUM_PROPERTIES] = NULL;
+ sessions = g_list_append(sessions, session);
+
+ session_id = line_list[1];
+ session[SESSION_SESSION_ID] = session_id;
+
+ REMMINA_PLUGIN_INFO("%s", g_strdup_printf(
+ _("Found already existing X2Go session with ID: '%s'"),
+ session[SESSION_SESSION_ID])
+ );
+
+ found_session = TRUE;
+ continue;
+ }
+
+ if (!found_session) {
+ continue;
+ }
+
+ if (g_strcmp0(current_line, "-------------") == 0) {
+ continue;
+ }
+
+ gchar* value = NULL;
+ gchar** line_list = g_strsplit(current_line, ": ", 0);
+
+ if (line_list == NULL ||
+ line_list[0] == NULL ||
+ line_list[1] == NULL ||
+ strlen(line_list[0]) <= 0 ||
+ strlen(line_list[1]) <= 0)
+ {
+ // Probably the empty line at the end of every session.
+ found_session = FALSE;
+ continue;
+ }
+ value = line_list[1];
+
+ if (g_str_has_prefix(current_line, "cookie: ")) {
+ REMMINA_PLUGIN_DEBUG("cookie:\t'%s'", value);
+ session[SESSION_COOKIE] = value;
+ } else if (g_str_has_prefix(current_line, "agent PID: ")) {
+ REMMINA_PLUGIN_DEBUG("agent PID:\t'%s'", value);
+ session[SESSION_AGENT_PID] = value;
+ } else if (g_str_has_prefix(current_line, "display: ")) {
+ REMMINA_PLUGIN_DEBUG("display:\t'%s'", value);
+ session[SESSION_DISPLAY] = value;
+ } else if (g_str_has_prefix(current_line, "status: ")) {
+ REMMINA_PLUGIN_DEBUG("status:\t'%s'", value);
+ session[SESSION_STATUS] = value;
+ } else if (g_str_has_prefix(current_line, "graphic port: ")) {
+ REMMINA_PLUGIN_DEBUG("graphic port:\t'%s'", value);
+ session[SESSION_GRAPHIC_PORT] = value;
+ } else if (g_str_has_prefix(current_line, "snd port: ")) {
+ REMMINA_PLUGIN_DEBUG("snd port:\t'%s'", value);
+ session[SESSION_SND_PORT] = value;
+ } else if (g_str_has_prefix(current_line, "sshfs port: ")) {
+ REMMINA_PLUGIN_DEBUG("sshfs port:\t'%s'", value);
+ session[SESSION_SSHFS_PORT] = value;
+ } else if (g_str_has_prefix(current_line, "username: ")) {
+ REMMINA_PLUGIN_DEBUG("username:\t'%s'", value);
+ session[SESSION_USERNAME] = value;
+ } else if (g_str_has_prefix(current_line, "hostname: ")) {
+ REMMINA_PLUGIN_DEBUG("hostname:\t'%s'", value);
+ session[SESSION_HOSTNAME] = value;
+ } else if (g_str_has_prefix(current_line, "create date: ")) {
+ REMMINA_PLUGIN_DEBUG("create date:\t'%s'", value);
+ session[SESSION_CREATE_DATE] = value;
+ } else if (g_str_has_prefix(current_line, "suspended since: ")) {
+ REMMINA_PLUGIN_DEBUG("suspended since:\t'%s'", value);
+ session[SESSION_SUSPENDED_SINCE] = value;
+ } else {
+ REMMINA_PLUGIN_DEBUG("Not supported:\t'%s'", value);
+ found_session = FALSE;
+ }
+ }
+
+ if (!sessions) {
+ g_set_error(error, 1, 1,
+ _("Could not find any sessions on remote machine. Creating a new "
+ "session now.")
+ );
+
+ // returning NULL with `error` set.
+ }
+
+ return sessions;
+}
+
+/**
+ * @brief Asks the user, with the help of a dialog, whether he or she would like
+ * to continue an already existing session.
+ *
+ * @param error Is set if there is something to tell the user. \n
+ * Not necessarily an *error* message.
+ * @param connect_data Stores all necessary information needed for
+ * etrieving sessions from a X2Go server.
+ * @return gchar* ID of session. Can be 'NULL' but then 'error' is set.
+ */
+static gchar* rmplugin_x2go_ask_session(RemminaProtocolWidget *gp, GError **error,
+ struct _ConnectionData* connect_data)
+{
+ GList *sessions_list = NULL;
+ sessions_list = rmplugin_x2go_parse_pyhoca_sessions(gp, error, connect_data);
+
+ if (!sessions_list || *error) {
+ // If no error is set but sessions_list is NULL
+ // then something is not right at all.
+ // Most likely the developer forgot to add an error message. Crash.
+ g_assert(*error != NULL);
+ return NULL;
+ }
+
+ // Prep new DialogData struct.
+ struct _DialogData *ddata = g_new0(struct _DialogData, 1);
+ SET_DIALOG_DATA(gp, ddata);
+ ddata->parent = NULL;
+ ddata->flags = GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT;
+ //ddata->type = GTK_MESSAGE_QUESTION;
+ //ddata->buttons = GTK_BUTTONS_OK; // Doesn't get used in our custom factory.
+ ddata->title = _("Choose a session to resume:");
+ ddata->message = "";
+ ddata->callbackfunc = G_CALLBACK(rmplugin_x2go_session_chooser_callback);
+ ddata->dialog_factory_func = G_CALLBACK(rmplugin_x2go_choose_session_dialog_factory);
+ ddata->dialog_factory_data = sessions_list;
+
+ // Open dialog here. Dialog rmplugin_x2go_session_chooser_callback (callbackfunc)
+ // should set SET_RESUME_SESSION.
+ IDLE_ADD((GSourceFunc)rmplugin_x2go_open_dialog, gp);
+
+ guint counter = 0;
+ while (!IS_SESSION_SELECTED(gp)) {
+ // 0.5 Seconds. Give dialog chance to open.
+ usleep(500 * 1000);
+
+ // Every 5 seconds
+ if (counter % 10 == 0 || counter == 0) {
+ REMMINA_PLUGIN_INFO("%s", _("Waiting for user to select a session…"));
+ }
+ counter++;
+ }
+
+ gchar* chosen_resume_session = GET_RESUME_SESSION(gp);
+
+ if (!chosen_resume_session || strlen(chosen_resume_session) <= 0) {
+ g_set_error(error, 1, 1, _("No session was selected. Creating a new one."));
+ return NULL;
+ }
+
+ return chosen_resume_session;
+}
+
static gboolean rmplugin_x2go_exec_x2go(gchar *host,
- gint sshport,
+ gint sshport,
gchar *username,
gchar *password,
gchar *command,
@@ -705,7 +1523,7 @@ static gboolean rmplugin_x2go_exec_x2go(gchar *host,
gchar *kbdtype,
gchar *audio,
gchar *clipboard,
- gint dpi,
+ gint dpi,
gchar *resolution,
RemminaProtocolWidget *gp,
gchar *errmsg)
@@ -717,10 +1535,36 @@ static gboolean rmplugin_x2go_exec_x2go(gchar *host,
gint argc = 0;
// Sets `username` and `password`.
- if (!rmplugin_x2go_get_auth(gp, errmsg, username, password)) {
+ if (!rmplugin_x2go_get_auth(gp, &errmsg, &username, &password)) {
return FALSE;
}
+ struct _ConnectionData* connect_data = g_new0(struct _ConnectionData, 1);
+ connect_data->host = host;
+ connect_data->username = username;
+ connect_data->password = password;
+
+ GError *session_error = NULL;
+ gchar* resume_session_id = rmplugin_x2go_ask_session(gp, &session_error,
+ connect_data);
+
+ if (!resume_session_id || session_error || strlen(resume_session_id) <= 0) {
+ // If no error is set but session_id is NULL
+ // then something is not right at all.
+ // Most likely the developer forgot to add an error message. Crash.
+ g_assert(session_error != NULL);
+
+ REMMINA_PLUGIN_WARNING("%s", g_strdup_printf(
+ _("A non-critical error happened: %s"),
+ session_error->message
+ ));
+ } else {
+ REMMINA_PLUGIN_INFO("%s", g_strdup_printf(
+ _("User chose to resume session with ID: '%s'"),
+ resume_session_id
+ ));
+ }
+
argc = 0;
argv[argc++] = g_strdup("pyhoca-cli");
@@ -730,8 +1574,34 @@ static gboolean rmplugin_x2go_exec_x2go(gchar *host,
if (FEATURE_AVAILABLE(gpdata, "REMOTE_SSH_PORT")) {
argv[argc++] = g_strdup("-p");
argv[argc++] = g_strdup_printf ("%d", sshport);
+ } else {
+ REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("REMOTE_SSH_PORT"));
+ }
+
+ if (resume_session_id && strlen(resume_session_id) > 0) {
+ REMMINA_PLUGIN_INFO("%s", g_strdup_printf(
+ _("Resuming session '%s'…"),
+ resume_session_id
+ ));
+
+ if (FEATURE_AVAILABLE(gpdata, "RESUME")) {
+ argv[argc++] = g_strdup("--resume");
+ argv[argc++] = g_strdup_printf("%s", resume_session_id);
+ } else {
+ REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("RESUME"));
+ }
}
+ // Deprecated. The user either wants to continue a
+ // session or just not. No inbetween.
+ // if (!resume_session_id) {
+ // if (FEATURE_AVAILABLE(gpdata, "TRY_RESUME")) {
+ // argv[argc++] = g_strdup("--try-resume");
+ // } else {
+ // REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("TRY_RESUME"));
+ // }
+ // }
+
if (FEATURE_AVAILABLE(gpdata, "USERNAME")) {
argv[argc++] = g_strdup("-u");
if (username){
@@ -739,17 +1609,23 @@ static gboolean rmplugin_x2go_exec_x2go(gchar *host,
} else {
argv[argc++] = g_strdup_printf ("%s", g_get_user_name());
}
+ } else {
+ REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("USERNAME"));
}
if (password && FEATURE_AVAILABLE(gpdata, "PASSWORD")) {
argv[argc++] = g_strdup("--force-password");
argv[argc++] = g_strdup("--password");
argv[argc++] = g_strdup_printf ("%s", password);
+ } else {
+ REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("PASSWORD"));
}
if (FEATURE_AVAILABLE(gpdata, "AUTH_ATTEMPTS")) {
argv[argc++] = g_strdup("--auth-attempts");
argv[argc++] = g_strdup_printf ("%i", 0);
+ } else {
+ REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("AUTH_ATTEMPTS"));
}
if (FEATURE_AVAILABLE(gpdata, "COMMAND")) {
@@ -758,6 +1634,8 @@ static gboolean rmplugin_x2go_exec_x2go(gchar *host,
// the command string...
// argv[argc++] = g_strdup_printf ("%s", g_shell_quote(command));
argv[argc++] = g_strdup(command);
+ } else {
+ REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("COMMAND"));
}
if (FEATURE_AVAILABLE(gpdata, "KBD_LAYOUT")) {
@@ -768,6 +1646,8 @@ static gboolean rmplugin_x2go_exec_x2go(gchar *host,
argv[argc++] = g_strdup("--kbd-layout");
argv[argc++] = g_strdup("auto");
}
+ } else {
+ REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("KBD_LAYOUT"));
}
if (FEATURE_AVAILABLE(gpdata, "KBD_TYPE")) {
@@ -778,6 +1658,8 @@ static gboolean rmplugin_x2go_exec_x2go(gchar *host,
argv[argc++] = g_strdup("--kbd-type");
argv[argc++] = g_strdup("auto");
}
+ } else {
+ REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("KBD_TYPE"));
}
if (FEATURE_AVAILABLE(gpdata, "GEOMETRY")) {
@@ -785,14 +1667,14 @@ static gboolean rmplugin_x2go_exec_x2go(gchar *host,
resolution = "800x600";
argv[argc++] = g_strdup("-g");
argv[argc++] = g_strdup_printf ("%s", resolution);
- }
-
- if (FEATURE_AVAILABLE(gpdata, "TRY_RESUME")) {
- argv[argc++] = g_strdup("--try-resume");
+ } else {
+ REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("GEOMETRY"));
}
if (FEATURE_AVAILABLE(gpdata, "TERMINATE_ON_CTRL_C")) {
argv[argc++] = g_strdup("--terminate-on-ctrl-c");
+ } else {
+ REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("TERMINATE_ON_CTRL_C"));
}
if (FEATURE_AVAILABLE(gpdata, "SOUND")) {
@@ -803,11 +1685,17 @@ static gboolean rmplugin_x2go_exec_x2go(gchar *host,
argv[argc++] = g_strdup("--sound");
argv[argc++] = g_strdup("none");
}
+ } else {
+ REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("SOUND"));
}
- if (clipboard && FEATURE_AVAILABLE(gpdata, "CLIPBOARD_MODE")) {
- argv[argc++] = g_strdup("--clipboard-mode");
- argv[argc++] = g_strdup_printf ("%s", clipboard);
+ if (FEATURE_AVAILABLE(gpdata, "CLIPBOARD_MODE")) {
+ if (clipboard) {
+ argv[argc++] = g_strdup("--clipboard-mode");
+ argv[argc++] = g_strdup_printf("%s", clipboard);
+ }
+ } else {
+ REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("CLIPBOARD_MODE"));
}
if (FEATURE_AVAILABLE(gpdata, "DPI")) {
@@ -823,6 +1711,8 @@ static gboolean rmplugin_x2go_exec_x2go(gchar *host,
}
argv[argc++] = g_strdup("--dpi");
argv[argc++] = g_strdup_printf ("%i", dpi);
+ } else {
+ REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("DPI"));
}
argv[argc++] = NULL;
@@ -838,7 +1728,7 @@ static gboolean rmplugin_x2go_exec_x2go(gchar *host,
REMMINA_PLUGIN_INFO("%s", _("Started pyhoca-cli with following arguments:"));
// Print every argument except passwords. Free all arg strings.
for (gint i = 0; i < argc - 1; i++) {
- if (strcmp(argv[i], "--password") == 0) {
+ if (g_strcmp0(argv[i], "--password") == 0) {
g_printf("%s ", argv[i]);
g_printf("XXXXXX ");
g_free (argv[i]);
@@ -858,7 +1748,7 @@ static gboolean rmplugin_x2go_exec_x2go(gchar *host,
gchar *error_title = _("An error occured while "
"starting an X2Go session…");
- DialogData *ddata = g_new0(DialogData, 1);
+ struct _DialogData* ddata = g_new0(struct _DialogData, 1);
SET_DIALOG_DATA(gp, ddata);
ddata->parent = NULL;
ddata->flags = GTK_DIALOG_MODAL;
@@ -883,7 +1773,13 @@ static gboolean rmplugin_x2go_exec_x2go(gchar *host,
// Prevent a race condition where pyhoca-cli is not
// started yet (pidx2go == 0) but a watcher is added.
+
+ struct timespec ts;
+ // 0.001 seconds.
+ ts.tv_nsec = 1 * 1000 * 1000;
+ ts.tv_sec = 0;
while (gpdata->pidx2go == 0) {
+ nanosleep(&ts, NULL);
REMMINA_PLUGIN_DEBUG("Waiting for PyHoca-CLI to start…");
};
@@ -932,10 +1828,26 @@ static GList* rmplugin_x2go_populate_available_features_list()
GList* returning_glist = NULL;
+ // We will now start pyhoca-cli with only the '--list-cmdline-features' option
+ // and depending on the exit code and standard output we will determine if some
+ // features are available or not.
+
+ gchar* argv[50];
+ gint argc = 0;
+
+ argv[argc++] = g_strdup("pyhoca-cli");
+ argv[argc++] = g_strdup("--list-cmdline-features");
+ argv[argc++] = NULL;
+
+ GError* error = NULL; // Won't be actually used.
+
// Querying pyhoca-cli's command line features.
- gchar* features_string = rmplugin_x2go_get_pyhoca_features();
+ gchar** envp = g_get_environ();
+ gchar* features_string = rmplugin_x2go_spawn_pyhoca_process(argc, argv,
+ &error, envp);
+ g_strfreev(envp);
- if (!features_string) {
+ if (!features_string || error) {
// We added the '--list-cmdline-features' on commit 17d1be1319ba6 of
// pyhoca-cli. In order to protect setups which don't have the newest
// version of pyhoca-cli available yet we artificially create a list
@@ -948,11 +1860,9 @@ static GList* rmplugin_x2go_populate_available_features_list()
return rmplugin_x2go_old_pyhoca_features();
} else {
- guint features_amount = 0;
- gchar **features_list = rmplugin_x2go_split_string(features_string, '\n',
- &features_amount);
+ gchar **features_list = g_strsplit(features_string, "\n", 0);
- if (features_list == NULL || features_amount <= 0) {
+ if (features_list == NULL) {
gchar *error_msg = _("Could not parse PyHoca-CLI's command-line "
"features. Using a limited feature-set for now.");
REMMINA_PLUGIN_WARNING("%s", error_msg);
@@ -962,7 +1872,10 @@ static GList* rmplugin_x2go_populate_available_features_list()
REMMINA_PLUGIN_INFO("%s", _("Retrieved the following PyHoca-CLI "
"command-line features:"));
- for(int k = 0; k < features_amount; k++) {
+ for(int k = 0; features_list[k] != NULL; k++) {
+ // Filter out empty strings
+ if (strlen(features_list[k]) <= 0) continue;
+
REMMINA_PLUGIN_INFO("%s",
g_strdup_printf(_("Available feature[%i]: '%s'"),
k+1, features_list[k]));
@@ -986,7 +1899,7 @@ static gboolean rmplugin_x2go_on_plug_removed(GtkSocket *socket, RemminaProtocol
TRACE_CALL(__func__);
REMMINA_PLUGIN_DEBUG("Function entry.");
rmplugin_x2go_close_connection(gp);
- return TRUE;
+ return G_SOURCE_CONTINUE;
}
static void rmplugin_x2go_init(RemminaProtocolWidget *gp)
@@ -1295,8 +2208,10 @@ static gpointer rmplugin_x2go_main_thread(RemminaProtocolWidget* gp)
{
TRACE_CALL(__func__);
if (!gp) {
- REMMINA_PLUGIN_CRITICAL("%s", _("Internal error: RemminaProtocolWidget* "
- "gp is NULL!"));
+ REMMINA_PLUGIN_CRITICAL("%s", g_strdup_printf(
+ _("Internal error: %s"),
+ _("RemminaProtocolWidget* gp is 'NULL'!")
+ ));
return NULL;
}
@@ -1323,7 +2238,8 @@ static gboolean rmplugin_x2go_open_connection(RemminaProtocolWidget *gp)
}
gpdata->socket_id = gtk_socket_get_id(GTK_SOCKET(gpdata->socket));
- if (pthread_create(&gpdata->thread, NULL, rmplugin_x2go_main_thread, gp)) {
+ // casting to void* is allowed since return type 'gpointer' is actually void*.
+ if (pthread_create(&gpdata->thread, NULL, (void*) rmplugin_x2go_main_thread, gp)) {
rm_plugin_service->protocol_plugin_set_error(gp, _("Could not initialize "
"pthread. Falling back to non-threaded mode…"));
gpdata->thread = 0;
@@ -1357,9 +2273,9 @@ static const RemminaProtocolFeature rmplugin_x2go_features[] = {
* @param string The string to which `element_to_add` will be added.
*/
static gchar* rmplugin_x2go_enumeration_prettifier(const guint max_elements,
- const guint current_element,
- gchar* element_to_add,
- gchar* string)
+ const guint current_element,
+ gchar* element_to_add,
+ gchar* string)
{
if (max_elements > 2) {
if (current_element == max_elements - 1) {
@@ -1410,7 +2326,8 @@ static gchar* rmplugin_x2go_enumeration_prettifier(const guint max_elements,
* value is invalid. If the given value is error-free then NULL gets returned.
*
*/
-static GError* rmplugin_x2go_string_setting_validator(gchar* key, gchar* value, gchar* data)
+static GError* rmplugin_x2go_string_setting_validator(gchar* key, gchar* value,
+ gchar* data)
{
GError *error = NULL;
@@ -1421,12 +2338,15 @@ static GError* rmplugin_x2go_string_setting_validator(gchar* key, gchar* value,
return error;
}
+ gchar **elements_list = g_strsplit(data, ",", 0);
+
guint elements_amount = 0;
- gchar **elements_list = rmplugin_x2go_split_string(data, ',', &elements_amount);
+ elements_amount = g_strv_length(elements_list);
- if (elements_amount <= 0 || elements_list == NULL) {
- // Something went wrong, there can't be less than 1 element!
- // And elements_list can't be NULL!
+ if (elements_list == NULL ||
+ elements_list[0] == NULL ||
+ strlen(elements_list[0]) <= 0)
+ {
gchar *error_msg = _("Validation data in ProtocolSettings array is invalid!");
REMMINA_PLUGIN_CRITICAL("%s", error_msg);
g_set_error(&error, 1, 1, error_msg);
@@ -1436,15 +2356,15 @@ static GError* rmplugin_x2go_string_setting_validator(gchar* key, gchar* value,
gchar *data_str = "";
if (!key || !value) {
- REMMINA_PLUGIN_CRITICAL("key or value is NULL!");
+ REMMINA_PLUGIN_CRITICAL("%s", _("Parameters 'key' or 'value' are 'NULL'!"));
g_set_error(&error, 1, 1, _("Internal error."));
return error;
}
- for (int i = 0; i < elements_amount; i++) {
+ for (guint i = 0; elements_list[i] != NULL; i++) {
// Don't wanna crash if elements_list[i] is NULL.
gchar* element = elements_list[i] ? elements_list[i] : "";
- if (strcmp(value, element) == 0) {
+ if (g_strcmp0(value, element) == 0) {
// We found value in elements_list. Value passed validation.
return NULL;
}
@@ -1460,7 +2380,7 @@ static GError* rmplugin_x2go_string_setting_validator(gchar* key, gchar* value,
}
g_free(data_str);
- g_free(elements_list);
+ g_strfreev(elements_list);
return error;
}
@@ -1479,16 +2399,19 @@ static GError* rmplugin_x2go_string_setting_validator(gchar* key, gchar* value,
* value is invalid. If the given value is error-free then NULL gets returned.
*
*/
-static GError* rmplugin_x2go_int_setting_validator(gchar* key, gpointer value, gchar* data)
+static GError* rmplugin_x2go_int_setting_validator(gchar* key, gpointer value,
+ gchar* data)
{
GError *error = NULL;
- guint integer_amount = 0;
- gchar **integer_list = rmplugin_x2go_split_string(data, ';', &integer_amount);
+ gchar **integer_list = g_strsplit(data, ";", 0);
- if (integer_amount != 2 || integer_list == NULL) {
- // Something went wrong, there can't be more or less than 2 list entries.
- // And integer_list can't be NULL!
+ if (integer_list == NULL ||
+ integer_list[0] == NULL ||
+ integer_list[1] == NULL ||
+ strlen(integer_list[0]) <= 0 ||
+ strlen(integer_list[1]) <= 0)
+ {
gchar *error_msg = _("Validation data in ProtocolSettings array is invalid!");
REMMINA_PLUGIN_CRITICAL("%s", error_msg);
g_set_error(&error, 1, 1, error_msg);
@@ -1498,13 +2421,25 @@ static GError* rmplugin_x2go_int_setting_validator(gchar* key, gpointer value, g
gint minimum;
str2int_errno err = str2int(&minimum, integer_list[0], 10);
if (err == STR2INT_INCONVERTIBLE) {
- g_set_error(&error, 1, 1, _("The lower limit is not a valid integer!"));
+ g_set_error(&error, 1, 1, g_strdup_printf(
+ _("Internal error: %s"),
+ _("The lower limit is not a valid integer!")
+ ));
} else if (err == STR2INT_OVERFLOW) {
- g_set_error(&error, 1, 1, _("The lower limit is too high!"));
+ g_set_error(&error, 1, 1, g_strdup_printf(
+ _("Internal error: %s"),
+ _("The lower limit is too high!")
+ ));
} else if (err == STR2INT_UNDERFLOW) {
- g_set_error(&error, 1, 1, _("The lower limit is too low!"));
+ g_set_error(&error, 1, 1, g_strdup_printf(
+ _("Internal error: %s"),
+ _("The lower limit is too low!")
+ ));
} else if (err == STR2INT_INVALID_DATA) {
- g_set_error(&error, 1, 1, _("Something went wrong."));
+ g_set_error(&error, 1, 1, g_strdup_printf(
+ _("Internal error: %s"),
+ _("Something unknown went wrong.")
+ ));
}
if (error) {
@@ -1516,21 +2451,25 @@ static GError* rmplugin_x2go_int_setting_validator(gchar* key, gpointer value, g
gint maximum;
err = str2int(&maximum, integer_list[1], 10);
if (err == STR2INT_INCONVERTIBLE) {
- g_set_error(&error, 1, 1, g_strdup_printf("%s%s",
- _("Internal error: "),
- _("The upper limit is not a valid integer!")));
+ g_set_error(&error, 1, 1, g_strdup_printf(
+ _("Internal error: %s"),
+ _("The upper limit is not a valid integer!")
+ ));
} else if (err == STR2INT_OVERFLOW) {
- g_set_error(&error, 1, 1, g_strdup_printf("%s%s",
- _("Internal error: "),
- _("The upper limit is too high!")));
+ g_set_error(&error, 1, 1, g_strdup_printf(
+ _("Internal error: %s"),
+ _("The upper limit is too high!")
+ ));
} else if (err == STR2INT_UNDERFLOW) {
- g_set_error(&error, 1, 1, g_strdup_printf("%s%s",
- _("Internal error: "),
- _("The upper limit is too low!")));
+ g_set_error(&error, 1, 1, g_strdup_printf(
+ _("Internal error: %s"),
+ _("The upper limit is too low!")
+ ));
} else if (err == STR2INT_INVALID_DATA) {
- g_set_error(&error, 1, 1, g_strdup_printf("%s%s",
- _("Internal error: "),
- _("Something went wrong.")));
+ g_set_error(&error, 1, 1, g_strdup_printf(
+ _("Internal error: %s"),
+ _("Something unknown went wrong.")
+ ));
}
if (error) {
@@ -1549,7 +2488,7 @@ static GError* rmplugin_x2go_int_setting_validator(gchar* key, gpointer value, g
g_set_error(&error, 1, 1, _("Input must be a number between %i and %i."),
minimum, maximum);
} else if (err == STR2INT_INVALID_DATA) {
- g_set_error(&error, 1, 1, _("Something went wrong."));
+ g_set_error(&error, 1, 1, _("Something unknown went wrong."));
}
if (error) {