Remmina - The GTK+ Remote Desktop Client  v1.4.33
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_log.c
Go to the documentation of this file.
1 /*
2  * Remmina - The GTK+ Remote Desktop Client
3  * Copyright (C) 2010 Vic Lee
4  * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo
5  * Copyright (C) 2016-2023 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 "remmina_public.h"
40 #include "remmina_pref.h"
41 #include "remmina_log.h"
42 #include "remmina_stats.h"
44 
45 gboolean logstart;
46 
47 /***** Define the log window GUI *****/
48 #define REMMINA_TYPE_LOG_WINDOW (remmina_log_window_get_type())
49 #define REMMINA_LOG_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), REMMINA_TYPE_LOG_WINDOW, RemminaLogWindow))
50 #define REMMINA_LOG_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), REMMINA_TYPE_LOG_WINDOW, RemminaLogWindowClass))
51 #define REMMINA_IS_LOG_WINDOW(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), REMMINA_TYPE_LOG_WINDOW))
52 #define REMMINA_IS_LOG_WINDOW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), REMMINA_TYPE_LOG_WINDOW))
53 #define REMMINA_LOG_WINDOW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), REMMINA_TYPE_LOG_WINDOW, RemminaLogWindowClass))
54 
55 typedef struct _RemminaLogWindow {
56  GtkWindow window;
57 
58  GtkWidget *log_view;
59  GtkTextBuffer *log_buffer;
61 
62 typedef struct _RemminaLogWindowClass {
63  GtkWindowClass parent_class;
65 
67 G_GNUC_CONST;
68 
69 G_DEFINE_TYPE(RemminaLogWindow, remmina_log_window, GTK_TYPE_WINDOW)
70 
71 void remmina_log_stats()
72 {
73  TRACE_CALL(__func__);
74  JsonNode *n;
75 
77  if (n != NULL) {
78 
79  JsonGenerator *g = json_generator_new();
80  json_generator_set_pretty (g, TRUE);
81  json_generator_set_root(g, n);
82  json_node_unref(n);
83  g_autofree gchar *s = json_generator_to_data(g, NULL); // s=serialized stats
84  REMMINA_DEBUG("STATS: JSON data%s\n", s);
85  g_object_unref(g);
86  }
87 }
88 
90 {
91  TRACE_CALL(__func__);
92 }
93 
94 /* We will always only have one log window per instance */
95 static GtkWidget *log_window = NULL;
96 
97 static GtkWidget*
99 {
100  TRACE_CALL(__func__);
101  return GTK_WIDGET(g_object_new(REMMINA_TYPE_LOG_WINDOW, NULL));
102 }
103 
104 static void remmina_log_end(GtkWidget *widget, gpointer data)
105 {
106  TRACE_CALL(__func__);
107  log_window = NULL;
108 }
109 
110 static void remmina_log_start_stop (GtkSwitch *logswitch, gpointer user_data)
111 {
112  TRACE_CALL(__func__);
113  logstart = !logstart;
114 }
115 
117 {
118  TRACE_CALL(__func__);
119  if (log_window) {
120  gtk_window_present(GTK_WINDOW(log_window));
121  }else {
123  gtk_window_set_default_size(GTK_WINDOW(log_window), 640, 480);
124  gtk_window_set_resizable (GTK_WINDOW(log_window), TRUE);
125  gtk_window_set_decorated (GTK_WINDOW(log_window), TRUE);
126 
127  /* Header bar */
128  GtkWidget *header = gtk_header_bar_new ();
129  gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (header), TRUE);
130  gtk_header_bar_set_title (GTK_HEADER_BAR (header), _("Remmina debugging window"));
131  gtk_header_bar_set_has_subtitle (GTK_HEADER_BAR (header), FALSE);
132  /* Stats */
133  GtkWidget *getstat = gtk_button_new ();
134  gtk_widget_set_tooltip_text (getstat, _("Paste system info in the Remmina debugging window"));
135  GIcon *icon = g_themed_icon_new ("edit-paste-symbolic");
136  GtkWidget *image = gtk_image_new_from_gicon (icon, GTK_ICON_SIZE_BUTTON);
137  g_object_unref (icon);
138  gtk_container_add (GTK_CONTAINER (getstat), image);
139  gtk_header_bar_pack_start (GTK_HEADER_BAR (header), getstat);
140  /* Start logging */
141  GtkWidget *start = gtk_switch_new ();
142  logstart = TRUE;
143  gtk_switch_set_active (GTK_SWITCH(start), logstart);
144  gtk_widget_set_valign (start, GTK_ALIGN_CENTER);
145  gtk_header_bar_pack_start (GTK_HEADER_BAR (header), start);
146 
147  gtk_window_set_titlebar (GTK_WINDOW (log_window), header);
148 
149  g_signal_connect(getstat, "button-press-event", G_CALLBACK(remmina_log_stats), NULL);
150  g_signal_connect(start, "notify::active", G_CALLBACK(remmina_log_start_stop), NULL);
151  g_signal_connect(G_OBJECT(log_window), "destroy", G_CALLBACK(remmina_log_end), NULL);
152  gtk_widget_show_all(log_window);
153  }
154 
155  remmina_log_print(_("This window can help you find connection problems.\n"
156  "You can stop and start the logging at any moment using the On/Off switch.\n"
157  "The stats button (Ctrl+T), can be useful to gather system info you may share when reporting a bug.\n"
158  "There is more info about debugging Remmina on https://gitlab.com/Remmina/Remmina/-/wikis/Usage/Remmina-debugging\n"
159  ));
160 }
161 
162 gboolean remmina_log_running(void)
163 {
164  TRACE_CALL(__func__);
165  return (log_window != NULL);
166 }
167 
168 static gboolean remmina_log_scroll_to_end(gpointer data)
169 {
170  TRACE_CALL(__func__);
171  GtkTextIter iter;
172 
173  if (log_window) {
174  gtk_text_buffer_get_end_iter(REMMINA_LOG_WINDOW(log_window)->log_buffer, &iter);
175  gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(REMMINA_LOG_WINDOW(log_window)->log_view), &iter, 0.0, FALSE, 0.0,
176  0.0);
177  }
178  return FALSE;
179 }
180 
181 static gboolean remmina_log_print_real(gpointer data)
182 {
183  TRACE_CALL(__func__);
184  GtkTextIter iter;
185 
186  if (log_window && logstart) {
187  gtk_text_buffer_get_end_iter(REMMINA_LOG_WINDOW(log_window)->log_buffer, &iter);
188  gtk_text_buffer_insert(REMMINA_LOG_WINDOW(log_window)->log_buffer, &iter, (const gchar*)data, -1);
189  IDLE_ADD(remmina_log_scroll_to_end, NULL);
190  }
191  g_free(data);
192  return FALSE;
193 }
194 
195 // Only prints into Remmina's own debug window. (Not stdout!)
196 // See _remmina_{debug, info, error, critical, warning}
197 void remmina_log_print(const gchar *text)
198 {
199  TRACE_CALL(__func__);
200  if (!log_window)
201  return;
202 
203  IDLE_ADD(remmina_log_print_real, g_strdup(text));
204 }
205 
206 void _remmina_info(const gchar *fmt, ...)
207 {
208  TRACE_CALL(__func__);
209 
210  va_list args;
211  g_autofree gchar *text;
212  va_start(args, fmt);
213  text = g_strdup_vprintf(fmt, args);
214  va_end(args);
215 
216  // always appends newline
217  g_info ("%s", text);
218 
219  g_autofree gchar *buf_tmp = g_strconcat(text, "\n", NULL);
220  /* freed in remmina_log_print_real */
221  gchar *bufn = g_strconcat("(INFO) - ", buf_tmp, NULL);
222 
223  if (!log_window) {
224  free(bufn);
225  return;
226  }
227  IDLE_ADD(remmina_log_print_real, bufn);
228 }
229 
230 void _remmina_message(const gchar *fmt, ...)
231 {
232  TRACE_CALL(__func__);
233 
234  va_list args;
235  g_autofree gchar *text;
236  va_start(args, fmt);
237  text = g_strdup_vprintf(fmt, args);
238  va_end(args);
239 
240  // always appends newline
241  g_message ("%s", text);
242 
243  if (!log_window) {
244  return;
245  }
246 
247  g_autofree gchar *buf_tmp = g_strconcat(text, "\n", NULL);
248  /* freed in remmina_log_print_real */
249  gchar *bufn = g_strconcat("(MESSAGE) - ", buf_tmp, NULL);
250 
251  IDLE_ADD(remmina_log_print_real, bufn);
252 }
253 
259 void _remmina_debug(const gchar *fun, const gchar *fmt, ...)
260 {
261  TRACE_CALL(__func__);
262 
263  va_list args;
264  gchar *text;
265  va_start(args, fmt);
266  text = g_strdup_vprintf(fmt, args);
267  va_end(args);
268 
269  g_autofree gchar *buf = g_strconcat("(", fun, ") - ", text, NULL);
270  g_free(text);
271 
272  // always appends newline
273  g_debug ("%s", buf);
274 
275  if (!log_window) {
276  return;
277  }
278 
279  g_autofree gchar *buf_tmp = g_strconcat(buf, "\n", NULL);
280  /* freed in remmina_log_print_real */
281  gchar *bufn = g_strconcat("(DEBUG) - ", buf_tmp, NULL);
282 
283  IDLE_ADD(remmina_log_print_real, bufn);
284 }
285 
286 void _remmina_warning(const gchar *fun, const gchar *fmt, ...)
287 {
288  TRACE_CALL(__func__);
289 
290  va_list args;
291  gchar *text;
292  va_start(args, fmt);
293  text = g_strdup_vprintf(fmt, args);
294  va_end(args);
295 
296  g_autofree gchar *buf = g_strconcat("(", fun, ") - ", text, NULL);
297  g_free(text);
298 
299  // always appends newline
300  g_warning ("%s", buf);
301 
302  if (!log_window) {
303  return;
304  }
305 
306  g_autofree gchar *buf_tmp = g_strconcat(buf, "\n", NULL);
307  /* freed in remmina_log_print_real */
308  gchar *bufn = g_strconcat("(WARN) - ", buf_tmp, NULL);
309 
310  IDLE_ADD(remmina_log_print_real, bufn);
311 }
312 
313 void _remmina_audit(const gchar *fun, const gchar *fmt, ...)
314 {
315  TRACE_CALL(__func__);
316  va_list args;
317  va_start(args, fmt);
318  gchar *text = g_strdup_vprintf(fmt, args);
319  va_end(args);
320 
321 #if GLIB_CHECK_VERSION(2,62,0)
322  GDateTime* tv = g_date_time_new_now_local();
323  gchar *isodate = g_date_time_format_iso8601(tv);
324  g_date_time_unref(tv);
325 #else
326  GTimeVal tv;
327  g_get_current_time(&tv);
328  gchar *isodate = g_time_val_to_iso8601(&tv);
329 #endif
330 
331  g_autofree gchar *buf = g_strdup("");
332 
333  if (isodate) {
334 
335  buf = g_strconcat(
336  "[", isodate, "] - ",
337  g_get_host_name (),
338  " - ",
339  g_get_user_name (),
340  " - ",
341  text,
342  NULL);
343 
344  }
345 
346  g_free(text);
347  if (remmina_pref_get_boolean("audit"))
348  _remmina_message(buf);
349  else
350  _remmina_debug(fun, buf);
351 }
352 
353 // !!! Calling this function will crash Remmina !!!
354 // !!! purposefully and send a trap signal !!!
355 void _remmina_error(const gchar *fun, const gchar *fmt, ...)
356 {
357  TRACE_CALL(__func__);
358 
359  va_list args;
360  gchar *text;
361  va_start(args, fmt);
362  text = g_strdup_vprintf(fmt, args);
363  va_end(args);
364 
365  g_autofree gchar *buf = g_strconcat("(", fun, ") - ", text, NULL);
366  g_free(text);
367 
368  // always appends newline
369  g_error ("%s", buf);
370 
371  if (!log_window) {
372  return;
373  }
374 
375  g_autofree gchar *buf_tmp = g_strconcat(buf, "\n", NULL);
376  /* freed in remmina_log_print_real */
377  gchar *bufn = g_strconcat("(ERROR) - ", buf_tmp, NULL);
378 
379  IDLE_ADD(remmina_log_print_real, bufn);
380 }
381 
382 void _remmina_critical(const gchar *fun, const gchar *fmt, ...)
383 {
384  TRACE_CALL(__func__);
385 
386  va_list args;
387  gchar *text;
388  va_start(args, fmt);
389  text = g_strdup_vprintf(fmt, args);
390  va_end(args);
391 
392  g_autofree gchar *buf = g_strconcat("(", fun, ") - ", text, NULL);
393  g_free(text);
394 
395  // always appends newline
396  g_critical ("%s", buf);
397 
398  if (!log_window) {
399  return;
400  }
401 
402  g_autofree gchar *buf_tmp = g_strconcat(buf, "\n", NULL);
403  /* freed in remmina_log_print_real */
404  gchar *bufn = g_strconcat("(CRIT) - ", buf_tmp, NULL);
405 
406  IDLE_ADD(remmina_log_print_real, bufn);
407 }
408 
409 // Only prints into Remmina's own debug window. (Not stdout!)
410 // See _remmina_{message, info, debug warning, error, critical}
411 void remmina_log_printf(const gchar *fmt, ...)
412 {
413  TRACE_CALL(__func__);
414  va_list args;
415  gchar *text;
416 
417  if (!log_window) return;
418 
419  va_start(args, fmt);
420  text = g_strdup_vprintf(fmt, args);
421  va_end(args);
422 
423  IDLE_ADD(remmina_log_print_real, text);
424 }
425 
426 static gboolean remmina_log_on_keypress(GtkWidget *widget, GdkEvent *event, gpointer user_data)
427 {
428  TRACE_CALL(__func__);
429 
430  if (!log_window)
431  return FALSE;
432 
433  GdkEventKey *e = (GdkEventKey *)event;
434 
435  if ((e->state & GDK_CONTROL_MASK) == GDK_CONTROL_MASK) {
436  if (e->keyval == GDK_KEY_t) {
437  remmina_log_stats();
438  }
439  return TRUE;
440  }
441 
442  return FALSE;
443 }
444 
446 {
447  TRACE_CALL(__func__);
448  GtkWidget *scrolledwindow;
449  GtkWidget *widget;
450 
451  gtk_container_set_border_width(GTK_CONTAINER(logwin), 4);
452 
453  scrolledwindow = gtk_scrolled_window_new(NULL, NULL);
454  gtk_widget_show(scrolledwindow);
455  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolledwindow), GTK_POLICY_NEVER, GTK_POLICY_ALWAYS);
456  gtk_container_add(GTK_CONTAINER(logwin), scrolledwindow);
457 
458  widget = gtk_text_view_new();
459  gtk_widget_show(widget);
460  gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(widget), GTK_WRAP_WORD_CHAR);
461  gtk_text_view_set_editable(GTK_TEXT_VIEW(widget), FALSE);
462  gtk_text_view_set_monospace(GTK_TEXT_VIEW(widget), TRUE);
463  gtk_container_add(GTK_CONTAINER(scrolledwindow), widget);
464  logwin->log_view = widget;
465  logwin->log_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(widget));
466 
467  g_signal_connect(G_OBJECT(logwin->log_view), "key-press-event", G_CALLBACK(remmina_log_on_keypress), (gpointer)logwin);
468 }
469 
void remmina_log_start(void)
Definition: remmina_log.c:116
gboolean logstart
Definition: remmina_log.c:45
GtkWindow window
Definition: remmina_log.c:56
void _remmina_critical(const gchar *fun, const gchar *fmt,...)
Definition: remmina_log.c:382
static void remmina_log_start_stop(GtkSwitch *logswitch, gpointer user_data)
Definition: remmina_log.c:110
void _remmina_warning(const gchar *fun, const gchar *fmt,...)
Definition: remmina_log.c:286
void _remmina_audit(const gchar *fun, const gchar *fmt,...)
Definition: remmina_log.c:313
static gboolean remmina_log_print_real(gpointer data)
Definition: remmina_log.c:181
struct _RemminaLogWindowClass RemminaLogWindowClass
static gboolean remmina_log_scroll_to_end(gpointer data)
Definition: remmina_log.c:168
void _remmina_debug(const gchar *fun, const gchar *fmt,...)
Print a string in the Remmina Debug Windows and in the terminal.
Definition: remmina_log.c:259
GtkTextBuffer * log_buffer
Definition: remmina_log.c:59
gboolean remmina_log_running(void)
Definition: remmina_log.c:162
GtkWidget * log_view
Definition: remmina_log.c:58
G_DEFINE_TYPE(RemminaConnectionWindow, rcw, GTK_TYPE_WINDOW)
Definition: rcw.c:81
gboolean remmina_pref_get_boolean(const gchar *key)
static void remmina_log_window_init(RemminaLogWindow *logwin)
Definition: remmina_log.c:445
void _remmina_info(const gchar *fmt,...)
Definition: remmina_log.c:206
static gboolean remmina_log_on_keypress(GtkWidget *widget, GdkEvent *event, gpointer user_data)
Definition: remmina_log.c:426
GType remmina_log_window_get_type(void)
Definition: remmina_log.c:66
static GtkWidget * remmina_log_window_new(void)
Definition: remmina_log.c:98
JsonNode * remmina_stats_get_all()
Get all statistics in JSON format to send periodically to the PHP server.
struct _RemminaLogWindow RemminaLogWindow
static GtkWidget * log_window
Definition: remmina_log.c:95
void remmina_log_printf(const gchar *fmt,...)
Definition: remmina_log.c:411
static void remmina_log_window_class_init(RemminaLogWindowClass *klass)
Definition: remmina_log.c:89
void remmina_log_print(const gchar *text)
Definition: remmina_log.c:197
void _remmina_message(const gchar *fmt,...)
Definition: remmina_log.c:230
static void remmina_log_end(GtkWidget *widget, gpointer data)
Definition: remmina_log.c:104
void _remmina_error(const gchar *fun, const gchar *fmt,...)
Definition: remmina_log.c:355
GtkWindowClass parent_class
Definition: remmina_log.c:63