/* * 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 #include #include #include "common/remmina_plugin.h" #include #include #define LIBSSH_STATIC 1 #include #include #include #include #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; }