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

gitlab.com/Remmina/remmina-plugins.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/nx/nx_plugin.c')
-rw-r--r--plugins/nx/nx_plugin.c831
1 files changed, 831 insertions, 0 deletions
diff --git a/plugins/nx/nx_plugin.c b/plugins/nx/nx_plugin.c
new file mode 100644
index 0000000..01791ac
--- /dev/null
+++ b/plugins/nx/nx_plugin.c
@@ -0,0 +1,831 @@
+/*
+ * Remmina - The GTK+ Remote Desktop Client
+ * Copyright (C) 2010-2011 Vic Lee
+ * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
+ * Copyright (C) 2016-2021 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.
+ *
+ */
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include "common/remmina_plugin.h"
+#include <gtk/gtkx.h>
+#include <time.h>
+#define LIBSSH_STATIC 1
+#include <libssh/libssh.h>
+#include <X11/Xlib.h>
+#include <X11/XKBlib.h>
+#include <X11/extensions/XKBrules.h>
+#include "nx_plugin.h"
+#include "nx_session_manager.h"
+
+#define REMMINA_PLUGIN_NX_FEATURE_TOOL_SENDCTRLALTDEL 1
+#define REMMINA_PLUGIN_NX_FEATURE_GTKSOCKET 1
+
+/* Forward declaration */
+static RemminaProtocolPlugin remmina_plugin_nx;
+
+RemminaPluginService *remmina_plugin_nx_service = NULL;
+
+static gchar *remmina_kbtype = "pc102/us";
+
+/* When more than one NX sessions is connecting in progress, we need this mutex and array
+ * to prevent them from stealing the same window ID.
+ */
+static pthread_mutex_t remmina_nx_init_mutex;
+static GArray *remmina_nx_window_id_array;
+
+/* --------- Support for execution on main thread of GTK functions -------------- */
+
+struct onMainThread_cb_data {
+ enum { FUNC_GTK_SOCKET_ADD_ID } func;
+
+ GtkSocket* sk;
+ Window w;
+
+ /* Mutex for thread synchronization */
+ pthread_mutex_t mu;
+ /* Flag to catch cancellations */
+ gboolean cancelled;
+};
+
+static gboolean onMainThread_cb(struct onMainThread_cb_data *d)
+{
+ TRACE_CALL(__func__);
+ if ( !d->cancelled ) {
+ switch ( d->func ) {
+ case FUNC_GTK_SOCKET_ADD_ID:
+ gtk_socket_add_id( d->sk, d->w );
+ break;
+ }
+ pthread_mutex_unlock( &d->mu );
+ } else {
+ /* Thread has been cancelled, so we must free d memory here */
+ g_free( d );
+ }
+ return G_SOURCE_REMOVE;
+}
+
+
+static void onMainThread_cleanup_handler(gpointer data)
+{
+ TRACE_CALL(__func__);
+ struct onMainThread_cb_data *d = data;
+ d->cancelled = TRUE;
+}
+
+
+static void onMainThread_schedule_callback_and_wait( struct onMainThread_cb_data *d )
+{
+ TRACE_CALL(__func__);
+ d->cancelled = FALSE;
+ pthread_cleanup_push( onMainThread_cleanup_handler, d );
+ pthread_mutex_init( &d->mu, NULL );
+ pthread_mutex_lock( &d->mu );
+ gdk_threads_add_idle( (GSourceFunc)onMainThread_cb, (gpointer)d );
+
+ pthread_mutex_lock( &d->mu );
+
+ pthread_cleanup_pop(0);
+ pthread_mutex_unlock( &d->mu );
+ pthread_mutex_destroy( &d->mu );
+}
+
+static void onMainThread_gtk_socket_add_id( GtkSocket* sk, Window w)
+{
+ TRACE_CALL(__func__);
+
+ struct onMainThread_cb_data *d;
+
+ d = (struct onMainThread_cb_data *)g_malloc( sizeof(struct onMainThread_cb_data) );
+ d->func = FUNC_GTK_SOCKET_ADD_ID;
+ d->sk = sk;
+ d->w = w;
+
+ onMainThread_schedule_callback_and_wait( d );
+ g_free(d);
+
+}
+
+
+/* --------------------------------------- */
+
+
+static gboolean remmina_plugin_nx_try_window_id(Window window_id)
+{
+ TRACE_CALL(__func__);
+ gint i;
+ gboolean found = FALSE;
+
+ pthread_mutex_lock(&remmina_nx_init_mutex);
+ for (i = 0; i < remmina_nx_window_id_array->len; i++) {
+ if (g_array_index(remmina_nx_window_id_array, Window, i) == window_id) {
+ found = TRUE;
+ break;
+ }
+ }
+ if (!found) {
+ g_array_append_val(remmina_nx_window_id_array, window_id);
+ }
+ pthread_mutex_unlock(&remmina_nx_init_mutex);
+
+ return (!found);
+}
+
+static void remmina_plugin_nx_remove_window_id(Window window_id)
+{
+ TRACE_CALL(__func__);
+ gint i;
+ gboolean found = FALSE;
+
+ pthread_mutex_lock(&remmina_nx_init_mutex);
+ for (i = 0; i < remmina_nx_window_id_array->len; i++) {
+ if (g_array_index(remmina_nx_window_id_array, Window, i) == window_id) {
+ found = TRUE;
+ break;
+ }
+ }
+ if (found) {
+ g_array_remove_index_fast(remmina_nx_window_id_array, i);
+ }
+ pthread_mutex_unlock(&remmina_nx_init_mutex);
+}
+
+static void remmina_plugin_nx_on_plug_added(GtkSocket *socket, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_nx_service->protocol_plugin_signal_connection_opened(gp);
+}
+
+static void remmina_plugin_nx_on_plug_removed(GtkSocket *socket, RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ remmina_plugin_nx_service->protocol_plugin_signal_connection_closed(gp);
+}
+
+gboolean remmina_plugin_nx_ssh_auth_callback(gchar **passphrase, gpointer userdata)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = (RemminaProtocolWidget*)userdata;
+ gint ret;
+
+ /* SSH passwords must not be saved */
+ ret = remmina_plugin_nx_service->protocol_plugin_init_auth(gp, 0,
+ _("SSH credentials"), NULL,
+ NULL,
+ NULL,
+ _("Password for private SSH key"));
+ if (ret == GTK_RESPONSE_OK) {
+ *passphrase = remmina_plugin_nx_service->protocol_plugin_init_get_password(gp);
+ return TRUE;
+ } else
+ return FALSE;
+}
+
+static void remmina_plugin_nx_on_proxy_exit(GPid pid, gint status, gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = (RemminaProtocolWidget*)data;
+
+ remmina_plugin_nx_service->protocol_plugin_signal_connection_closed(gp);
+}
+
+static int remmina_plugin_nx_dummy_handler(Display *dsp, XErrorEvent *err)
+{
+ TRACE_CALL(__func__);
+ return 0;
+}
+
+static gboolean remmina_plugin_nx_start_create_notify(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+
+ gpdata->display = XOpenDisplay(gdk_display_get_name(gdk_display_get_default()));
+ if (gpdata->display == NULL)
+ return FALSE;
+
+ gpdata->orig_handler = XSetErrorHandler(remmina_plugin_nx_dummy_handler);
+
+ XSelectInput(gpdata->display, XDefaultRootWindow(gpdata->display), SubstructureNotifyMask);
+
+ return TRUE;
+}
+
+static gboolean remmina_plugin_nx_monitor_create_notify(RemminaProtocolWidget *gp, const gchar *cmd)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata;
+ Atom atom;
+ XEvent xev;
+ Window w;
+ Atom type;
+ int format;
+ unsigned long nitems, rest;
+ unsigned char *data = NULL;
+ struct timespec ts;
+
+ CANCEL_DEFER
+
+ gpdata = GET_PLUGIN_DATA(gp);
+ atom = XInternAtom(gpdata->display, "WM_COMMAND", True);
+ if (atom == None)
+ return FALSE;
+
+ ts.tv_sec = 0;
+ ts.tv_nsec = 200000000;
+
+ while (1) {
+ pthread_testcancel();
+ while (!XPending(gpdata->display)) {
+ nanosleep(&ts, NULL);
+ continue;
+ }
+ XNextEvent(gpdata->display, &xev);
+ if (xev.type != CreateNotify)
+ continue;
+ w = xev.xcreatewindow.window;
+ if (XGetWindowProperty(gpdata->display, w, atom, 0, 255, False, AnyPropertyType, &type, &format, &nitems, &rest,
+ &data) != Success)
+ continue;
+ if (data && strstr((char*)data, cmd) && remmina_plugin_nx_try_window_id(w)) {
+ gpdata->window_id = w;
+ XFree(data);
+ break;
+ }
+ if (data)
+ XFree(data);
+ }
+
+ XSetErrorHandler(gpdata->orig_handler);
+ XCloseDisplay(gpdata->display);
+ gpdata->display = NULL;
+
+ CANCEL_ASYNC
+ return TRUE;
+}
+
+static gint remmina_plugin_nx_wait_signal(RemminaPluginNxData *gpdata)
+{
+ TRACE_CALL(__func__);
+ fd_set set;
+ guchar dummy = 0;
+
+ FD_ZERO(&set);
+ FD_SET(gpdata->event_pipe[0], &set);
+ select(gpdata->event_pipe[0] + 1, &set, NULL, NULL, NULL);
+ if (read(gpdata->event_pipe[0], &dummy, 1)) {
+ }
+ return (gint)dummy;
+}
+
+static void remmina_plugin_nx_log_callback(const gchar *fmt, ...)
+{
+ char buffer[256];
+ va_list args;
+ va_start(args, fmt);
+ vsnprintf(buffer, sizeof(buffer), fmt, args);
+ REMMINA_PLUGIN_DEBUG(buffer);
+ va_end(args);
+}
+
+
+static gboolean remmina_plugin_nx_start_session(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+ RemminaFile *remminafile;
+ RemminaNXSession *nx;
+ const gchar *type, *app;
+ gchar *s1, *s2;
+ gint port;
+ gint ret;
+ gboolean is_empty_list;
+ gint event_type = 0;
+ const gchar *cs;
+ gint i;
+ gboolean disablepasswordstoring;
+
+ remminafile = remmina_plugin_nx_service->protocol_plugin_get_file(gp);
+ nx = gpdata->nx;
+
+ /* Connect */
+
+ remmina_nx_session_set_encryption(nx,
+ remmina_plugin_nx_service->file_get_int(remminafile, "disableencryption", FALSE) ? 0 : 1);
+ remmina_nx_session_set_localport(nx, remmina_plugin_nx_service->pref_get_sshtunnel_port());
+ remmina_nx_session_set_log_callback(nx, remmina_plugin_nx_log_callback);
+
+ s2 = remmina_plugin_nx_service->protocol_plugin_start_direct_tunnel(gp, 22, FALSE);
+ if (s2 == NULL) {
+ return FALSE;
+ }
+ remmina_plugin_nx_service->get_server_port(s2, 22, &s1, &port);
+ g_free(s2);
+
+ if (!remmina_nx_session_open(nx, s1, port, remmina_plugin_nx_service->file_get_string(remminafile, "nx_privatekey"),
+ remmina_plugin_nx_ssh_auth_callback, gp)) {
+ g_free(s1);
+ return FALSE;
+ }
+ g_free(s1);
+
+ /* Login */
+
+ s1 = g_strdup(remmina_plugin_nx_service->file_get_string(remminafile, "username"));
+ s2 = g_strdup(remmina_plugin_nx_service->file_get_string(remminafile, "password"));
+
+ if (s1 && s2) {
+ ret = remmina_nx_session_login(nx, s1, s2);
+ } else {
+ gchar *s_username, *s_password;
+
+ disablepasswordstoring = remmina_plugin_nx_service->file_get_int(remminafile, "disablepasswordstoring", FALSE);
+
+ ret = remmina_plugin_nx_service->protocol_plugin_init_auth(gp,
+ (disablepasswordstoring ? 0 : REMMINA_MESSAGE_PANEL_FLAG_SAVEPASSWORD) | REMMINA_MESSAGE_PANEL_FLAG_USERNAME,
+ _("Enter NX authentication credentials"),
+ remmina_plugin_nx_service->file_get_string(remminafile, "username"),
+ remmina_plugin_nx_service->file_get_string(remminafile, "password"),
+ NULL,
+ NULL);
+ if (ret == GTK_RESPONSE_OK) {
+ gboolean save;
+ s_username = remmina_plugin_nx_service->protocol_plugin_init_get_username(gp);
+ s_password = remmina_plugin_nx_service->protocol_plugin_init_get_password(gp);
+ save = remmina_plugin_nx_service->protocol_plugin_init_get_savepassword(gp);
+ if (save) {
+ remmina_plugin_nx_service->file_set_string(remminafile, "username", s_username);
+ remmina_plugin_nx_service->file_set_string(remminafile, "password", s_password);
+ } else
+ remmina_plugin_nx_service->file_unsave_passwords(remminafile);
+ } else {
+ return False;
+ }
+
+ ret = remmina_nx_session_login(nx, s_username, s_password);
+ g_free(s_username);
+ g_free(s_password);
+ }
+ g_free(s1);
+ g_free(s2);
+
+ if (!ret)
+ return FALSE;
+
+ remmina_plugin_nx_service->protocol_plugin_init_save_cred(gp);
+
+ /* Prepare the session type and application */
+ cs = remmina_plugin_nx_service->file_get_string(remminafile, "exec");
+ if (!cs || g_strcmp0(cs, "GNOME") == 0) {
+ type = "unix-gnome";
+ app = NULL;
+ }else
+ if (g_strcmp0(cs, "KDE") == 0) {
+ type = "unix-kde";
+ app = NULL;
+ }else
+ if (g_strcmp0(cs, "Xfce") == 0) {
+ /* NX does not know Xfce. So we simply launch the Xfce start-up program. */
+ type = "unix-application";
+ app = "startxfce4";
+ }else
+ if (g_strcmp0(cs, "Shadow") == 0) {
+ type = "shadow";
+ app = NULL;
+ }else {
+ type = "unix-application";
+ app = cs;
+ }
+
+ /* List sessions */
+
+ gpdata->attach_session = (g_strcmp0(type, "shadow") == 0);
+ while (1) {
+ remmina_nx_session_add_parameter(nx, "type", type);
+ if (!gpdata->attach_session) {
+ remmina_nx_session_add_parameter(nx, "user",
+ remmina_plugin_nx_service->file_get_string(remminafile, "username"));
+ remmina_nx_session_add_parameter(nx, "status", "suspended,running");
+ }
+
+ if (!remmina_nx_session_list(nx)) {
+ return FALSE;
+ }
+
+ is_empty_list = !remmina_nx_session_iter_first(nx, &gpdata->iter);
+ if (is_empty_list && !gpdata->manager_started && !gpdata->attach_session) {
+ event_type = REMMINA_NX_EVENT_START;
+ }else {
+ remmina_nx_session_manager_start(gp);
+ event_type = remmina_plugin_nx_wait_signal(gpdata);
+ if (event_type == REMMINA_NX_EVENT_CANCEL) {
+ return FALSE;
+ }
+ if (event_type == REMMINA_NX_EVENT_TERMINATE) {
+ if (!is_empty_list) {
+ s1 = remmina_nx_session_iter_get(nx, &gpdata->iter, REMMINA_NX_SESSION_COLUMN_ID);
+ remmina_nx_session_add_parameter(nx, "sessionid", s1);
+ g_free(s1);
+ if (!remmina_nx_session_terminate(nx)) {
+ remmina_nx_session_manager_start(gp);
+ remmina_plugin_nx_wait_signal(gpdata);
+ }
+ }
+ continue;
+ }
+ }
+
+ break;
+ }
+
+ /* Start, Restore or Attach, based on the setting and existing session */
+ remmina_nx_session_add_parameter(nx, "type", type);
+ i = remmina_plugin_nx_service->file_get_int(remminafile, "quality", 0);
+ remmina_nx_session_add_parameter(nx, "link", i > 2 ? "lan" : i == 2 ? "adsl" : i == 1 ? "isdn" : "modem");
+ remmina_nx_session_add_parameter(nx, "geometry", "%ix%i",
+ remmina_plugin_nx_service->get_profile_remote_width(gp),
+ remmina_plugin_nx_service->get_profile_remote_height(gp));
+ remmina_nx_session_add_parameter(nx, "keyboard", remmina_kbtype);
+ remmina_nx_session_add_parameter(nx, "client", "linux");
+ remmina_nx_session_add_parameter(nx, "media", "0");
+ remmina_nx_session_add_parameter(nx, "clipboard",
+ remmina_plugin_nx_service->file_get_int(remminafile, "disableclipboard", FALSE) ? "none" : "both");
+
+ switch (event_type) {
+
+ case REMMINA_NX_EVENT_START:
+ if (app)
+ remmina_nx_session_add_parameter(nx, "application", app);
+
+ remmina_nx_session_add_parameter(nx, "session",
+ remmina_plugin_nx_service->file_get_string(remminafile, "name"));
+ remmina_nx_session_add_parameter(nx, "screeninfo", "%ix%ix24+render",
+ remmina_plugin_nx_service->file_get_int(remminafile, "resolution_width", 0),
+ remmina_plugin_nx_service->file_get_int(remminafile, "resolution_height", 0));
+
+ if (!remmina_nx_session_start(nx))
+ return FALSE;
+ break;
+
+ case REMMINA_NX_EVENT_ATTACH:
+ s1 = remmina_nx_session_iter_get(nx, &gpdata->iter, REMMINA_NX_SESSION_COLUMN_ID);
+ remmina_nx_session_add_parameter(nx, "id", s1);
+ g_free(s1);
+
+ s1 = remmina_nx_session_iter_get(nx, &gpdata->iter, REMMINA_NX_SESSION_COLUMN_DISPLAY);
+ remmina_nx_session_add_parameter(nx, "display", s1);
+ g_free(s1);
+
+ if (!remmina_nx_session_attach(nx))
+ return FALSE;
+ break;
+
+ case REMMINA_NX_EVENT_RESTORE:
+ s1 = remmina_nx_session_iter_get(nx, &gpdata->iter, REMMINA_NX_SESSION_COLUMN_ID);
+ remmina_nx_session_add_parameter(nx, "id", s1);
+ g_free(s1);
+
+ remmina_nx_session_add_parameter(nx, "session",
+ remmina_plugin_nx_service->file_get_string(remminafile, "name"));
+
+ if (!remmina_nx_session_restore(nx))
+ return FALSE;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ if (!remmina_nx_session_tunnel_open(nx))
+ return FALSE;
+
+ if (!remmina_plugin_nx_start_create_notify(gp))
+ return FALSE;
+
+ /* nxproxy */
+ if (!remmina_nx_session_invoke_proxy(nx, -1, remmina_plugin_nx_on_proxy_exit, gp))
+ return FALSE;
+
+ /* get the window id of the remote nxagent */
+ if (!remmina_plugin_nx_monitor_create_notify(gp, "nxagent"))
+ return FALSE;
+
+ /* embed it */
+ onMainThread_gtk_socket_add_id(GTK_SOCKET(gpdata->socket), gpdata->window_id);
+
+
+ return TRUE;
+}
+
+static gboolean remmina_plugin_nx_main(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+ gboolean ret;
+ const gchar *err;
+
+ gpdata->nx = remmina_nx_session_new();
+ ret = remmina_plugin_nx_start_session(gp);
+ if (!ret) {
+ err = remmina_nx_session_get_error(gpdata->nx);
+ if (err) {
+ remmina_plugin_nx_service->protocol_plugin_set_error(gp, "%s", err);
+ }
+ }
+
+ gpdata->thread = 0;
+ return ret;
+}
+
+static gpointer remmina_plugin_nx_main_thread(gpointer data)
+{
+ TRACE_CALL(__func__);
+ RemminaProtocolWidget *gp = (RemminaProtocolWidget *)data;
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
+
+ CANCEL_ASYNC
+ if (!remmina_plugin_nx_main(gp)) {
+ remmina_plugin_nx_service->protocol_plugin_signal_connection_closed(gp);
+ }
+ return NULL;
+}
+
+static void remmina_plugin_nx_init(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata;
+ gint flags;
+
+ gpdata = g_new0(RemminaPluginNxData, 1);
+ g_object_set_data_full(G_OBJECT(gp), "plugin-data", gpdata, g_free);
+
+ gpdata->socket = gtk_socket_new();
+ remmina_plugin_nx_service->protocol_plugin_register_hostkey(gp, gpdata->socket);
+ gtk_widget_show(gpdata->socket);
+ g_signal_connect(G_OBJECT(gpdata->socket), "plug-added", G_CALLBACK(remmina_plugin_nx_on_plug_added), gp);
+ g_signal_connect(G_OBJECT(gpdata->socket), "plug-removed", G_CALLBACK(remmina_plugin_nx_on_plug_removed), gp);
+ gtk_container_add(GTK_CONTAINER(gp), gpdata->socket);
+
+ if (pipe(gpdata->event_pipe)) {
+ g_print("Error creating pipes.\n");
+ gpdata->event_pipe[0] = -1;
+ gpdata->event_pipe[1] = -1;
+ }else {
+ flags = fcntl(gpdata->event_pipe[0], F_GETFL, 0);
+ fcntl(gpdata->event_pipe[0], F_SETFL, flags | O_NONBLOCK);
+ }
+}
+
+static gboolean remmina_plugin_nx_open_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+ gint width, height;
+
+ if (!remmina_plugin_nx_service->gtksocket_available()) {
+ remmina_plugin_nx_service->protocol_plugin_set_error(gp,
+ _("The protocol “%s” is unavailable because GtkSocket only works under X.Org."),
+ remmina_plugin_nx.name);
+ return FALSE;
+ }
+
+ width = remmina_plugin_nx_service->get_profile_remote_width(gp);
+ height = remmina_plugin_nx_service->get_profile_remote_height(gp);
+
+ remmina_plugin_nx_service->protocol_plugin_set_width(gp, width);
+ remmina_plugin_nx_service->protocol_plugin_set_height(gp, height);
+ gtk_widget_set_size_request(GTK_WIDGET(gp), width, height);
+
+ gpdata->socket_id = gtk_socket_get_id(GTK_SOCKET(gpdata->socket));
+
+ if (pthread_create(&gpdata->thread, NULL, remmina_plugin_nx_main_thread, gp)) {
+ remmina_plugin_nx_service->protocol_plugin_set_error(gp,
+ "Failed to initialize pthread. Falling back to non-thread mode…");
+ gpdata->thread = 0;
+ return FALSE;
+ }else {
+ return TRUE;
+ }
+}
+
+static gboolean remmina_plugin_nx_close_connection(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+
+ if (gpdata->thread) {
+ pthread_cancel(gpdata->thread);
+ if (gpdata->thread)
+ pthread_join(gpdata->thread, NULL);
+ }
+ if (gpdata->session_manager_start_handler) {
+ g_source_remove(gpdata->session_manager_start_handler);
+ gpdata->session_manager_start_handler = 0;
+ }
+
+ if (gpdata->window_id) {
+ remmina_plugin_nx_remove_window_id(gpdata->window_id);
+ }
+
+ if (gpdata->nx) {
+ remmina_nx_session_free(gpdata->nx);
+ gpdata->nx = NULL;
+ }
+
+ if (gpdata->display) {
+ XSetErrorHandler(gpdata->orig_handler);
+ XCloseDisplay(gpdata->display);
+ gpdata->display = NULL;
+ }
+ close(gpdata->event_pipe[0]);
+ close(gpdata->event_pipe[1]);
+
+ remmina_plugin_nx_service->protocol_plugin_signal_connection_closed(gp);
+
+ return FALSE;
+}
+
+/* Send CTRL+ALT+DEL keys keystrokes to the plugin socket widget */
+static void remmina_plugin_nx_send_ctrlaltdel(RemminaProtocolWidget *gp)
+{
+ TRACE_CALL(__func__);
+ guint keys[] = { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_Delete };
+ RemminaPluginNxData *gpdata = GET_PLUGIN_DATA(gp);
+
+ remmina_plugin_nx_service->protocol_plugin_send_keys_signals(gpdata->socket,
+ keys, G_N_ELEMENTS(keys), GDK_KEY_PRESS | GDK_KEY_RELEASE);
+}
+
+static gboolean remmina_plugin_nx_query_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ return TRUE;
+}
+
+static void remmina_plugin_nx_call_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
+{
+ TRACE_CALL(__func__);
+ switch (feature->id) {
+ case REMMINA_PLUGIN_NX_FEATURE_TOOL_SENDCTRLALTDEL:
+ remmina_plugin_nx_send_ctrlaltdel(gp);
+ break;
+ default:
+ break;
+ }
+}
+
+/* Array of key/value pairs for quality selection */
+static gpointer quality_list[] =
+{
+ "0", N_("Poor (fastest)"),
+ "1", N_("Medium"),
+ "2", N_("Good"),
+ "9", N_("Best (slowest)"),
+ NULL
+};
+
+/* Array of RemminaProtocolSetting for basic settings.
+ * Each item is composed by:
+ * a) RemminaProtocolSettingType for setting type
+ * b) Setting name
+ * c) Setting description
+ * d) Compact disposition
+ * e) Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO
+ * f) Setting tooltip
+ */
+static const RemminaProtocolSetting remmina_plugin_nx_basic_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_SERVER, "server", NULL, FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_FILE, "nx_privatekey", N_("SSH identity file"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "username", N_("Username"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_PASSWORD, "password", N_("User password"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_RESOLUTION, "resolution", NULL, FALSE, GINT_TO_POINTER(1), NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "quality", N_("Quality"), FALSE, quality_list, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_COMBO, "exec", N_("Start-up program"), FALSE, "GNOME,KDE,Xfce,Shadow", NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL }
+};
+
+/* Array of RemminaProtocolSetting for advanced settings.
+ * Each item is composed by:
+ * a) RemminaProtocolSettingType for setting type
+ * b) Setting name
+ * c) Setting description
+ * d) Compact disposition
+ * e) Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO
+ * f) Setting tooltip
+ */
+static const RemminaProtocolSetting remmina_plugin_nx_advanced_settings[] =
+{
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableclipboard", N_("Disable clipboard sync"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disableencryption", N_("Disable encryption"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "showcursor", N_("Use local cursor"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "disablepasswordstoring", N_("Forget passwords after use"), FALSE, NULL, NULL },
+ { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL }
+};
+
+/* Array for available features.
+ * The last element of the array must be REMMINA_PROTOCOL_FEATURE_TYPE_END. */
+static const RemminaProtocolFeature remmina_plugin_nx_features[] =
+{
+ { REMMINA_PROTOCOL_FEATURE_TYPE_TOOL, REMMINA_PLUGIN_NX_FEATURE_TOOL_SENDCTRLALTDEL, N_("Send Ctrl+Alt+Del"), NULL, NULL },
+ { REMMINA_PROTOCOL_FEATURE_TYPE_GTKSOCKET, REMMINA_PLUGIN_NX_FEATURE_GTKSOCKET, NULL, NULL, NULL},
+ { REMMINA_PROTOCOL_FEATURE_TYPE_END, 0, NULL, NULL, NULL }
+};
+
+/* Protocol plugin definition and features */
+static RemminaProtocolPlugin remmina_plugin_nx =
+{
+ REMMINA_PLUGIN_TYPE_PROTOCOL, // Type
+ "NX", // Name
+ N_("NX - NX Technology"), // Description
+ GETTEXT_PACKAGE, // Translation domain
+ VERSION, // Version number
+ "remmina-nx-symbolic", // Icon for normal connection
+ "remmina-nx-symbolic", // Icon for SSH connection
+ remmina_plugin_nx_basic_settings, // Array for basic settings
+ remmina_plugin_nx_advanced_settings, // Array for advanced settings
+ REMMINA_PROTOCOL_SSH_SETTING_TUNNEL, // SSH settings type
+ remmina_plugin_nx_features, // Array for available features
+ remmina_plugin_nx_init, // Plugin initialization
+ remmina_plugin_nx_open_connection, // Plugin open connection
+ remmina_plugin_nx_close_connection, // Plugin close connection
+ remmina_plugin_nx_query_feature, // Query for available features
+ remmina_plugin_nx_call_feature, // Call a feature
+ NULL, // Send a keystroke
+ NULL, // No screenshot support available
+ NULL, // RCW map event
+ NULL // RCW unmap event
+};
+
+G_MODULE_EXPORT gboolean
+remmina_plugin_entry(RemminaPluginService *service)
+{
+ TRACE_CALL(__func__);
+ Display *dpy;
+ XkbRF_VarDefsRec vd;
+ gchar *s;
+
+ remmina_plugin_nx_service = service;
+
+ bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR);
+ bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
+
+ if ((dpy = XkbOpenDisplay(NULL, NULL, NULL, NULL, NULL, NULL)) != NULL) {
+ if (XkbRF_GetNamesProp(dpy, NULL, &vd)) {
+ remmina_kbtype = g_strdup_printf("%s/%s", vd.model, vd.layout);
+ if (vd.layout)
+ XFree(vd.layout);
+ if (vd.model)
+ XFree(vd.model);
+ if (vd.variant)
+ XFree(vd.variant);
+ if (vd.options)
+ XFree(vd.options);
+ s = strchr(remmina_kbtype, ',');
+ if (s)
+ *s = '\0';
+ /* g_print("NX: Detected “%s” keyboard type\n", remmina_kbtype); */
+ }
+ XCloseDisplay(dpy);
+ }
+
+ if (!service->register_plugin((RemminaPlugin*)&remmina_plugin_nx)) {
+ return FALSE;
+ }
+
+ ssh_init();
+ pthread_mutex_init(&remmina_nx_init_mutex, NULL);
+ remmina_nx_window_id_array = g_array_new(FALSE, TRUE, sizeof(Window));
+
+ return TRUE;
+}