Remmina - The GTK+ Remote Desktop Client  v1.4.2
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.
remmina_chat_window.c
Go to the documentation of this file.
1 /*
2  * Remmina - The GTK+ Remote Desktop Client
3  * Copyright (C) 2009 - Vic Lee
4  * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
5  * Copyright (C) 2016-2020 Antenore Gatta, Giovanni Panozzo
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21  *
22  * In addition, as a special exception, the copyright holders give
23  * permission to link the code of portions of this program with the
24  * OpenSSL library under certain conditions as described in each
25  * individual source file, and distribute linked combinations
26  * including the two.
27  * You must obey the GNU General Public License in all respects
28  * for all of the code used other than OpenSSL. * If you modify
29  * file(s) with this exception, you may extend this exception to your
30  * version of the file(s), but you are not obligated to do so. * If you
31  * do not wish to do so, delete this exception statement from your
32  * version. * If you delete this exception statement from all source
33  * files in the program, then also delete it here.
34  *
35  */
36 
37 #include <gtk/gtk.h>
38 #include <glib/gi18n.h>
39 #include <gdk/gdkkeysyms.h>
40 #include "remmina_chat_window.h"
42 
43 G_DEFINE_TYPE( RemminaChatWindow, remmina_chat_window, GTK_TYPE_WINDOW)
44 
45 enum {
46  SEND_SIGNAL,
48 };
49 
51 
53 {
54  TRACE_CALL(__func__);
55  remmina_chat_window_signals[SEND_SIGNAL] = g_signal_new("send", G_TYPE_FROM_CLASS(klass),
56  G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION, G_STRUCT_OFFSET(RemminaChatWindowClass, send), NULL, NULL,
57  g_cclosure_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING);
58 }
59 
61 {
62  TRACE_CALL(__func__);
63  window->history_text = NULL;
64  window->send_text = NULL;
65 }
66 
67 static void remmina_chat_window_clear_send_text(GtkWidget* widget, RemminaChatWindow* window)
68 {
69  TRACE_CALL(__func__);
70  GtkTextBuffer* buffer;
71 
72  buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(window->send_text));
73  gtk_text_buffer_set_text(buffer, "", -1);
74  gtk_widget_grab_focus(window->send_text);
75 }
76 
78 {
79  TRACE_CALL(__func__);
80  GtkTextBuffer* buffer;
81  GtkTextIter iter;
82 
83  buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(window->history_text));
84  gtk_text_buffer_get_end_iter(buffer, &iter);
85  gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(window->history_text), &iter, 0.0, FALSE, 0.0, 0.0);
86 
87  return FALSE;
88 }
89 
90 static void remmina_chat_window_append_text(RemminaChatWindow* window, const gchar* name, const gchar* tagname,
91  const gchar* text)
92 {
93  TRACE_CALL(__func__);
94  GtkTextBuffer* buffer;
95  GtkTextIter iter;
96  gchar* ptr;
97 
98  buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(window->history_text));
99 
100  if (name) {
101  ptr = g_strdup_printf("(%s) ", name);
102  gtk_text_buffer_get_end_iter(buffer, &iter);
103  if (tagname) {
104  gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, ptr, -1, tagname, NULL);
105  }else {
106  gtk_text_buffer_insert(buffer, &iter, ptr, -1);
107  }
108  g_free(ptr);
109  }
110 
111  if (text && text[0] != 0) {
112  gtk_text_buffer_get_end_iter(buffer, &iter);
113  if (text[strlen(text) - 1] == '\n') {
114  gtk_text_buffer_insert(buffer, &iter, text, -1);
115  }else {
116  ptr = g_strdup_printf("%s\n", text);
117  gtk_text_buffer_insert(buffer, &iter, ptr, -1);
118  g_free(ptr);
119  }
120  }
121 
122  /* Use g_idle_add to make the scroll happen after the text has been actually updated */
123  g_idle_add((GSourceFunc)remmina_chat_window_scroll_proc, window);
124 }
125 
126 static void remmina_chat_window_send(GtkWidget* widget, RemminaChatWindow* window)
127 {
128  TRACE_CALL(__func__);
129  GtkTextBuffer* buffer;
130  GtkTextIter start, end;
131  gchar* text;
132 
133  buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(window->send_text));
134  gtk_text_buffer_get_bounds(buffer, &start, &end);
135  text = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
136 
137  if (!text || text[0] == '\0')
138  return;
139 
140  g_signal_emit_by_name(G_OBJECT(window), "send", text);
141 
142  remmina_chat_window_append_text(window, g_get_user_name(), "sender-foreground", text);
143 
144  g_free(text);
145 
146  remmina_chat_window_clear_send_text(widget, window);
147 }
148 
149 static gboolean remmina_chat_window_send_text_on_key(GtkWidget* widget, GdkEventKey* event, RemminaChatWindow* window)
150 {
151  TRACE_CALL(__func__);
152  if (event->keyval == GDK_KEY_Return) {
153  remmina_chat_window_send(widget, window);
154  return TRUE;
155  }
156  return FALSE;
157 }
158 
159 GtkWidget*
160 remmina_chat_window_new(GtkWindow* parent, const gchar* chat_with)
161 {
162  TRACE_CALL(__func__);
163  RemminaChatWindow* window;
164  gchar buf[100];
165  GtkWidget* grid;
166  GtkWidget* scrolledwindow;
167  GtkWidget* widget;
168  GtkWidget* image;
169  GtkTextBuffer* buffer;
170 
171  window = REMMINA_CHAT_WINDOW(g_object_new(REMMINA_TYPE_CHAT_WINDOW, NULL));
172 
173  if (parent) {
174  gtk_window_set_transient_for(GTK_WINDOW(window), parent);
175  }
176 
177  /* Title */
178  g_snprintf(buf, sizeof(buf), _("Chat with %s"), chat_with);
179  gtk_window_set_title(GTK_WINDOW(window), buf);
180  gtk_window_set_default_size(GTK_WINDOW(window), 450, 300);
181 
182  /* Main container */
183  grid = gtk_grid_new();
184  gtk_widget_show(grid);
185  gtk_grid_set_row_spacing(GTK_GRID(grid), 4);
186  gtk_grid_set_column_spacing(GTK_GRID(grid), 4);
187  gtk_container_set_border_width(GTK_CONTAINER(grid), 8);
188  gtk_container_add(GTK_CONTAINER(window), grid);
189 
190  /* Chat history */
191  scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
192  gtk_widget_show(scrolledwindow);
193  gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(scrolledwindow), 100);
194  gtk_widget_set_hexpand(GTK_WIDGET(scrolledwindow), TRUE);
195  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
196  gtk_grid_attach(GTK_GRID(grid), scrolledwindow, 0, 0, 3, 1);
197 
198  widget = gtk_text_view_new();
199  gtk_widget_show(widget);
200  gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(widget), GTK_WRAP_WORD_CHAR);
201  gtk_text_view_set_editable(GTK_TEXT_VIEW(widget), FALSE);
202  gtk_container_add(GTK_CONTAINER(scrolledwindow), widget);
203 
204  buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
205  gtk_text_buffer_create_tag(buffer, "sender-foreground", "foreground", "blue", NULL);
206  gtk_text_buffer_create_tag(buffer, "receiver-foreground", "foreground", "red", NULL);
207 
208  window->history_text = widget;
209 
210  /* Chat message to be sent */
211  scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
212  gtk_widget_show(scrolledwindow);
213  gtk_scrolled_window_set_min_content_height(GTK_SCROLLED_WINDOW(scrolledwindow), 100);
214  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
215  gtk_widget_set_hexpand(GTK_WIDGET(scrolledwindow), TRUE);
216  gtk_grid_attach(GTK_GRID(grid), scrolledwindow, 0, 1, 3, 1);
217 
218  widget = gtk_text_view_new();
219  gtk_widget_show(widget);
220  gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(widget), GTK_WRAP_WORD_CHAR);
221  gtk_container_add(GTK_CONTAINER(scrolledwindow), widget);
222  g_signal_connect(G_OBJECT(widget), "key-press-event", G_CALLBACK(remmina_chat_window_send_text_on_key), window);
223 
224  window->send_text = widget;
225 
226  /* Send button */
227  image = gtk_image_new_from_icon_name("remmina-document-send-symbolic", GTK_ICON_SIZE_BUTTON);
228  gtk_widget_show(image);
229 
230  widget = gtk_button_new_with_mnemonic(_("_Send"));
231  gtk_widget_show(widget);
232  gtk_button_set_image(GTK_BUTTON(widget), image);
233  gtk_grid_attach(GTK_GRID(grid), widget, 2, 2, 1, 1);
234  g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_chat_window_send), window);
235 
236  /* Clear button */
237  image = gtk_image_new_from_icon_name("edit-clear", GTK_ICON_SIZE_BUTTON);
238  gtk_widget_show(image);
239 
240  widget = gtk_button_new_with_mnemonic(_("_Clear"));
241  gtk_widget_show(widget);
242  gtk_button_set_image(GTK_BUTTON(widget), image);
243  gtk_grid_attach(GTK_GRID(grid), widget, 1, 2, 1, 1);
244  g_signal_connect(G_OBJECT(widget), "clicked", G_CALLBACK(remmina_chat_window_clear_send_text), window);
245 
246  gtk_widget_grab_focus(window->send_text);
247 
248  return GTK_WIDGET(window);
249 }
250 
251 void remmina_chat_window_receive(RemminaChatWindow* window, const gchar* name, const gchar* text)
252 {
253  TRACE_CALL(__func__);
254  remmina_chat_window_append_text(window, name, "receiver-foreground", text);
255 }
256 
static void remmina_chat_window_append_text(RemminaChatWindow *window, const gchar *name, const gchar *tagname, const gchar *text)
static guint remmina_chat_window_signals[LAST_SIGNAL]
static void remmina_chat_window_send(GtkWidget *widget, RemminaChatWindow *window)
G_DEFINE_TYPE(RemminaChatWindow, remmina_chat_window, GTK_TYPE_WINDOW)
static gboolean remmina_chat_window_send_text_on_key(GtkWidget *widget, GdkEventKey *event, RemminaChatWindow *window)
static void remmina_chat_window_class_init(RemminaChatWindowClass *klass)
static void remmina_chat_window_init(RemminaChatWindow *window)
static gboolean remmina_chat_window_scroll_proc(RemminaChatWindow *window)
static void remmina_chat_window_clear_send_text(GtkWidget *widget, RemminaChatWindow *window)
void remmina_chat_window_receive(RemminaChatWindow *window, const gchar *name, const gchar *text)
GtkWidget * remmina_chat_window_new(GtkWindow *parent, const gchar *chat_with)