1 /*
2  * vinagre-spice-plugin.c
3  * This file is part of vinagre
4  *
5  * Copyright (C) 2011 Red Hat, Inc.
6  * Author: Marc-Andre Lureau <marcandre.lureau@redhat.com>
7  *
8  * vinagre-spice-plugin.c is free software: you can redistribute it and/or modify it
9  * under the terms of the GNU General Public License as published by the
10  * Free Software Foundation, either version 2 of the License, or
11  * (at your option) any later version.
12  *
13  * vinagre-spice-plugin.c is distributed in the hope that it will be useful, but
14  * WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
16  * See the GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License along
19  * with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25 
26 #include <glib/gi18n-lib.h>
27 #include <gmodule.h>
28 
29 #include <vinagre/vinagre-cache-prefs.h>
30 #include <vinagre/vinagre-protocol.h>
31 
32 #include "vinagre-spice-plugin.h"
33 #include "vinagre-spice-connection.h"
34 #include "vinagre-spice-tab.h"
35 
36 void spice_register_types (void);
37 static void vinagre_spice_protocol_iface_init (VinagreProtocolInterface *iface);
38 
39 G_DEFINE_TYPE_EXTENDED (VinagreSpicePlugin,
40 			vinagre_spice_plugin,
41 			VINAGRE_TYPE_STATIC_EXTENSION,
42 			0,
43 			G_IMPLEMENT_INTERFACE (VINAGRE_TYPE_PROTOCOL,
44 					       vinagre_spice_protocol_iface_init))
45 
46 static const gchar *
impl_get_protocol(VinagreProtocol * plugin)47 impl_get_protocol (VinagreProtocol *plugin)
48 {
49   return "spice";
50 }
51 
52 static const gchar *
impl_get_mdns_service(VinagreProtocol * plugin)53 impl_get_mdns_service (VinagreProtocol *plugin)
54 {
55   return "_spice._tcp";
56 }
57 
58 static VinagreConnection *
impl_new_connection(VinagreProtocol * plugin)59 impl_new_connection (VinagreProtocol *plugin)
60 {
61   VinagreConnection *conn;
62 
63   conn = vinagre_spice_connection_new ();
64 
65   return conn;
66 }
67 
68 static VinagreConnection *
impl_new_connection_from_file(VinagreProtocol * plugin,const gchar * data,gboolean use_bookmarks,gchar ** error_msg)69 impl_new_connection_from_file (VinagreProtocol *plugin,
70 			       const gchar   *data,
71 			       gboolean	      use_bookmarks,
72 			       gchar	    **error_msg)
73 {
74   GKeyFile	    *file;
75   GError	    *error;
76   gboolean	     loaded;
77   gchar		    *host, *actual_host, *protocol;
78   gint		     port;
79   VinagreConnection *conn;
80 
81   *error_msg = NULL;
82   conn = NULL;
83   host = NULL;
84   protocol = NULL;
85   error = NULL;
86 
87   file = g_key_file_new ();
88   loaded = g_key_file_load_from_data (file,
89 				      data,
90 				      -1,
91 				      0,
92 				      &error);
93   if (!loaded)
94     {
95       if (error)
96 	{
97 	  *error_msg = g_strdup (error->message);
98 	  g_error_free (error);
99 	}
100       else
101 	*error_msg = g_strdup (_("Could not parse the file."));
102 
103       goto the_end;
104     }
105   if (!g_key_file_has_group (file, "connection"))
106     {
107       /* Translators: Do not translate "connection". It's the name of a group in the .spice (.ini like) file. */
108       *error_msg = g_strdup (_("The file is not a Spice one: Missing the group \"connection\"."));
109       goto the_end;
110     }
111 
112   if (!g_key_file_has_key (file, "connection", "host", NULL))
113     {
114       /* Translators: Do not translate "host". It's the name of a key in the .spice (.ini like) file. */
115       *error_msg = g_strdup (_("The file is not a Spice one: Missing the key \"host\"."));
116       goto the_end;
117     }
118 
119   host = g_key_file_get_string (file, "connection", "host", NULL);
120   port = g_key_file_get_integer (file, "connection", "port", NULL);
121   if (!port)
122     {
123       if (!vinagre_connection_split_string (host, "spice", &protocol, &actual_host, &port, error_msg))
124 	goto the_end;
125 
126       g_free (host);
127       host = actual_host;
128     }
129 
130   if (use_bookmarks)
131     conn = vinagre_bookmarks_exists (vinagre_bookmarks_get_default (), "spice", host, port);
132   if (!conn)
133     {
134       gchar *s_value;
135 
136       conn = vinagre_spice_connection_new ();
137       vinagre_connection_set_host (conn, host);
138       vinagre_connection_set_port (conn, port);
139 
140       s_value = g_key_file_get_string  (file, "connection", "password", NULL);
141       vinagre_connection_set_password (conn, s_value);
142       g_free (s_value);
143     }
144 
145  the_end:
146 
147   g_free (host);
148   g_free (protocol);
149   g_key_file_free (file);
150   return conn;
151 
152 }
153 
154 static gboolean
impl_recognize_file(VinagreProtocol * plugin,GFile * file)155 impl_recognize_file (VinagreProtocol *plugin, GFile *file)
156 {
157   gboolean result = FALSE;
158   gchar *filename = g_file_get_basename (file);
159 
160   if (filename)
161     {
162       result = g_str_has_suffix (filename, ".spice");
163       g_free (filename);
164     }
165 
166   return result;
167 }
168 
169 static gchar **
impl_get_public_description(VinagreProtocol * plugin)170 impl_get_public_description (VinagreProtocol *plugin)
171 {
172   gchar **result = g_new (gchar *, 3);
173 
174   result[0] = g_strdup (_("SPICE"));
175   /* Translators: This is a description of the SPICE protocol. It appears at Connect dialog. */
176   result[1] = g_strdup (_("Access Spice desktop server"));
177   result[2] = NULL;
178 
179   return result;
180 }
181 
182 static gint
impl_get_default_port(VinagreProtocol * plugin)183 impl_get_default_port (VinagreProtocol *plugin)
184 {
185   return 5900;
186 }
187 
188 static void
vinagre_spice_plugin_init(VinagreSpicePlugin * plugin)189 vinagre_spice_plugin_init (VinagreSpicePlugin *plugin)
190 {
191 }
192 
193 static GtkWidget *
impl_new_tab(VinagreProtocol * plugin,VinagreConnection * conn,VinagreWindow * window)194 impl_new_tab (VinagreProtocol *plugin,
195 	      VinagreConnection *conn,
196 	      VinagreWindow	*window)
197 {
198   return vinagre_spice_tab_new (conn, window);
199 }
200 
201 static void
ssh_tunnel_check_toggled_cb(GtkToggleButton * button,GObject * box)202 ssh_tunnel_check_toggled_cb (GtkToggleButton *button, GObject *box)
203 {
204   gboolean active = gtk_toggle_button_get_active (button);
205   GtkWidget *ssh_host_entry = g_object_get_data (G_OBJECT (box), "ssh_host");
206 
207   gtk_widget_set_sensitive (ssh_host_entry, active);
208 
209   if (active)
210     gtk_widget_grab_focus (ssh_host_entry);
211   else
212     gtk_entry_set_text (GTK_ENTRY (ssh_host_entry), "");
213 }
214 
215 static GtkWidget *
impl_get_connect_widget(VinagreProtocol * plugin,VinagreConnection * conn)216 impl_get_connect_widget (VinagreProtocol *plugin, VinagreConnection *conn)
217 {
218   GtkWidget *box, *label, *check, *p_entry, *box2, *ssh_host_entry, *grid;
219   gchar	    *str, *ssh_host;
220   gboolean  has_conn = VINAGRE_IS_SPICE_CONNECTION (conn);
221 
222   box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
223 
224   str = g_strdup_printf ("<b>%s</b>", _("SPICE Options"));
225   label = gtk_label_new (str);
226   g_free (str);
227   gtk_label_set_use_markup (GTK_LABEL (label), TRUE);
228   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
229   gtk_misc_set_padding (GTK_MISC (label), 0, 6);
230   gtk_box_pack_start (GTK_BOX (box), label, FALSE, FALSE, 0);
231 
232   grid = gtk_grid_new ();
233   label = gtk_label_new ("  ");
234   gtk_grid_attach (GTK_GRID (grid), label, 0, 0, 1, 1);
235 
236   /* View only check button - not fully ready on spice-gtk side */
237   check = gtk_check_button_new_with_mnemonic (_("_View only"));
238   g_object_set_data (G_OBJECT (box), "view_only", check);
239   /* gtk_table_attach_defaults (table, check, 1, 2, 0, 1); */
240   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check),
241 				has_conn ? vinagre_spice_connection_get_view_only (VINAGRE_SPICE_CONNECTION (conn))
242 				: vinagre_cache_prefs_get_boolean ("spice-connection", "view-only", FALSE));
243 
244   /* Resize guest check button */
245   check = gtk_check_button_new_with_mnemonic (_("_Resize guest"));
246   g_object_set_data (G_OBJECT (box), "resize_guest", check);
247   gtk_grid_attach (GTK_GRID (grid), check, 1, 0, 1, 1);
248   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check),
249 				has_conn ? vinagre_spice_connection_get_resize_guest (VINAGRE_SPICE_CONNECTION (conn))
250 				: vinagre_cache_prefs_get_boolean ("spice-connection", "resize-guest", TRUE));
251 
252   /* Clipboard sharing check button */
253   check = gtk_check_button_new_with_mnemonic (_("_Share clipboard"));
254   g_object_set_data (G_OBJECT (box), "auto_clipboard", check);
255   gtk_grid_attach (GTK_GRID (grid), check, 1, 1, 1, 1);
256   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check),
257 				has_conn ? vinagre_spice_connection_get_auto_clipboard (VINAGRE_SPICE_CONNECTION (conn))
258 				: vinagre_cache_prefs_get_boolean ("spice-connection", "auto-clipboard", TRUE));
259 
260   /* Scaling check button */
261   check = gtk_check_button_new_with_mnemonic (_("_Scaling"));
262   g_object_set_data (G_OBJECT (box), "scaling", check);
263   gtk_grid_attach (GTK_GRID (grid), check, 1, 2, 1, 1);
264   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check),
265 				has_conn ? vinagre_spice_connection_get_scaling (VINAGRE_SPICE_CONNECTION (conn))
266 				: vinagre_cache_prefs_get_boolean ("spice-connection", "scaling", FALSE));
267 
268   /* SSH Tunneling */
269   box2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
270 
271   /* Translators: the whole sentence will be: Use host <hostname> as a SSH tunnel*/
272   check = gtk_check_button_new_with_mnemonic (_("Use h_ost"));
273   g_object_set_data (G_OBJECT (box), "use_ssh", check);
274   gtk_box_pack_start (GTK_BOX (box2), check, FALSE, FALSE, 0);
275 
276   ssh_host_entry = gtk_entry_new ();
277   gtk_widget_set_sensitive (ssh_host_entry, FALSE);
278   g_object_set_data (G_OBJECT (box), "ssh_host", ssh_host_entry);
279   /* Translators: This is the tooltip of the SSH tunneling entry */
280   str = g_strdup_printf ("%s\n%s\n%s",
281 			 _("hostname or user@hostname"),
282 			 _("Supply an alternative port using colon"),
283 			 _("For instance: joe@example.com:5022"));
284   gtk_widget_set_tooltip_text (ssh_host_entry, str);
285   g_free (str);
286   gtk_box_pack_start (GTK_BOX (box2), ssh_host_entry, FALSE, FALSE, 0);
287 
288   /* Translators: the whole sentence will be: Use host <hostname> as a SSH tunnel*/
289   label = gtk_label_new_with_mnemonic (_("as a SSH tunnel"));
290   gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5);
291   gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 0);
292 
293   g_signal_connect (check,
294 		    "toggled",
295 		    G_CALLBACK (ssh_tunnel_check_toggled_cb),
296 		    box);
297 
298   ssh_host = has_conn ? g_strdup (vinagre_spice_connection_get_ssh_tunnel_host (VINAGRE_SPICE_CONNECTION (conn)))
299     : vinagre_cache_prefs_get_string  ("spice-connection", "ssh-tunnel-host", NULL);
300   if (ssh_host)
301     gtk_entry_set_text (GTK_ENTRY (ssh_host_entry), ssh_host);
302   gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), ssh_host && *ssh_host);
303   g_free (ssh_host);
304 
305   gtk_grid_attach (GTK_GRID (grid), box2, 1, 3, 1, 1);
306 
307   /* Password */
308   box2 = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
309 
310   label = gtk_label_new_with_mnemonic (_("_Password:"));
311   gtk_box_pack_start (GTK_BOX (box2), label, FALSE, FALSE, 0);
312 
313   p_entry = gtk_entry_new ();
314   /* Translators: This is the tooltip for the password field in a SPICE connection */
315   gtk_widget_set_tooltip_text (p_entry, _("Optional"));
316   g_object_set_data (G_OBJECT (box), "password_entry", p_entry);
317   gtk_box_pack_start (GTK_BOX (box2), p_entry, FALSE, FALSE, 0);
318   gtk_label_set_mnemonic_widget (GTK_LABEL (label), p_entry);
319   str = g_strdup (vinagre_cache_prefs_get_string  ("spice-connection", "password", ""));
320   gtk_entry_set_text (GTK_ENTRY (p_entry), str);
321   gtk_entry_set_activates_default (GTK_ENTRY (p_entry), TRUE);
322   g_free (str);
323   /* FIXME: how do you set the password here? gtk_table_attach_defaults (table, box2, 1, 2, 4, 5); */
324 
325   gtk_box_pack_start (GTK_BOX (box), grid, FALSE, FALSE, 0);
326   return box;
327 }
328 
329 static GtkFileFilter *
impl_get_file_filter(VinagreProtocol * plugin)330 impl_get_file_filter (VinagreProtocol *plugin)
331 {
332   GtkFileFilter *filter;
333 
334   filter = gtk_file_filter_new ();
335   /* Translators: this is a pattern to open *.spice files in a open dialog. */
336   gtk_file_filter_set_name (filter, _("Spice Files"));
337   gtk_file_filter_add_pattern (filter, "*.spice");
338 
339   return filter;
340 }
341 
342 static void
vinagre_spice_protocol_iface_init(VinagreProtocolInterface * iface)343 vinagre_spice_protocol_iface_init (VinagreProtocolInterface *iface)
344 {
345   iface->get_protocol  = impl_get_protocol;
346   iface->get_public_description	 = impl_get_public_description;
347   iface->get_default_port = impl_get_default_port;
348   iface->get_connect_widget = impl_get_connect_widget;
349   iface->get_mdns_service  = impl_get_mdns_service;
350   iface->get_file_filter = impl_get_file_filter;
351   iface->new_connection = impl_new_connection;
352   iface->new_tab = impl_new_tab;
353   iface->new_connection_from_file = impl_new_connection_from_file;
354   iface->recognize_file = impl_recognize_file;
355 }
356 
357 static void
vinagre_spice_plugin_class_finalize(VinagreSpicePluginClass * klass)358 vinagre_spice_plugin_class_finalize (VinagreSpicePluginClass *klass)
359 {
360 }
361 
362 static void
vinagre_spice_plugin_class_init(VinagreSpicePluginClass * klass)363 vinagre_spice_plugin_class_init (VinagreSpicePluginClass *klass)
364 {
365 }
366 
367 __attribute__((constructor)) void
spice_register_types(void)368 spice_register_types (void)
369 {
370   g_type_init ();
371   volatile dontoptimiseaway = vinagre_spice_plugin_get_type ();
372 }
373 
374 /* vim: set ts=8: */
375