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:
authorDaniel Teichmann <daniel.teichmann@das-netzwerkteam.de>2021-10-21 14:33:26 +0300
committerDaniel Teichmann <daniel.teichmann@das-netzwerkteam.de>2021-11-04 17:07:13 +0300
commitca6dc1605ed9f9958cf62dd60f289560a99bc296 (patch)
tree806ce19d993177507f2571241f920866065bf35f /plugins/x2go
parent68e713d61077eec562f9819e26788ef7b22d9e77 (diff)
x2go_plugin.c: Introduce session-chooser dialog; Rewrite dialog-system in order to make custom dialogs possible; Introduce structs _ConnectionData and _Session; Adjust docstrings; Rewrite get_pyhoca_features() into spawn_pyhoca_process();
Diffstat (limited to 'plugins/x2go')
-rw-r--r--plugins/x2go/x2go_plugin.c657
1 files changed, 613 insertions, 44 deletions
diff --git a/plugins/x2go/x2go_plugin.c b/plugins/x2go/x2go_plugin.c
index 640a98faa..18d53efee 100644
--- a/plugins/x2go/x2go_plugin.c
+++ b/plugins/x2go/x2go_plugin.c
@@ -70,6 +70,34 @@
#define GET_PLUGIN_DATA(gp) \
(RemminaPluginX2GoData*) g_object_get_data(G_OBJECT(gp), "plugin-data")
+// --------- SESSIONS ------------
+#define SET_RESUME_SESSION_DATA(gp, resume_data) \
+ g_object_set_data_full(G_OBJECT(gp), "resume-session-data", \
+ resume_data, \
+ g_free)
+
+#define GET_RESUME_SESSION_DATA(gp) \
+ (struct _Session*) g_object_get_data(G_OBJECT(gp), "resume-session-data")
+
+#define GET_CURRENT_SESSIONS(gp) \
+ (GList*) g_object_get_data(G_OBJECT(gp), "current-sessions-list")
+
+#define SET_CURRENT_SESSIONS(gp, current_sessions_list) \
+ g_object_set_data_full(G_OBJECT(gp), "current-sessions-list", \
+ current_sessions_list, \
+ g_free)
+
+// 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);
@@ -114,6 +142,24 @@
static RemminaPluginService *rm_plugin_service = NULL;
+/**
+ * @brief TODO: FIXME: Complete me!
+ */
+struct _Session {
+ gchar* session_id;
+ gchar* cookie;
+ gchar* agent_pid;
+ gchar* display;
+ gchar* status;
+ gchar* graphic_port;
+ gchar* snd_port;
+ gchar* sshfs_port;
+ gchar* username;
+ gchar* hostname;
+ gchar* create_date;
+ gchar* suspended_since;
+};
+
// Following str2int code was adapted from Stackoverflow:
// https://stackoverflow.com/questions/7021725/how-to-convert-a-string-to-integer-in-c
typedef enum {
@@ -167,10 +213,13 @@ str2int_errno str2int(gint *out, gchar *s, gint base)
* @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()
@@ -196,6 +245,10 @@ struct _DialogData
gchar *title;
gchar *message;
GCallback callbackfunc;
+
+ // If the dialog needs to be custom.
+ GCallback dialog_factory_func;
+ gpointer dialog_factory_data;
};
/**
@@ -225,15 +278,31 @@ static gboolean rmplugin_x2go_open_dialog(RemminaProtocolWidget *gp)
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",
@@ -253,6 +322,109 @@ static gboolean rmplugin_x2go_open_dialog(RemminaProtocolWidget *gp)
return G_SOURCE_REMOVE;
}
+static GtkWidget* rmplugin_x2go_choose_session_dialog_builder(RemminaProtocolWidget* gp,
+ gpointer factory_data)
+{
+ REMMINA_PLUGIN_DEBUG("Function entry.");
+
+ struct _DialogData* ddata = GET_DIALOG_DATA(gp);
+
+ if (ddata && factory_data) {
+ // 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 FALSE;
+ }
+ } else {
+ REMMINA_PLUGIN_CRITICAL("%s", _("Can't retrieve `DialogData` or "
+ "`factory_data`! Aborting…"));
+ return FALSE;
+ }
+
+ GtkWidget *widget_gtk_dialog = NULL;
+ widget_gtk_dialog = gtk_message_dialog_new(ddata->parent,
+ ddata->flags,
+ ddata->type,
+ ddata->buttons,
+ ddata->title);
+
+ GtkWidget *combo = gtk_combo_box_new();
+ gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(
+ GTK_DIALOG(widget_gtk_dialog))),
+ GTK_WIDGET(combo), TRUE, TRUE, 5);
+
+ GList *sessions_list = factory_data;
+ GList *elem;
+
+ // Make sessions_list available for callback function.
+ SET_CURRENT_SESSIONS(gp, sessions_list);
+
+ for (elem = sessions_list; elem; elem = elem->next) {
+ struct _Session* session = elem->data;
+ g_assert(session != NULL);
+
+ REMMINA_PLUGIN_INFO("%s", g_strdup_printf(
+ _("Found session: '%s'"),
+ session->session_id
+ ));
+
+ // TODO: FIXME: Build proper dialog now.
+ }
+
+ return widget_gtk_dialog;
+}
+
+static gboolean rmplugin_x2go_test_callback(RemminaProtocolWidget* gp, gint response_id,
+ GtkDialog *self)
+{
+ REMMINA_PLUGIN_DEBUG("Function entry.");
+
+ REMMINA_PLUGIN_DEBUG("response: %i", response_id);
+
+
+ if (response_id == GTK_RESPONSE_OK) {
+ GList *sessions_list = GET_CURRENT_SESSIONS(gp);
+ GList *elem;
+
+ if (sessions_list == NULL) {
+ REMMINA_PLUGIN_INFO("%s", g_strdup_printf(
+ _("Internal error: %s"),
+ _("Couldn't get 'sessions_list' via GET_CURRENT_SESSIONS.")
+ ));
+
+ SET_RESUME_SESSION_DATA(gp, NULL);
+ }
+
+ for (elem = sessions_list; elem; elem = elem->next) {
+ struct _Session* session = elem->data;
+ g_assert(session != NULL);
+
+ REMMINA_PLUGIN_INFO("%s", g_strdup_printf(
+ _("Resuming session '%s'"),
+ session->session_id
+ ));
+
+ SET_RESUME_SESSION_DATA(gp, session);
+ break;
+ }
+ } else {
+ REMMINA_PLUGIN_DEBUG("User clicked dialog away. Creating a new "
+ "session then.");
+ SET_RESUME_SESSION_DATA(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.
+ SET_SESSION_SELECTED(gp, (gpointer) TRUE);
+
+ gtk_widget_destroy(GTK_WIDGET(self));
+
+ return G_SOURCE_REMOVE;
+}
+
typedef struct _RemminaPluginX2GoData {
GtkWidget *socket;
gint socket_id;
@@ -479,36 +651,47 @@ 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.
*
- * @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)
{
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;
+ }
- 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, envp, 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.
@@ -526,16 +709,17 @@ static gchar* rmplugin_x2go_get_pyhoca_features()
}
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));
+ 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;
@@ -649,6 +833,334 @@ static gboolean rmplugin_x2go_get_auth(RemminaProtocolWidget *gp, gchar* errmsg,
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;
+ }
+
+ argv[argc++] = NULL;
+
+ gchar* std_out = rmplugin_x2go_spawn_pyhoca_process(argc, argv, error);
+
+ 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;
+ struct _Session *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 = g_new0(struct _Session, 1);
+ sessions = g_list_append(sessions, session);
+
+ session_id = line_list[1];
+ session->session_id = session_id;
+
+ REMMINA_PLUGIN_INFO("%s", g_strdup_printf(
+ _("Found already existing X2Go session with ID: '%s'"),
+ 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->cookie = value;
+ } else if (g_str_has_prefix(current_line, "agent PID: ")) {
+ REMMINA_PLUGIN_DEBUG("agent PID:\t'%s'", value);
+ session->agent_pid = value;
+ } else if (g_str_has_prefix(current_line, "display: ")) {
+ REMMINA_PLUGIN_DEBUG("display:\t'%s'", value);
+ session->display = value;
+ } else if (g_str_has_prefix(current_line, "status: ")) {
+ REMMINA_PLUGIN_DEBUG("status:\t'%s'", value);
+ session->status = value;
+ } else if (g_str_has_prefix(current_line, "graphic port: ")) {
+ REMMINA_PLUGIN_DEBUG("graphic port:\t'%s'", value);
+ session->graphic_port = value;
+ } else if (g_str_has_prefix(current_line, "snd port: ")) {
+ REMMINA_PLUGIN_DEBUG("snd port:\t'%s'", value);
+ session->snd_port = value;
+ } else if (g_str_has_prefix(current_line, "sshfs port: ")) {
+ REMMINA_PLUGIN_DEBUG("sshfs port:\t'%s'", value);
+ session->sshfs_port = value;
+ } else if (g_str_has_prefix(current_line, "username: ")) {
+ REMMINA_PLUGIN_DEBUG("username:\t'%s'", value);
+ session->username = value;
+ } else if (g_str_has_prefix(current_line, "hostname: ")) {
+ REMMINA_PLUGIN_DEBUG("hostname:\t'%s'", value);
+ session->hostname = value;
+ } else if (g_str_has_prefix(current_line, "create date: ")) {
+ REMMINA_PLUGIN_DEBUG("create date:\t'%s'", value);
+ session->create_date = value;
+ } else if (g_str_has_prefix(current_line, "suspended since: ")) {
+ REMMINA_PLUGIN_DEBUG("suspended since:\t'%s'", value);
+ 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 TODO: Complete me!
+ */
+static struct _Session* 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;
+ ddata->type = GTK_MESSAGE_QUESTION;
+ ddata->buttons = GTK_BUTTONS_OK;
+ ddata->title = _("Choose a session to resume:");
+ ddata->message = "Normal dailog factory function.";
+ ddata->callbackfunc = G_CALLBACK(rmplugin_x2go_test_callback);
+ ddata->dialog_factory_func = G_CALLBACK(rmplugin_x2go_choose_session_dialog_builder);
+ ddata->dialog_factory_data = sessions_list;
+
+ // Open dialog here. Dialog rmplugin_x2go_test_callback (callbackfunc)
+ // should set SET_RESUME_SESSION_DATA.
+ 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 respond to dialog…"));
+ }
+ counter++;
+ }
+
+ struct _Session* chosen_resume_session = GET_RESUME_SESSION_DATA(gp);
+
+ if (!chosen_resume_session) {
+ g_set_error(error, 1, 1, _("No session was selected. Creating a new one."));
+ return NULL;
+ }
+
+ // Safety first. If not set, crash.
+ g_assert(chosen_resume_session->session_id != NULL);
+ g_assert(strlen(chosen_resume_session->session_id) > 0);
+
+ return chosen_resume_session;
+}
+
static gboolean rmplugin_x2go_exec_x2go(gchar *host,
gint sshport,
gchar *username,
@@ -674,6 +1186,32 @@ static gboolean rmplugin_x2go_exec_x2go(gchar *host,
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;
+ struct _Session* resume_session = rmplugin_x2go_ask_session(gp, &session_error,
+ connect_data);
+
+ if (!resume_session || session_error) {
+ // 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->session_id
+ ));
+ }
+
argc = 0;
argv[argc++] = g_strdup("pyhoca-cli");
@@ -687,6 +1225,20 @@ static gboolean rmplugin_x2go_exec_x2go(gchar *host,
REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("REMOTE_SSH_PORT"));
}
+ if (resume_session) {
+ REMMINA_PLUGIN_INFO(
+ "%s", g_strdup_printf(_("Resuming session '%s'…"),
+ resume_session->session_id)
+ );
+
+ if (FEATURE_AVAILABLE(gpdata, "RESUME")) {
+ argv[argc++] = g_strdup("--resume");
+ argv[argc++] = g_strdup_printf("%s", resume_session->session_id);
+ } else {
+ REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("RESUME"));
+ }
+ }
+
if (FEATURE_AVAILABLE(gpdata, "USERNAME")) {
argv[argc++] = g_strdup("-u");
if (username){
@@ -756,8 +1308,12 @@ static gboolean rmplugin_x2go_exec_x2go(gchar *host,
REMMINA_PLUGIN_DEBUG("%s", FEATURE_NOT_AVAIL_STR("GEOMETRY"));
}
- if (FEATURE_AVAILABLE(gpdata, "TRY_RESUME")) {
- argv[argc++] = g_strdup("--try-resume");
+ if (!resume_session) {
+ 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, "TERMINATE_ON_CTRL_C")) {
@@ -911,10 +1467,23 @@ 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* features_string = rmplugin_x2go_spawn_pyhoca_process(argc, argv, &error);
- 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