1 /*
2  * vinagre-spice-tab.c
3  * SPICE Implementation for VinagreSpiceTab widget
4  * This file is part of vinagre
5  *
6  * Copyright (C) 2011 Red Hat, Inc.
7  * Author: Marc-Andre Lureau <marcandre.lureau@redhat.com>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program. If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include <config.h>
24 #include <glib/gi18n.h>
25 #include <spice-client-glib-2.0/spice-session.h>
26 #include <spice-client-glib-2.0/spice-audio.h>
27 #include <spice-client-gtk-3.0/spice-widget.h>
28 #include <gdk/gdkkeysyms.h>
29 
30 #include <vinagre/vinagre-prefs.h>
31 
32 #include "vinagre-spice-tab.h"
33 #include "vinagre-spice-connection.h"
34 #include "vinagre-spice-tunnel.h"
35 #include "vinagre-vala.h"
36 
37 #define VINAGRE_SPICE_TAB_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), VINAGRE_TYPE_SPICE_TAB, VinagreSpiceTabPrivate))
38 
39 /* From spice-protocol.h */
40 #define SPICE_MAX_PASSWORD_LENGTH 60
41 
42 typedef struct _VinagreSpiceDisplay
43 {
44   GtkWidget  *display;
45 } VinagreSpiceDisplay;
46 
47 struct _VinagreSpiceTabPrivate
48 {
49   SpiceSession *spice;
50   SpiceAudio *audio;
51   GtkWidget  *display, *align;
52   gboolean   mouse_grabbed;
53   GSList     *connected_actions, *initialized_actions;
54   GtkWidget  *viewonly_button, *scaling_button;
55   GtkAction  *scaling_action, *viewonly_action, *original_size_action, *resize_guest_action, *auto_clipboard_action;
56   gulong     signal_align;
57   VinagreSpiceDisplay *wins[4]; /* TODO: handle multi-display */
58 };
59 
60 G_DEFINE_TYPE (VinagreSpiceTab, vinagre_spice_tab, VINAGRE_TYPE_TAB)
61 
62 /* Properties */
63 enum
64 {
65   PROP_0,
66 };
67 
68 static void open_spice (VinagreSpiceTab *spice_tab);
69 static void setup_toolbar (VinagreSpiceTab *spice_tab);
70 
71 static void
vinagre_spice_tab_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)72 vinagre_spice_tab_get_property (GObject	   *object,
73 				guint	    prop_id,
74 				GValue	   *value,
75 				GParamSpec *pspec)
76 {
77   switch (prop_id)
78     {
79     default:
80       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
81       break;
82     }
83 }
84 
85 static void
view_scaling_cb(GtkAction * action,VinagreSpiceTab * spice_tab)86 view_scaling_cb (GtkAction *action, VinagreSpiceTab *spice_tab)
87 {
88   vinagre_spice_tab_set_scaling (spice_tab,
89 				 gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
90 }
91 
92 static void
view_resize_guest_cb(GtkAction * action,VinagreSpiceTab * spice_tab)93 view_resize_guest_cb (GtkAction *action, VinagreSpiceTab *spice_tab)
94 {
95   vinagre_spice_tab_set_resize_guest (spice_tab,
96 				      gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
97 }
98 
99 static void
view_auto_clipboard_cb(GtkAction * action,VinagreSpiceTab * spice_tab)100 view_auto_clipboard_cb (GtkAction *action, VinagreSpiceTab *spice_tab)
101 {
102   vinagre_spice_tab_set_auto_clipboard (spice_tab,
103 					gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
104 }
105 
106 static void
view_viewonly_cb(GtkAction * action,VinagreSpiceTab * spice_tab)107 view_viewonly_cb (GtkAction *action, VinagreSpiceTab *spice_tab)
108 {
109   vinagre_spice_tab_set_viewonly (spice_tab,
110 				  gtk_toggle_action_get_active (GTK_TOGGLE_ACTION (action)));
111 }
112 
113 static void
send_ctrlaltdel_cb(GtkAction * action,VinagreSpiceTab * spice_tab)114 send_ctrlaltdel_cb (GtkAction *action, VinagreSpiceTab *spice_tab)
115 {
116   vinagre_spice_tab_send_ctrlaltdel (spice_tab);
117 }
118 
119 const static GSList *
spice_get_connected_actions(VinagreTab * tab)120 spice_get_connected_actions (VinagreTab *tab)
121 {
122   VinagreSpiceTab *spice_tab = VINAGRE_SPICE_TAB (tab);
123 
124   return spice_tab->priv->connected_actions;
125 }
126 
127 const static GSList *
spice_get_initialized_actions(VinagreTab * tab)128 spice_get_initialized_actions (VinagreTab *tab)
129 {
130   VinagreSpiceTab *spice_tab = VINAGRE_SPICE_TAB (tab);
131 
132   return spice_tab->priv->initialized_actions;
133 }
134 
135 static gchar *
spice_tab_get_tooltip(VinagreTab * tab)136 spice_tab_get_tooltip (VinagreTab *tab)
137 {
138   VinagreConnection *conn = vinagre_tab_get_conn (tab);
139 
140   return  g_markup_printf_escaped ("<b>%s</b> %s\n\n"
141 				   "<b>%s</b> %d\n",
142 				   _("Host:"), vinagre_connection_get_host (conn),
143 				   _("Port:"), vinagre_connection_get_port (conn));
144 }
145 
146 static void
vinagre_spice_tab_finalize(GObject * object)147 vinagre_spice_tab_finalize (GObject *object)
148 {
149   G_OBJECT_CLASS (vinagre_spice_tab_parent_class)->finalize (object);
150 }
151 
152 static void
vinagre_spice_tab_dispose(GObject * object)153 vinagre_spice_tab_dispose (GObject *object)
154 {
155   VinagreSpiceTab *spice_tab = VINAGRE_SPICE_TAB (object);
156 
157   if (spice_tab->priv->connected_actions)
158     {
159       vinagre_tab_free_actions (spice_tab->priv->connected_actions);
160       spice_tab->priv->connected_actions = NULL;
161     }
162 
163   if (spice_tab->priv->initialized_actions)
164     {
165       vinagre_tab_free_actions (spice_tab->priv->initialized_actions);
166       spice_tab->priv->initialized_actions = NULL;
167     }
168 
169   if (spice_tab->priv->audio)
170     {
171       g_object_unref (spice_tab->priv->audio);
172       spice_tab->priv->audio = NULL;
173     }
174 
175   if (spice_tab->priv->spice)
176     {
177       spice_session_disconnect (spice_tab->priv->spice);
178       g_object_unref (spice_tab->priv->spice);
179       spice_tab->priv->spice = NULL;
180     }
181 
182   G_OBJECT_CLASS (vinagre_spice_tab_parent_class)->dispose (object);
183 }
184 
185 static void
vinagre_spice_tab_constructed(GObject * object)186 vinagre_spice_tab_constructed (GObject *object)
187 {
188   VinagreSpiceTab *spice_tab = VINAGRE_SPICE_TAB (object);
189 
190   if (G_OBJECT_CLASS (vinagre_spice_tab_parent_class)->constructed)
191     G_OBJECT_CLASS (vinagre_spice_tab_parent_class)->constructed (object);
192 
193   setup_toolbar (spice_tab);
194   open_spice (spice_tab);
195 }
196 
197 static void
vinagre_spice_tab_class_init(VinagreSpiceTabClass * klass)198 vinagre_spice_tab_class_init (VinagreSpiceTabClass *klass)
199 {
200   GObjectClass *object_class = G_OBJECT_CLASS (klass);
201   VinagreTabClass* tab_class = VINAGRE_TAB_CLASS (klass);
202 
203   object_class->finalize = vinagre_spice_tab_finalize;
204   object_class->dispose = vinagre_spice_tab_dispose;
205   object_class->get_property = vinagre_spice_tab_get_property;
206   object_class->constructed = vinagre_spice_tab_constructed;
207 
208   tab_class->impl_get_tooltip = spice_tab_get_tooltip;
209   tab_class->impl_get_connected_actions = spice_get_connected_actions;
210   tab_class->impl_get_initialized_actions = spice_get_initialized_actions;
211 
212   g_type_class_add_private (object_class, sizeof (VinagreSpiceTabPrivate));
213 }
214 
215 static gboolean
idle_close(VinagreTab * tab)216 idle_close (VinagreTab *tab)
217 {
218   vinagre_notebook_close_tab (vinagre_tab_get_notebook (tab), tab);
219   return FALSE;
220 }
221 
222 static void
open_spice(VinagreSpiceTab * spice_tab)223 open_spice (VinagreSpiceTab *spice_tab)
224 {
225   gchar	     *host, *port_str, *ssh_tunnel_host;
226   gint	     port, fd;
227   gboolean   scaling, success;
228   GError     *error;
229   SpiceSession *spice = spice_tab->priv->spice;
230   VinagreTab *tab = VINAGRE_TAB (spice_tab);
231   GtkWindow  *window = GTK_WINDOW (vinagre_tab_get_window (tab));
232   VinagreConnection *conn = vinagre_tab_get_conn (tab);
233 
234   success = TRUE;
235   error = NULL;
236 
237   g_object_get (conn,
238 		"port", &port,
239 		"host", &host,
240 		"scaling", &scaling,
241 		"fd", &fd,
242 		"ssh-tunnel-host", &ssh_tunnel_host,
243 		NULL);
244 
245   port_str = g_strdup_printf ("%d", port);
246 
247   if (fd > 0)
248     success = spice_session_open_fd (spice, fd);
249   else
250     {
251       if (ssh_tunnel_host && *ssh_tunnel_host)
252 	if (!vinagre_spice_tunnel_create (window, &host, &port_str, ssh_tunnel_host, &error))
253 	  {
254 	    success = FALSE;
255 	    vinagre_utils_show_error_dialog (_("Error creating the SSH tunnel"),
256 				      error ? error->message : _("Unknown reason"),
257 				      window);
258 	    goto out;
259 	  }
260       g_object_set (spice, "host", host, "port", port_str,
261 		    "password", vinagre_connection_get_password (conn),
262 		    NULL);
263       success = spice_session_connect (spice);
264     }
265 
266   if (!success)
267     vinagre_utils_show_error_dialog (_("Error connecting to host."),
268 			      error ? error->message : _("Unknown reason"),
269 			      window);
270 
271  out:
272   g_free (port_str);
273   g_free (host);
274   g_free (ssh_tunnel_host);
275   g_clear_error (&error);
276 
277   if (!success)
278     g_idle_add ((GSourceFunc)idle_close, spice_tab);
279 }
280 
281 static void
spice_main_channel_event_cb(SpiceChannel * channel,SpiceChannelEvent event,VinagreSpiceTab * spice_tab)282 spice_main_channel_event_cb(SpiceChannel *channel, SpiceChannelEvent event,
283 			    VinagreSpiceTab *spice_tab)
284 {
285   switch (event) {
286   case SPICE_CHANNEL_OPENED:
287     g_signal_emit_by_name (G_OBJECT (spice_tab), "tab-connected");
288     break;
289   case SPICE_CHANNEL_CLOSED:
290     g_signal_emit_by_name (G_OBJECT (spice_tab), "tab-disconnected");
291     break;
292   case SPICE_CHANNEL_ERROR_AUTH: {
293     VinagreTab *tab = VINAGRE_TAB (spice_tab);
294     VinagreConnection *conn = vinagre_tab_get_conn (tab);
295     gchar *name = vinagre_connection_get_best_name (conn), *password = NULL;
296     GtkWindow *window = GTK_WINDOW (vinagre_tab_get_window (tab));
297     gboolean save_in_keyring = FALSE;
298 
299     vinagre_tab_remove_credentials_from_keyring (VINAGRE_TAB (tab));
300     if (!vinagre_utils_request_credential (window,
301 				       "SPICE",
302 				       name,
303 				       NULL,
304 				       NULL,
305 				       FALSE,
306 				       FALSE,
307 				       TRUE,
308 				       SPICE_MAX_PASSWORD_LENGTH,
309 				       NULL,
310 				       NULL,
311 				       &password,
312 				       &save_in_keyring))
313       {
314 	vinagre_tab_remove_from_notebook (tab);
315 	goto out;
316       }
317     vinagre_connection_set_password (conn, password);
318     vinagre_tab_set_save_credentials (tab, save_in_keyring);
319 
320     g_object_set (spice_tab->priv->spice, "password", password, NULL);
321     spice_session_connect (spice_tab->priv->spice);
322 
323     out:
324     g_free (password);
325     break;
326   }
327   case SPICE_CHANNEL_ERROR_IO:
328   case SPICE_CHANNEL_ERROR_TLS:
329   case SPICE_CHANNEL_ERROR_LINK:
330   case SPICE_CHANNEL_ERROR_CONNECT:
331     g_signal_emit_by_name (G_OBJECT (spice_tab), "tab-disconnected");
332     break;
333   default:
334     g_warning("unhandled main channel event: %d", event);
335     break;
336   }
337 }
338 
339 static void
spice_mouse_grab_cb(GtkWidget * widget,gint grabbed,VinagreSpiceTab * spice_tab)340 spice_mouse_grab_cb(GtkWidget *widget, gint grabbed, VinagreSpiceTab *spice_tab)
341 {
342   spice_tab->priv->mouse_grabbed = grabbed;
343 }
344 
345 static VinagreSpiceDisplay *
create_spice_display(VinagreSpiceTab * spice_tab,int id)346 create_spice_display (VinagreSpiceTab *spice_tab, int id)
347 {
348   VinagreSpiceDisplay *d;
349   GtkLabel *label;
350   gchar	   *name;
351   gboolean scaling, resize_guest, view_only, fullscreen, auto_clipboard;
352   VinagreTab *tab = VINAGRE_TAB (spice_tab);
353   VinagreConnection *conn = vinagre_tab_get_conn (tab);
354 
355   d = g_new0(VinagreSpiceDisplay, 1);
356 
357   /* Create the display widget */
358   spice_tab->priv->display = d->display = GTK_WIDGET (spice_display_new (spice_tab->priv->spice, id));
359 
360   vinagre_tab_add_view (tab, d->display);
361   vinagre_tab_set_has_screenshot (tab, TRUE);
362 
363   g_signal_connect (spice_tab->priv->display, "mouse-grab",
364 		    G_CALLBACK (spice_mouse_grab_cb), spice_tab);
365 
366   g_object_get (conn,
367 		"fullscreen", &fullscreen,
368 		"resize-guest", &resize_guest,
369 		"auto-clipboard", &auto_clipboard,
370 		"scaling", &scaling,
371 		"view-only", &view_only,
372 		NULL);
373 
374   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (spice_tab->priv->scaling_action), scaling);
375   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (spice_tab->priv->resize_guest_action), resize_guest);
376   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (spice_tab->priv->auto_clipboard_action), auto_clipboard);
377   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (spice_tab->priv->viewonly_action), view_only);
378   g_object_set (d->display,
379 		"grab-keyboard", TRUE,
380 		"grab-mouse", TRUE,
381 		"resize-guest", resize_guest,
382 		"auto-clipboard", auto_clipboard,
383 		"scaling", scaling,
384 		NULL);
385   /* TODO: add view-only here when spice-gtk ready */
386 
387   g_object_get (spice_tab->priv->spice, "uri", &name, NULL); /* TODO: a better friendly name? */
388   vinagre_spice_connection_set_desktop_name (VINAGRE_SPICE_CONNECTION (conn), name);
389   g_free (name);
390 
391   name = vinagre_connection_get_best_name (conn);
392   label = g_object_get_data (G_OBJECT (tab), "label");
393   g_return_val_if_fail (label != NULL, d);
394   gtk_label_set_label (label, name);
395   g_free (name);
396 
397   vinagre_tab_save_credentials_in_keyring (tab);
398   vinagre_tab_add_recent_used (tab);
399   vinagre_tab_set_state (tab, VINAGRE_TAB_STATE_CONNECTED);
400 
401   g_signal_emit_by_name (G_OBJECT (tab), "tab-initialized");
402 
403   gtk_widget_show_all (GTK_WIDGET (tab));
404 
405   return d;
406 }
407 
408 static void
destroy_spice_display(VinagreSpiceTab * tab,VinagreSpiceDisplay * display)409 destroy_spice_display (VinagreSpiceTab *tab, VinagreSpiceDisplay *display)
410 {
411 }
412 
413 static void
spice_channel_new_cb(SpiceSession * s,SpiceChannel * channel,VinagreSpiceTab * tab)414 spice_channel_new_cb (SpiceSession *s, SpiceChannel *channel, VinagreSpiceTab *tab)
415 {
416   int id;
417 
418   g_object_get (channel, "channel-id", &id, NULL);
419   g_object_ref (tab);
420 
421   if (SPICE_IS_MAIN_CHANNEL (channel)) {
422     g_signal_connect (channel, "channel-event",
423 		      G_CALLBACK (spice_main_channel_event_cb), tab);
424   }
425 
426   if (SPICE_IS_DISPLAY_CHANNEL (channel)) {
427     if (id >= G_N_ELEMENTS (tab->priv->wins))
428       return;
429     if (tab->priv->wins[id] != NULL)
430       return;
431     tab->priv->wins[id] = create_spice_display (tab, id);
432   }
433 
434   if (SPICE_IS_PLAYBACK_CHANNEL (channel) ||
435       SPICE_IS_RECORD_CHANNEL (channel)) {
436     if (tab->priv->audio != NULL)
437       return;
438     tab->priv->audio = spice_audio_new (s, NULL, NULL);
439   }
440 }
441 
442 static void
spice_channel_destroy_cb(SpiceSession * s,SpiceChannel * channel,VinagreSpiceTab * tab)443 spice_channel_destroy_cb (SpiceSession *s, SpiceChannel *channel, VinagreSpiceTab *tab)
444 {
445   int id;
446 
447   g_object_get (channel, "channel-id", &id, NULL);
448   g_object_unref (tab);
449 
450   if (SPICE_IS_DISPLAY_CHANNEL (channel)) {
451     if (id >= G_N_ELEMENTS (tab->priv->wins))
452       return;
453     if (tab->priv->wins[id] == NULL)
454       return;
455     destroy_spice_display (tab, tab->priv->wins[id]);
456     tab->priv->wins[id] = NULL;
457   }
458 }
459 
460 static GSList *
create_connected_actions(VinagreSpiceTab * tab)461 create_connected_actions (VinagreSpiceTab *tab)
462 {
463   GSList *list = NULL;
464   VinagreTabUiAction *a;
465 
466   /* View->Scaling */
467   a = g_slice_new (VinagreTabUiAction);
468   a->paths = g_new (gchar *, 3);
469   a->paths[0] = g_strdup ("/MenuBar/ViewMenu");
470   a->paths[1] = g_strdup ("/ToolBar");
471   a->paths[2] = NULL;
472   a->action = GTK_ACTION (gtk_toggle_action_new ("SPICEViewScaling",
473 						 _("S_caling"),
474 						 _("Fit the remote screen into the current window size"),
475 						 "zoom-fit-best"));
476   gtk_action_set_icon_name (a->action, "zoom-fit-best");
477   g_signal_connect (a->action, "activate", G_CALLBACK (view_scaling_cb), tab);
478   list = g_slist_append (list, a);
479   tab->priv->scaling_action = a->action;
480 
481   /* View->Resize Guest */
482   a = g_slice_new (VinagreTabUiAction);
483   a->paths = g_new (gchar *, 2);
484   a->paths[0] = g_strdup ("/MenuBar/ViewMenu");
485   a->paths[1] = NULL;
486   a->action = GTK_ACTION (gtk_toggle_action_new ("SPICEViewResizeGuest",
487 						 _("_Resize guest"),
488 						 _("Resize the screen guest to best fit"),
489 						 NULL));
490   g_signal_connect (a->action, "activate", G_CALLBACK (view_resize_guest_cb), tab);
491   list = g_slist_append (list, a);
492   tab->priv->resize_guest_action = a->action;
493 
494   /* View->Share clipboard */
495   a = g_slice_new (VinagreTabUiAction);
496   a->paths = g_new (gchar *, 2);
497   a->paths[0] = g_strdup ("/MenuBar/ViewMenu");
498   a->paths[1] = NULL;
499   a->action = GTK_ACTION (gtk_toggle_action_new ("SPICEViewAutoClipboard",
500 						 _("_Share clipboard"),
501 						 _("Automatically share clipboard between client and guest"),
502 						 NULL));
503   g_signal_connect (a->action, "activate", G_CALLBACK (view_auto_clipboard_cb), tab);
504   list = g_slist_append (list, a);
505   tab->priv->auto_clipboard_action = a->action;
506 
507   /* View->View Only TODO: not ready in spice-gtk yet */
508   a = g_slice_new (VinagreTabUiAction);
509   a->paths = g_new (gchar *, 3);
510   a->paths[0] = g_strdup ("/MenuBar/ViewMenu");
511   a->paths[1] = g_strdup ("/ToolBar");
512   a->paths[2] = NULL;
513   a->action = GTK_ACTION (gtk_toggle_action_new ("SPICEViewViewOnly",
514 						 _("_View only"),
515 						 _("Do not send mouse and keyboard events"),
516 						 "emblem-readonly"));
517   gtk_action_set_icon_name (a->action, "emblem-readonly");
518   g_signal_connect (a->action, "activate", G_CALLBACK (view_viewonly_cb), tab);
519   /* list = g_slist_append (list, a); */
520   tab->priv->viewonly_action = a->action;
521 
522   return list;
523 }
524 
525 static GSList *
create_initialized_actions(VinagreSpiceTab * tab)526 create_initialized_actions (VinagreSpiceTab *tab)
527 {
528   GSList *list = NULL;
529   VinagreTabUiAction *a;
530 
531   /* Remote->Send CTRL-ALT-DEL */
532   a = g_slice_new (VinagreTabUiAction);
533   a->paths = g_new (gchar *, 3);
534   a->paths[0] = g_strdup ("/MenuBar/RemoteeMenu/RemoteOps_1");
535   a->paths[1] = g_strdup ("/ToolBar");
536   a->paths[2] = NULL;
537   a->action = gtk_action_new ("SPICERemoteSendCtrlAltDel",
538 			      _("_Send Ctrl-Alt-Del"),
539 			      _("Send Ctrl+Alt+Del to the remote desktop"),
540 			      "preferences-desktop-keyboard-shortcuts");
541   gtk_action_set_is_important (a->action, TRUE);
542   gtk_action_set_icon_name (a->action, "preferences-desktop-keyboard-shortcuts");
543   g_signal_connect (a->action, "activate", G_CALLBACK (send_ctrlaltdel_cb), tab);
544   list = g_slist_append (list, a);
545 
546   return list;
547 }
548 
549 static void
viewonly_button_clicked(GtkToggleToolButton * button,VinagreSpiceTab * spice_tab)550 viewonly_button_clicked (GtkToggleToolButton *button,
551 			 VinagreSpiceTab     *spice_tab)
552 {
553   vinagre_spice_tab_set_viewonly (spice_tab, gtk_toggle_tool_button_get_active (button));
554 
555   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (spice_tab->priv->viewonly_action),
556 				vinagre_spice_tab_get_viewonly (spice_tab));
557 }
558 
559 static void
scaling_button_clicked(GtkToggleToolButton * button,VinagreSpiceTab * spice_tab)560 scaling_button_clicked (GtkToggleToolButton *button,
561 			VinagreSpiceTab	    *spice_tab)
562 {
563   if (!vinagre_spice_tab_set_scaling (spice_tab, gtk_toggle_tool_button_get_active (button)))
564     gtk_toggle_tool_button_set_active (button, FALSE);
565 
566   gtk_toggle_action_set_active (GTK_TOGGLE_ACTION (spice_tab->priv->scaling_action),
567 				vinagre_spice_tab_get_scaling (spice_tab));
568 }
569 
570 static void
cad_button_clicked(GtkToolButton * button,VinagreSpiceTab * spice_tab)571 cad_button_clicked (GtkToolButton *button,
572 		    VinagreSpiceTab *spice_tab)
573 {
574   vinagre_spice_tab_send_ctrlaltdel (spice_tab);
575 }
576 
577 static void
setup_toolbar(VinagreSpiceTab * spice_tab)578 setup_toolbar (VinagreSpiceTab *spice_tab)
579 {
580   GtkWidget *toolbar = vinagre_tab_get_toolbar (VINAGRE_TAB (spice_tab));
581   GtkWidget *button;
582 
583   /* Space */
584   button = GTK_WIDGET (gtk_separator_tool_item_new ());
585   gtk_tool_item_set_expand (GTK_TOOL_ITEM (button), TRUE);
586   gtk_widget_show (GTK_WIDGET (button));
587   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (button), -1);
588 
589   /* Scaling */
590   button = GTK_WIDGET (gtk_toggle_tool_button_new ());
591   gtk_tool_button_set_label (GTK_TOOL_BUTTON (button), _("Scaling"));
592   gtk_tool_item_set_tooltip_text (GTK_TOOL_ITEM (button), _("Scaling"));
593   gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (button), "zoom-fit-best");
594   gtk_widget_show (GTK_WIDGET (button));
595   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (button), -1);
596   g_signal_connect (button, "toggled", G_CALLBACK (scaling_button_clicked), spice_tab);
597   spice_tab->priv->scaling_button = button;
598 
599   /* Read only */
600   button = GTK_WIDGET (gtk_toggle_tool_button_new ());
601   gtk_tool_button_set_label (GTK_TOOL_BUTTON (button), _("Read only"));
602   gtk_tool_item_set_tooltip_text (GTK_TOOL_ITEM (button), _("Read only"));
603   gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (button), "emblem-readonly");
604   gtk_widget_show (GTK_WIDGET (button));
605   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (button), -1);
606   g_signal_connect (button, "toggled", G_CALLBACK (viewonly_button_clicked), spice_tab);
607   spice_tab->priv->viewonly_button = button;
608 
609   /* Send Ctrl-alt-del */
610   button = GTK_WIDGET (gtk_tool_button_new (NULL, _("Send Ctrl-Alt-Del")));
611   gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (button), "preferences-desktop-keyboard-shortcuts");
612   gtk_tool_item_set_tooltip_text (GTK_TOOL_ITEM (button), _("Send Ctrl+Alt+Del to the remote desktop"));
613   g_signal_connect (button, "clicked", G_CALLBACK (cad_button_clicked), spice_tab);
614   gtk_widget_show (GTK_WIDGET (button));
615   gtk_toolbar_insert (GTK_TOOLBAR (toolbar), GTK_TOOL_ITEM (button), -1);
616 }
617 
618 static void
vinagre_spice_tab_init(VinagreSpiceTab * spice_tab)619 vinagre_spice_tab_init (VinagreSpiceTab *spice_tab)
620 {
621   SpiceSession *spice;
622 
623   spice_tab->priv = VINAGRE_SPICE_TAB_GET_PRIVATE (spice_tab);
624   spice_tab->priv->connected_actions = create_connected_actions (spice_tab);
625   spice_tab->priv->initialized_actions = create_initialized_actions (spice_tab);
626 
627   /* Create the spice widget */
628   spice = spice_tab->priv->spice = spice_session_new ();
629   g_signal_connect(spice, "channel-new",
630 		   G_CALLBACK(spice_channel_new_cb), spice_tab);
631   g_signal_connect(spice, "channel-destroy",
632 		   G_CALLBACK(spice_channel_destroy_cb), spice_tab);
633 }
634 
635 GtkWidget *
vinagre_spice_tab_new(VinagreConnection * conn,VinagreWindow * window)636 vinagre_spice_tab_new (VinagreConnection *conn,
637 		       VinagreWindow	 *window)
638 {
639   return GTK_WIDGET (g_object_new (VINAGRE_TYPE_SPICE_TAB,
640 				   "conn", conn,
641 				   "window", window,
642 				   NULL));
643 }
644 
645 void
vinagre_spice_tab_send_ctrlaltdel(VinagreSpiceTab * tab)646 vinagre_spice_tab_send_ctrlaltdel (VinagreSpiceTab *tab)
647 {
648   guint keys[] = { GDK_KEY_Control_L, GDK_KEY_Alt_L, GDK_KEY_Delete };
649 
650   g_return_if_fail (VINAGRE_IS_SPICE_TAB (tab));
651 
652   spice_display_send_keys (SPICE_DISPLAY (tab->priv->display), keys, sizeof (keys) / sizeof (keys[0]), SPICE_DISPLAY_KEY_EVENT_CLICK);
653 }
654 
655 gboolean
vinagre_spice_tab_set_scaling(VinagreSpiceTab * tab,gboolean active)656 vinagre_spice_tab_set_scaling (VinagreSpiceTab *tab, gboolean active)
657 {
658   gboolean scaling;
659 
660   g_return_val_if_fail (VINAGRE_IS_SPICE_TAB (tab), FALSE);
661 
662   g_object_get (tab->priv->display, "scaling", &scaling, NULL);
663   if (scaling == active)
664     return TRUE;
665 
666   g_object_set (tab->priv->display, "scaling", active, NULL);
667 
668   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (tab->priv->scaling_button),
669 				     active);
670 
671   return TRUE;
672 }
673 
674 gboolean
vinagre_spice_tab_get_scaling(VinagreSpiceTab * tab)675 vinagre_spice_tab_get_scaling (VinagreSpiceTab *tab)
676 {
677   gboolean scaling;
678 
679   g_return_val_if_fail (VINAGRE_IS_SPICE_TAB (tab), FALSE);
680 
681   g_object_get (tab->priv->display, "scaling", &scaling, NULL);
682 
683   return scaling;
684 }
685 
686 void
vinagre_spice_tab_set_viewonly(VinagreSpiceTab * tab,gboolean active)687 vinagre_spice_tab_set_viewonly (VinagreSpiceTab *tab, gboolean active)
688 {
689   g_return_if_fail (VINAGRE_IS_SPICE_TAB (tab));
690 
691 #if 0
692   g_object_set (tab->priv->display, "read-only", active, NULL); /* only in future version of spice-gtk */
693 #endif
694 
695   gtk_toggle_tool_button_set_active (GTK_TOGGLE_TOOL_BUTTON (tab->priv->viewonly_button),
696 				     active);
697 }
698 
699 gboolean
vinagre_spice_tab_get_viewonly(VinagreSpiceTab * tab)700 vinagre_spice_tab_get_viewonly (VinagreSpiceTab *tab)
701 {
702   gboolean active = FALSE;
703 
704   g_return_val_if_fail (VINAGRE_IS_SPICE_TAB (tab), FALSE);
705 #if 0
706   g_object_get (tab->priv->display, "read-only", &active, NULL);  /* only in future version of spice-gtk */
707 #endif
708   return active;
709 }
710 
711 gboolean
vinagre_spice_tab_set_resize_guest(VinagreSpiceTab * tab,gboolean active)712 vinagre_spice_tab_set_resize_guest (VinagreSpiceTab *tab, gboolean active)
713 {
714   g_return_val_if_fail (VINAGRE_IS_SPICE_TAB (tab), FALSE);
715 
716   g_object_set (tab->priv->display, "resize-guest", active, NULL);
717 
718   return TRUE;
719 }
720 
721 gboolean
vinagre_spice_tab_get_resize_guest(VinagreSpiceTab * tab)722 vinagre_spice_tab_get_resize_guest (VinagreSpiceTab *tab)
723 {
724   gboolean active;
725 
726   g_return_val_if_fail (VINAGRE_IS_SPICE_TAB (tab), FALSE);
727 
728   g_object_get (tab->priv->display, "resize-guest", &active, NULL);
729 
730   return active;
731 }
732 
733 void
vinagre_spice_tab_set_auto_clipboard(VinagreSpiceTab * tab,gboolean active)734 vinagre_spice_tab_set_auto_clipboard (VinagreSpiceTab *tab, gboolean active)
735 {
736   g_return_if_fail (VINAGRE_IS_SPICE_TAB (tab));
737 
738   g_object_set (tab->priv->display, "auto-clipboard", active, NULL);
739 }
740 
741 gboolean
vinagre_spice_tab_get_auto_clipboard(VinagreSpiceTab * tab)742 vinagre_spice_tab_get_auto_clipboard (VinagreSpiceTab *tab)
743 {
744   gboolean active;
745 
746   g_return_val_if_fail (VINAGRE_IS_SPICE_TAB (tab), FALSE);
747 
748   g_object_get (tab->priv->display, "auto-clipboard", &active, NULL);
749 
750   return active;
751 }
752 
753 gboolean
vinagre_spice_tab_is_mouse_grab(VinagreSpiceTab * tab)754 vinagre_spice_tab_is_mouse_grab (VinagreSpiceTab *tab)
755 {
756   g_return_val_if_fail (VINAGRE_IS_SPICE_TAB (tab), FALSE);
757 
758   return tab->priv->mouse_grabbed;
759 }
760 
761 /* vim: set ts=8: */
762