Remmina - The GTK+ Remote Desktop Client  v1.4.19
Remmina is a remote desktop client written in GTK+, aiming to be useful for system administrators and travellers, who need to work with lots of remote computers in front of either large monitors or tiny netbooks. Remmina supports multiple network protocols in an integrated and consistent user interface. Currently RDP, VNC, NX, XDMCP and SSH are supported.
st_plugin.c
Go to the documentation of this file.
1 /*
2  * Remmina - The GTK+ Remote Desktop Client
3  * Copyright (C) 2017-2021 Antenore Gatta, Giovanni Panozzo
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  *
20  * In addition, as a special exception, the copyright holders give
21  * permission to link the code of portions of this program with the
22  * OpenSSL library under certain conditions as described in each
23  * individual source file, and distribute linked combinations
24  * including the two.
25  * You must obey the GNU General Public License in all respects
26  * for all of the code used other than OpenSSL. * If you modify
27  * file(s) with this exception, you may extend this exception to your
28  * version of the file(s), but you are not obligated to do so. * If you
29  * do not wish to do so, delete this exception statement from your
30  * version. * If you delete this exception statement from all source
31  * files in the program, then also delete it here.
32  *
33  */
34 
35 #include "st_plugin_config.h"
36 
37 #include "common/remmina_plugin.h"
38 
39 #include <gdk/gdkkeysyms.h>
40 #include <gtk/gtk.h>
41 #include <gtk/gtkx.h>
42 #include <glib.h>
43 #include <stdlib.h>
44 #include <sys/wait.h>
45 #include <unistd.h>
46 
47 #define REMMINA_PLUGIN_ST_FEATURE_GTKSOCKET 1
48 
49 typedef struct _RemminaPluginData
50 {
51  GtkWidget *socket;
52  gint socket_id;
53  GPid pid;
55 
57 #define REMMINA_PLUGIN_DEBUG(fmt, ...) remmina_plugin_service->_remmina_debug(__func__, fmt, ##__VA_ARGS__)
58 
60 {
61  TRACE_CALL(__func__);
62  return TRUE;
63 }
64 
65 
67 {
68  TRACE_CALL(__func__);
69  RemminaPluginData *gpdata;
70  gpdata = (RemminaPluginData*) g_object_get_data(G_OBJECT(gp), "plugin-data");
71  REMMINA_PLUGIN_DEBUG("[%s] Plugin plug added on socket %d", PLUGIN_NAME, gpdata->socket_id);
72  remmina_plugin_service->protocol_plugin_signal_connection_opened(gp);
73  return;
74 }
75 
77 {
78  TRACE_CALL(__func__);
79  REMMINA_PLUGIN_DEBUG("[%s] Plugin plug removed", PLUGIN_NAME);
80  remmina_plugin_service->protocol_plugin_signal_connection_closed(gp);
81 }
82 
84 {
85  TRACE_CALL(__func__);
86  REMMINA_PLUGIN_DEBUG("[%s] Plugin init", PLUGIN_NAME);
87  RemminaPluginData *gpdata;
88 
89  gpdata = g_new0(RemminaPluginData, 1);
90  g_object_set_data_full(G_OBJECT(gp), "plugin-data", gpdata, g_free);
91 
92  gpdata->socket = gtk_socket_new();
93  remmina_plugin_service->protocol_plugin_register_hostkey(gp, gpdata->socket);
94  gtk_widget_show(gpdata->socket);
95  g_signal_connect(G_OBJECT(gpdata->socket), "plug-added", G_CALLBACK(remmina_plugin_st_on_plug_added), gp);
96  g_signal_connect(G_OBJECT(gpdata->socket), "plug-removed", G_CALLBACK(remmina_plugin_st_on_plug_removed), gp);
97  gtk_container_add(GTK_CONTAINER(gp), gpdata->socket);
98 }
99 
101 {
102  TRACE_CALL(__func__);
103  REMMINA_PLUGIN_DEBUG("[%s] Plugin open connection", PLUGIN_NAME);
104 #define ADD_ARGUMENT(name, value) \
105  { \
106  argv[argc] = g_strdup(name); \
107  argv_debug[argc] = g_strdup(name); \
108  argc++; \
109  if (value != NULL) \
110  { \
111  argv[argc] = value; \
112  argv_debug[argc++] = g_strdup(g_strcmp0(name, "-p") != 0 ? value : "XXXXX"); \
113  } \
114  }
115  RemminaPluginData *gpdata;
116  RemminaFile *remminafile;
117  GError *error = NULL;
118  gchar *argv[50]; // Contains all the arguments
119  gchar *argv_debug[50]; // Contains all the arguments
120  gchar *command_line; // The whole command line obtained from argv_debug
121  const gchar *term; // Terminal emulator name from remimna profile.
122  const gchar *wflag = NULL;
123  const gchar *command; // The command to be passed to the terminal (if any)
124  gboolean isterm = FALSE;
125  gint argc;
126  gint i;
127 
128  gpdata = (RemminaPluginData*) g_object_get_data(G_OBJECT(gp), "plugin-data");
129  remminafile = remmina_plugin_service->protocol_plugin_get_file(gp);
130 
131  if (!remmina_plugin_service->file_get_int(remminafile, "detached", FALSE)) {
132  remmina_plugin_service->protocol_plugin_set_width(gp, 640);
133  remmina_plugin_service->protocol_plugin_set_height(gp, 480);
134  gtk_widget_set_size_request(GTK_WIDGET(gp), 640, 480);
135  gpdata->socket_id = gtk_socket_get_id(GTK_SOCKET(gpdata->socket));
136  }
137 
138  term = remmina_plugin_service->file_get_string(remminafile, "terminal");
139 
140  if (g_strcmp0(term, "st") == 0) {
141  /* on Debian based distros st is packaged as stterm */
142  if (!g_find_program_in_path(term))
143  term = "stterm";
144  wflag = "-w";
145  isterm = TRUE;
146  }else if (g_strcmp0(term, "urxvt") == 0) {
147  wflag = "-embed";
148  isterm = TRUE;
149  }else if (g_strcmp0(term, "xterm") == 0) {
150  wflag = "-xrm 'XTerm*allowSendEvents: true' -into";
151  isterm = TRUE;
152  }else if (g_strcmp0(term, "vim") == 0) {
153  wflag = "-g --socketid";
154  isterm = FALSE;
155  }else if (g_strcmp0(term, "emacs") == 0) {
156  wflag = "--parent-id";
157  isterm = FALSE;
158  }
159  if (!g_find_program_in_path(term)) {
160  remmina_plugin_service->protocol_plugin_set_error(gp, "%s not found", term);
161  return FALSE;
162  }
163 
164  argc = 0;
165  // Main executable name
166  ADD_ARGUMENT(g_strdup_printf("%s", term), NULL);
167  // Embed st-window in another window
168  if (gpdata->socket_id != 0)
169  ADD_ARGUMENT(g_strdup(wflag), g_strdup_printf("%i", gpdata->socket_id));
170  // Add eventually any additional arguments set by the user
171  command = remmina_plugin_service->file_get_string(remminafile, "cmd");
172  if(command && isterm)
173  ADD_ARGUMENT("-e", g_strdup_printf("%s", command));
174  if(command && !isterm)
175  ADD_ARGUMENT("", g_strdup_printf("%s", command));
176  // End of the arguments list
177  ADD_ARGUMENT(NULL, NULL);
178  // Retrieve the whole command line
179  command_line = g_strjoinv(g_strdup(" "), (gchar **)&argv_debug[0]);
180  REMMINA_PLUGIN_DEBUG("[%s] starting %s", PLUGIN_NAME, command_line);
181  // Execute the external process st
182  g_spawn_command_line_async(command_line, &error);
183  g_free(command_line);
184 
185  // Free the arguments list
186  for (i = 0; i < argc; i++)
187  {
188  g_free(argv_debug[i]);
189  g_free(argv[i]);
190  }
191  // Show error message
192  if (error) {
193  remmina_plugin_service->protocol_plugin_set_error(gp, "%s", error->message);
194  g_error_free(error);
195  return FALSE;
196  }
197  // Show attached window socket ID
198  if (!remmina_plugin_service->file_get_int(remminafile, "detached", FALSE)) {
199  REMMINA_PLUGIN_DEBUG("[%s] attached window to socket %d",
200  PLUGIN_NAME, gpdata->socket_id);
201  return TRUE;
202  }
203  else
204  {
205  return FALSE;
206  }
207 }
208 
210 {
211  TRACE_CALL(__func__);
212  REMMINA_PLUGIN_DEBUG("[%s] Plugin close connection", PLUGIN_NAME);
213  remmina_plugin_service->protocol_plugin_signal_connection_closed(gp);
214  return FALSE;
215 }
216 
217 static gpointer term_list[] =
218 {
219  "st", "Suckless Simple Terminal",
220  "urxvt", "rxvt-unicode",
221  "xterm", "Xterm",
222  "emacs", "GNU Emacs",
223  "vim", "Vim Text Editor",
224  NULL
225 };
226 
227 /* Array of RemminaProtocolSetting for basic settings.
228  * Each item is composed by:
229  * a) RemminaProtocolSettingType for setting type
230  * b) Setting name
231  * c) Setting description
232  * d) Compact disposition
233  * e) Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO
234  * f) Setting Tooltip
235  */
237 {
238  { REMMINA_PROTOCOL_SETTING_TYPE_SELECT, "terminal", N_("Terminal Emulator"), FALSE, term_list, NULL },
239  { REMMINA_PROTOCOL_SETTING_TYPE_TEXT, "cmd", N_("Command to be executed"), FALSE, NULL, NULL },
240  { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL }
241 };
242 
243 /* Array of RemminaProtocolSetting for advanced settings.
244  * Each item is composed by:
245  * a) RemminaProtocolSettingType for setting type
246  * b) Setting name
247  * c) Setting description
248  * d) Compact disposition
249  * e) Values for REMMINA_PROTOCOL_SETTING_TYPE_SELECT or REMMINA_PROTOCOL_SETTING_TYPE_COMBO
250  * f) Setting Tooltip
251  */
253 {
254  { REMMINA_PROTOCOL_SETTING_TYPE_CHECK, "detached", N_("Detached window"), TRUE, NULL, NULL },
255  { REMMINA_PROTOCOL_SETTING_TYPE_END, NULL, NULL, FALSE, NULL, NULL }
256 };
257 
258 /* Array for available features.
259  * The last element of the array must be REMMINA_PROTOCOL_FEATURE_TYPE_END. */
261 {
262  { REMMINA_PROTOCOL_FEATURE_TYPE_GTKSOCKET, REMMINA_PLUGIN_ST_FEATURE_GTKSOCKET, NULL, NULL, NULL},
263  { REMMINA_PROTOCOL_FEATURE_TYPE_END, 0, NULL, NULL, NULL}
264 };
265 
266 /* Protocol plugin definition and features */
268 {
270  PLUGIN_NAME, // Name
271  PLUGIN_DESCRIPTION, // Description
272  GETTEXT_PACKAGE, // Translation domain
273  PLUGIN_VERSION, // Version number
274  PLUGIN_APPICON, // Icon for normal connection
275  PLUGIN_APPICON, // Icon for SSH connection
276  remmina_plugin_st_basic_settings, // Array for basic settings
277  remmina_plugin_st_advanced_settings, // Array for advanced settings
278  REMMINA_PROTOCOL_SSH_SETTING_NONE, // SSH settings type
279  remmina_st_features, // Array for available features
280  remmina_plugin_st_init, // Plugin initialization
281  remmina_plugin_st_open_connection, // Plugin open connection
282  remmina_plugin_st_close_connection, // Plugin close connection
283  remmina_st_query_feature, // Query for available features
284  NULL, // Call a feature
285  NULL, // Send a keystroke
286  NULL, // Capture screenshot
287  NULL, // RCW map event
288  NULL // RCW unmap event
289 };
290 
291 G_MODULE_EXPORT gboolean remmina_plugin_entry(RemminaPluginService *service)
292 {
293  TRACE_CALL(__func__);
294  remmina_plugin_service = service;
295 
296  bindtextdomain(GETTEXT_PACKAGE, REMMINA_RUNTIME_LOCALEDIR);
297  bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
298 
299 
300  if (!service->register_plugin((RemminaPlugin *) &remmina_plugin))
301  {
302  return FALSE;
303  }
304  return TRUE;
305 }
static void remmina_plugin_st_init(RemminaProtocolWidget *gp)
Definition: st_plugin.c:83
void(* protocol_plugin_set_error)(RemminaProtocolWidget *gp, const gchar *fmt,...)
Definition: plugin.h:162
typedefG_BEGIN_DECLS struct _RemminaFile RemminaFile
Definition: types.h:43
static const RemminaProtocolSetting remmina_plugin_st_advanced_settings[]
Definition: st_plugin.c:252
static gboolean remmina_st_query_feature(RemminaProtocolWidget *gp, const RemminaProtocolFeature *feature)
Definition: st_plugin.c:59
void(* protocol_plugin_set_width)(RemminaProtocolWidget *gp, gint width)
Definition: plugin.h:155
static void remmina_plugin_st_on_plug_removed(GtkSocket *socket, RemminaProtocolWidget *gp)
Definition: st_plugin.c:76
static gboolean remmina_plugin_st_open_connection(RemminaProtocolWidget *gp)
Definition: st_plugin.c:100
void(* protocol_plugin_signal_connection_closed)(RemminaProtocolWidget *gp)
Definition: plugin.h:171
gint(* file_get_int)(RemminaFile *remminafile, const gchar *setting, gint default_value)
Definition: plugin.h:208
void(* protocol_plugin_signal_connection_opened)(RemminaProtocolWidget *gp)
Definition: plugin.h:172
static const RemminaProtocolFeature remmina_st_features[]
Definition: st_plugin.c:260
G_MODULE_EXPORT gboolean remmina_plugin_entry(RemminaPluginService *service)
Definition: st_plugin.c:291
gboolean(* register_plugin)(RemminaPlugin *plugin)
Definition: plugin.h:152
static RemminaPluginService * remmina_plugin_service
Definition: st_plugin.c:56
static gpointer term_list[]
Definition: st_plugin.c:217
GtkWidget * socket
Definition: st_plugin.c:51
RemminaFile *(* protocol_plugin_get_file)(RemminaProtocolWidget *gp)
Definition: plugin.h:164
static void remmina_plugin_st_on_plug_added(GtkSocket *socket, RemminaProtocolWidget *gp)
Definition: st_plugin.c:66
static RemminaProtocolPlugin remmina_plugin
Definition: st_plugin.c:267
void(* protocol_plugin_register_hostkey)(RemminaProtocolWidget *gp, GtkWidget *widget)
Definition: plugin.h:166
static gboolean remmina_plugin_st_close_connection(RemminaProtocolWidget *gp)
Definition: st_plugin.c:209
void(* protocol_plugin_set_height)(RemminaProtocolWidget *gp, gint height)
Definition: plugin.h:157
const gchar *(* file_get_string)(RemminaFile *remminafile, const gchar *setting)
Definition: plugin.h:205
static const RemminaProtocolSetting remmina_plugin_st_basic_settings[]
Definition: st_plugin.c:236
N_("Unable to connect to VNC server")
Definition: vnc_plugin.c:936
struct _RemminaPluginData RemminaPluginData