diff options
Diffstat (limited to 'plugins/nx/nx_plugin.c')
-rw-r--r-- | plugins/nx/nx_plugin.c | 831 |
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; +} |