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_public.c
Go to the documentation of this file.
1 /*
2  * Remmina - The GTK+ Remote Desktop Client
3  * Copyright (C) 2009-2011 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 "config.h"
38 #include <gtk/gtk.h>
39 #include <glib/gi18n.h>
40 #include <stdlib.h>
41 #include <string.h>
42 #include <unistd.h>
43 #ifdef HAVE_NETDB_H
44 #include <netdb.h>
45 #endif
46 #ifdef HAVE_SYS_SOCKET_H
47 #include <sys/socket.h>
48 #endif
49 #ifdef HAVE_SYS_UN_H
50 #include <sys/un.h>
51 #endif
52 #ifdef GDK_WINDOWING_X11
53 #include <gdk/gdkx.h>
54 #include <X11/Xlib.h>
55 #include <X11/Xutil.h>
56 #include <X11/Xatom.h>
57 #endif
58 #include "remmina_public.h"
60 
61 GtkWidget*
62 remmina_public_create_combo_entry(const gchar *text, const gchar *def, gboolean descending)
63 {
64  TRACE_CALL(__func__);
65  GtkWidget *combo;
66  gboolean found;
67  gchar *buf, *ptr1, *ptr2;
68  gint i;
69 
70  combo = gtk_combo_box_text_new_with_entry();
71  found = FALSE;
72 
73  if (text && text[0] != '\0') {
74  buf = g_strdup(text);
75  ptr1 = buf;
76  i = 0;
77  while (ptr1 && *ptr1 != '\0') {
78  ptr2 = strchr(ptr1, CHAR_DELIMITOR);
79  if (ptr2)
80  *ptr2++ = '\0';
81 
82  if (descending) {
83  gtk_combo_box_text_prepend_text(GTK_COMBO_BOX_TEXT(combo), ptr1);
84  if (!found && g_strcmp0(ptr1, def) == 0) {
85  gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
86  found = TRUE;
87  }
88  }else {
89  gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), ptr1);
90  if (!found && g_strcmp0(ptr1, def) == 0) {
91  gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i);
92  found = TRUE;
93  }
94  }
95 
96  ptr1 = ptr2;
97  i++;
98  }
99 
100  g_free(buf);
101  }
102 
103  if (!found && def && def[0] != '\0') {
104  gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo))), def);
105  }
106 
107  return combo;
108 }
109 
110 GtkWidget*
111 remmina_public_create_combo_text_d(const gchar *text, const gchar *def, const gchar *empty_choice)
112 {
113  TRACE_CALL(__func__);
114  GtkWidget *combo;
115  GtkListStore *store;
116  GtkCellRenderer *text_renderer;
117 
118  store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
119  combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
120 
121  text_renderer = gtk_cell_renderer_text_new();
122  gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(combo), text_renderer, TRUE);
123  gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo), text_renderer, "text", 1);
124 
125  remmina_public_load_combo_text_d(combo, text, def, empty_choice);
126 
127  return combo;
128 }
129 
130 void remmina_public_load_combo_text_d(GtkWidget *combo, const gchar *text, const gchar *def, const gchar *empty_choice)
131 {
132  TRACE_CALL(__func__);
133  GtkListStore *store;
134  GtkTreeIter iter;
135  gint i;
136  gchar *buf, *ptr1, *ptr2;
137 
138  store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combo)));
139  gtk_list_store_clear(store);
140 
141  i = 0;
142 
143  if (empty_choice) {
144  gtk_list_store_append(store, &iter);
145  gtk_list_store_set(store, &iter, 0, "", 1, empty_choice, -1);
146  gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i);
147  i++;
148  }
149 
150  if (text == NULL || text[0] == '\0')
151  return;
152 
153  buf = g_strdup(text);
154  ptr1 = buf;
155  while (ptr1 && *ptr1 != '\0') {
156  ptr2 = strchr(ptr1, CHAR_DELIMITOR);
157  if (ptr2)
158  *ptr2++ = '\0';
159 
160  gtk_list_store_append(store, &iter);
161  gtk_list_store_set(store, &iter, 0, ptr1, 1, ptr1, -1);
162 
163  if (i == 0 || g_strcmp0(ptr1, def) == 0) {
164  gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i);
165  }
166 
167  i++;
168  ptr1 = ptr2;
169  }
170 
171  g_free(buf);
172 }
173 
174 GtkWidget*
175 remmina_public_create_combo(gboolean use_icon)
176 {
177  TRACE_CALL(__func__);
178  GtkWidget *combo;
179  GtkListStore *store;
180  GtkCellRenderer *renderer;
181 
182  if (use_icon) {
183  store = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
184  }else {
185  store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
186  }
187  combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
188  gtk_widget_set_hexpand(combo, TRUE);
189 
190  if (use_icon) {
191  renderer = gtk_cell_renderer_pixbuf_new();
192  gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, FALSE);
193  gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo), renderer, "icon-name", 2);
194  }
195  renderer = gtk_cell_renderer_text_new();
196  gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE);
197  gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo), renderer, "text", 1);
198  if (use_icon)
199  g_object_set(G_OBJECT(renderer), "xpad", 5, NULL);
200 
201  return combo;
202 }
203 
204 GtkWidget*
205 remmina_public_create_combo_map(const gpointer *key_value_list, const gchar *def, gboolean use_icon, const gchar *domain)
206 {
207  TRACE_CALL(__func__);
208  gint i;
209  GtkWidget *combo;
210  GtkListStore *store;
211  GtkTreeIter iter;
212 
213  combo = remmina_public_create_combo(use_icon);
214  store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combo)));
215 
216  for (i = 0; key_value_list[i]; i += (use_icon ? 3 : 2)) {
217  gtk_list_store_append(store, &iter);
218  gtk_list_store_set(
219  store,
220  &iter,
221  0,
222  key_value_list[i],
223  1,
224  key_value_list[i + 1] && ((char*)key_value_list[i + 1])[0] ?
225  g_dgettext(domain, key_value_list[i + 1]) : "", -1);
226  if (use_icon) {
227  gtk_list_store_set(store, &iter, 2, key_value_list[i + 2], -1);
228  }
229  if (i == 0 || g_strcmp0(key_value_list[i], def) == 0) {
230  gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i / (use_icon ? 3 : 2));
231  }
232  }
233  return combo;
234 }
235 
236 GtkWidget*
237 remmina_public_create_combo_mapint(const gpointer *key_value_list, gint def, gboolean use_icon, const gchar *domain)
238 {
239  TRACE_CALL(__func__);
240  gchar buf[20];
241  g_snprintf(buf, sizeof(buf), "%i", def);
242  return remmina_public_create_combo_map(key_value_list, buf, use_icon, domain);
243 }
244 
245 void remmina_public_create_group(GtkGrid *grid, const gchar *group, gint row, gint rows, gint cols)
246 {
247  TRACE_CALL(__func__);
248  GtkWidget *widget;
249  gchar *str;
250 
251  widget = gtk_label_new(NULL);
252  gtk_widget_show(widget);
253  gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START);
254  gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER);
255  str = g_markup_printf_escaped("<b>%s</b>", group);
256  gtk_label_set_markup(GTK_LABEL(widget), str);
257  g_free(str);
258  gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 2);
259 
260  widget = gtk_label_new(NULL);
261  gtk_widget_show(widget);
262  gtk_grid_attach(GTK_GRID(grid), widget, 0, row + 1, 1, 1);
263 }
264 
265 gchar*
267 {
268  TRACE_CALL(__func__);
269  GtkTreeModel *model;
270  GtkTreeIter iter;
271  gchar *s;
272 
273  if (GTK_IS_COMBO_BOX_TEXT(combo)) {
274  return gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo));
275  }
276 
277  if (!gtk_combo_box_get_active_iter(combo, &iter))
278  return NULL;
279 
280  model = gtk_combo_box_get_model(combo);
281  gtk_tree_model_get(model, &iter, 0, &s, -1);
282 
283  return s;
284 }
285 
286 #if !GTK_CHECK_VERSION(3, 22, 0)
287 void remmina_public_popup_position(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data)
288 {
289  TRACE_CALL(__func__);
290  GtkWidget *widget;
291  gint tx, ty;
292  GtkAllocation allocation;
293 
294  widget = GTK_WIDGET(user_data);
295  if (gtk_widget_get_window(widget) == NULL) {
296  *x = 0;
297  *y = 0;
298  *push_in = TRUE;
299  return;
300  }
301  gdk_window_get_origin(gtk_widget_get_window(widget), &tx, &ty);
302  gtk_widget_get_allocation(widget, &allocation);
303  /* I’m unsure why the author made the check about a GdkWindow inside the
304  * widget argument. This function generally is called passing by a ToolButton
305  * which hasn’t any GdkWindow, therefore the positioning is wrong
306  * I think the gtk_widget_get_has_window() check should be removed
307  *
308  * While leaving the previous check intact I’m checking also if the provided
309  * widget is a GtkToggleToolButton and position the menu accordingly. */
310  if (gtk_widget_get_has_window(widget) ||
311  g_strcmp0(gtk_widget_get_name(widget), "GtkToggleToolButton") == 0) {
312  tx += allocation.x;
313  ty += allocation.y;
314  }
315 
316  *x = tx;
317  *y = ty + allocation.height - 1;
318  *push_in = TRUE;
319 }
320 #endif
321 
322 gchar*
323 remmina_public_combine_path(const gchar *path1, const gchar *path2)
324 {
325  TRACE_CALL(__func__);
326  if (!path1 || path1[0] == '\0')
327  return g_strdup(path2);
328  if (path1[strlen(path1) - 1] == '/')
329  return g_strdup_printf("%s%s", path1, path2);
330  return g_strdup_printf("%s/%s", path1, path2);
331 }
332 
333 void remmina_public_get_server_port(const gchar *server, gint defaultport, gchar **host, gint *port)
334 {
335  TRACE_CALL(__func__);
336  gchar *str, *ptr, *ptr2;
337 
338  str = g_strdup(server);
339 
340  if (str) {
341  /* [server]:port format */
342  ptr = strchr(str, '[');
343  if (ptr) {
344  ptr++;
345  ptr2 = strchr(ptr, ']');
346  if (ptr2) {
347  *ptr2++ = '\0';
348  if (*ptr2 == ':')
349  defaultport = atoi(ptr2 + 1);
350  }
351  if (host)
352  *host = g_strdup(ptr);
353  if (port)
354  *port = defaultport;
355  g_free(str);
356  return;
357  }
358 
359  /* server:port format, IPv6 cannot use this format */
360  ptr = strchr(str, ':');
361  if (ptr) {
362  ptr2 = strchr(ptr + 1, ':');
363  if (ptr2 == NULL) {
364  *ptr++ = '\0';
365  defaultport = atoi(ptr);
366  }
367  /* More than one ':' means this is IPv6 address. Treat it as a whole address */
368  }
369  }
370 
371  if (host)
372  *host = str;
373  else
374  g_free(str);
375  if (port)
376  *port = defaultport;
377 }
378 
379 gboolean remmina_public_get_xauth_cookie(const gchar *display, gchar **msg)
380 {
381  TRACE_CALL(__func__);
382  gchar buf[200];
383  gchar *out = NULL;
384  gchar *ptr;
385  GError *error = NULL;
386  gboolean ret;
387 
388  if (!display)
389  display = gdk_display_get_name(gdk_display_get_default());
390 
391  g_snprintf(buf, sizeof(buf), "xauth list %s", display);
392  ret = g_spawn_command_line_sync(buf, &out, NULL, NULL, &error);
393  if (ret) {
394  if ((ptr = g_strrstr(out, "MIT-MAGIC-COOKIE-1")) == NULL) {
395  *msg = g_strdup_printf("xauth returns %s", out);
396  ret = FALSE;
397  }else {
398  ptr += 19;
399  while (*ptr == ' ')
400  ptr++;
401  *msg = g_strndup(ptr, 32);
402  }
403  g_free(out);
404  }else {
405  *msg = g_strdup(error->message);
406  }
407  return ret;
408 }
409 
410 gint remmina_public_open_xdisplay(const gchar *disp)
411 {
412  TRACE_CALL(__func__);
413  gchar *display;
414  gchar *ptr;
415  gint port;
416  struct sockaddr_un addr;
417  gint sock = -1;
418 
419  display = g_strdup(disp);
420  ptr = g_strrstr(display, ":");
421  if (ptr) {
422  *ptr++ = '\0';
423  /* Assume you are using a local display… might need to implement remote display in the future */
424  if (display[0] == '\0' || strcmp(display, "unix") == 0) {
425  port = atoi(ptr);
426  sock = socket(AF_UNIX, SOCK_STREAM, 0);
427  if (sock >= 0) {
428  memset(&addr, 0, sizeof(addr));
429  addr.sun_family = AF_UNIX;
430  snprintf(addr.sun_path, sizeof(addr.sun_path), X_UNIX_SOCKET, port);
431  if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
432  close(sock);
433  sock = -1;
434  }
435  }
436  }
437  }
438 
439  g_free(display);
440  return sock;
441 }
442 
443 /* This function was copied from GEdit (gedit-utils.c). */
444 guint remmina_public_get_current_workspace(GdkScreen *screen)
445 {
446  TRACE_CALL(__func__);
447 #ifdef GDK_WINDOWING_X11
448 #if GTK_CHECK_VERSION(3, 10, 0)
449  g_return_val_if_fail(GDK_IS_SCREEN(screen), 0);
450  if (GDK_IS_X11_DISPLAY(gdk_screen_get_display(screen)))
451  return gdk_x11_screen_get_current_desktop(screen);
452  else
453  return 0;
454 
455 #else
456  GdkWindow *root_win;
457  GdkDisplay *display;
458  Atom type;
459  gint format;
460  gulong nitems;
461  gulong bytes_after;
462  guint *current_desktop;
463  gint err, result;
464  guint ret = 0;
465 
466  g_return_val_if_fail(GDK_IS_SCREEN(screen), 0);
467 
468  root_win = gdk_screen_get_root_window(screen);
469  display = gdk_screen_get_display(screen);
470 
471  gdk_error_trap_push();
472  result = XGetWindowProperty(GDK_DISPLAY_XDISPLAY(display), GDK_WINDOW_XID(root_win),
473  gdk_x11_get_xatom_by_name_for_display(display, "_NET_CURRENT_DESKTOP"),
474  0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
475  &bytes_after, (gpointer) & current_desktop);
476  err = gdk_error_trap_pop();
477 
478  if (err != Success || result != Success)
479  return ret;
480 
481  if (type == XA_CARDINAL && format == 32 && nitems > 0)
482  ret = current_desktop[0];
483 
484  XFree(current_desktop);
485  return ret;
486 #endif
487 #else
488  /* FIXME: on mac etc proably there are native APIs
489  * to get the current workspace etc */
490  return 0;
491 #endif
492 }
493 
494 /* This function was copied from GEdit (gedit-utils.c). */
495 guint remmina_public_get_window_workspace(GtkWindow *gtkwindow)
496 {
497  TRACE_CALL(__func__);
498 #ifdef GDK_WINDOWING_X11
499 #if GTK_CHECK_VERSION(3, 10, 0)
500  GdkWindow *window;
501  g_return_val_if_fail(GTK_IS_WINDOW(gtkwindow), 0);
502  g_return_val_if_fail(gtk_widget_get_realized(GTK_WIDGET(gtkwindow)), 0);
503  window = gtk_widget_get_window(GTK_WIDGET(gtkwindow));
504  if (GDK_IS_X11_DISPLAY(gdk_window_get_display(window)))
505  return gdk_x11_window_get_desktop(window);
506  else
507  return 0;
508 #else
509  GdkWindow *window;
510  GdkDisplay *display;
511  Atom type;
512  gint format;
513  gulong nitems;
514  gulong bytes_after;
515  guint *workspace;
516  gint err, result;
517  guint ret = 0;
518 
519  g_return_val_if_fail(GTK_IS_WINDOW(gtkwindow), 0);
520  g_return_val_if_fail(gtk_widget_get_realized(GTK_WIDGET(gtkwindow)), 0);
521 
522  window = gtk_widget_get_window(GTK_WIDGET(gtkwindow));
523  display = gdk_window_get_display(window);
524 
525  gdk_error_trap_push();
526  result = XGetWindowProperty(GDK_DISPLAY_XDISPLAY(display), GDK_WINDOW_XID(window),
527  gdk_x11_get_xatom_by_name_for_display(display, "_NET_WM_DESKTOP"),
528  0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
529  &bytes_after, (gpointer) & workspace);
530  err = gdk_error_trap_pop();
531 
532  if (err != Success || result != Success)
533  return ret;
534 
535  if (type == XA_CARDINAL && format == 32 && nitems > 0)
536  ret = workspace[0];
537 
538  XFree(workspace);
539  return ret;
540 #endif
541 #else
542  /* FIXME: on mac etc proably there are native APIs
543  * to get the current workspace etc */
544  return 0;
545 #endif
546 }
547 
548 /* Find hardware keycode for the requested keyval */
549 guint16 remmina_public_get_keycode_for_keyval(GdkKeymap *keymap, guint keyval)
550 {
551  TRACE_CALL(__func__);
552  GdkKeymapKey *keys = NULL;
553  gint length = 0;
554  guint16 keycode = 0;
555 
556  if (gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &length)) {
557  keycode = keys[0].keycode;
558  g_free(keys);
559  }
560  return keycode;
561 }
562 
563 /* Check if the requested keycode is a key modifier */
564 gboolean remmina_public_get_modifier_for_keycode(GdkKeymap *keymap, guint16 keycode)
565 {
566  TRACE_CALL(__func__);
567  g_return_val_if_fail(keycode > 0, FALSE);
568 #ifdef GDK_WINDOWING_X11
569  return gdk_x11_keymap_key_is_modifier(keymap, keycode);
570 #else
571  return FALSE;
572 #endif
573 }
574 
575 /* Load a GtkBuilder object from a filename */
576 GtkBuilder* remmina_public_gtk_builder_new_from_file(gchar *filename)
577 {
578  TRACE_CALL(__func__);
579  GError *err = NULL;
580  gchar *ui_path = g_strconcat(REMMINA_RUNTIME_UIDIR, G_DIR_SEPARATOR_S, filename, NULL);
581  GtkBuilder *builder = gtk_builder_new();
582  gtk_builder_add_from_file(builder, ui_path, &err);
583  if (err != NULL) {
584  g_print("Error adding build from file. Error: %s", err->message);
585  g_error_free(err);
586  }
587  g_free(ui_path);
588  return builder;
589 }
590 
591 /* Change parent container for a widget
592  * If possible use this function instead of the deprecated gtk_widget_reparent */
593 void remmina_public_gtk_widget_reparent(GtkWidget *widget, GtkContainer *container)
594 {
595  TRACE_CALL(__func__);
596  g_object_ref(widget);
597  gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(widget)), widget);
598  gtk_container_add(container, widget);
599  g_object_unref(widget);
600 }
601 
602 /* Validate the inserted value for a new resolution */
603 gboolean remmina_public_resolution_validation_func(const gchar *new_str, gchar **error)
604 {
605  TRACE_CALL(__func__);
606  gint i;
607  gint width, height;
608  gboolean splitted;
609  gboolean result;
610 
611  width = 0;
612  height = 0;
613  splitted = FALSE;
614  result = TRUE;
615  for (i = 0; new_str[i] != '\0'; i++) {
616  if (new_str[i] == 'x') {
617  if (splitted) {
618  result = FALSE;
619  break;
620  }
621  splitted = TRUE;
622  continue;
623  }
624  if (new_str[i] < '0' || new_str[i] > '9') {
625  result = FALSE;
626  break;
627  }
628  if (splitted) {
629  height = 1;
630  }else {
631  width = 1;
632  }
633  }
634 
635  if (width == 0 || height == 0)
636  result = FALSE;
637 
638  if (!result)
639  *error = g_strdup(_("Please enter format 'widthxheight'."));
640  return result;
641 }
642 
643 /* Used to send desktop notifications */
644 void remmina_public_send_notification(const gchar *notification_id,
645  const gchar *notification_title, const gchar *notification_message)
646 {
647  TRACE_CALL(__func__);
648 
649  GNotification *notification = g_notification_new(notification_title);
650  g_notification_set_body(notification, notification_message);
651 #if GLIB_CHECK_VERSION(2, 42, 0)
652  g_notification_set_priority(notification, G_NOTIFICATION_PRIORITY_NORMAL);
653 #endif
654  g_application_send_notification(g_application_get_default(), notification_id, notification);
655  g_object_unref(notification);
656 }
657 
658 /* Replaces all occurrences of search in a new copy of string by replacement. */
659 gchar* remmina_public_str_replace(const gchar *string, const gchar *search, const gchar *replacement)
660 {
661  TRACE_CALL(__func__);
662  gchar *str, **arr;
663 
664  g_return_val_if_fail(string != NULL, NULL);
665  g_return_val_if_fail(search != NULL, NULL);
666 
667  if (replacement == NULL)
668  replacement = "";
669 
670  arr = g_strsplit(string, search, -1);
671  if (arr != NULL && arr[0] != NULL)
672  str = g_strjoinv(replacement, arr);
673  else
674  str = g_strdup(string);
675 
676  g_strfreev(arr);
677  return str;
678 }
679 
680 /* Replaces all occurrences of search in a new copy of string by replacement
681  * and overwrites the original string */
682 gchar* remmina_public_str_replace_in_place(gchar *string, const gchar *search, const gchar *replacement)
683 {
684  TRACE_CALL(__func__);
685  gchar *new_string = remmina_public_str_replace(string, search, replacement);
686  g_free(string);
687  string = g_strdup(new_string);
688  return string;
689 }
690 
691 int remmina_public_split_resolution_string(const char *resolution_string, int *w, int *h)
692 {
693  int lw, lh;
694 
695  if (resolution_string == NULL || resolution_string[0] == 0)
696  return 0;
697  if (sscanf(resolution_string, "%dx%d", &lw, &lh) != 2)
698  return 0;
699  *w = lw;
700  *h = lh;
701  return 1;
702 }
703 
704 /* Return TRUE if current gtk version library in use is greater or equal than
705  * the required major.minor.micro */
706 gboolean remmina_gtk_check_version(guint major, guint minor, guint micro)
707 {
708  guint rtmajor, rtminor, rtmicro;
709  rtmajor = gtk_get_major_version();
710  if (rtmajor > major) {
711  return TRUE;
712  }else if (rtmajor == major) {
713  rtminor = gtk_get_minor_version();
714  if (rtminor > minor) {
715  return TRUE;
716  }else if (rtminor == minor) {
717  rtmicro = gtk_get_micro_version();
718  if (rtmicro >= micro) {
719  return TRUE;
720  }else {
721  return FALSE;
722  }
723  }else {
724  return FALSE;
725  }
726  }else {
727  return FALSE;
728  }
729 }
void remmina_public_create_group(GtkGrid *grid, const gchar *group, gint row, gint rows, gint cols)
void remmina_public_gtk_widget_reparent(GtkWidget *widget, GtkContainer *container)
GtkWidget * remmina_public_create_combo_entry(const gchar *text, const gchar *def, gboolean descending)
void remmina_public_send_notification(const gchar *notification_id, const gchar *notification_title, const gchar *notification_message)
guint remmina_public_get_window_workspace(GtkWindow *gtkwindow)
gchar * remmina_public_combo_get_active_text(GtkComboBox *combo)
gboolean remmina_public_resolution_validation_func(const gchar *new_str, gchar **error)
GtkBuilder * remmina_public_gtk_builder_new_from_file(gchar *filename)
int remmina_public_split_resolution_string(const char *resolution_string, int *w, int *h)
gchar * remmina_public_combine_path(const gchar *path1, const gchar *path2)
gchar * remmina_public_str_replace_in_place(gchar *string, const gchar *search, const gchar *replacement)
GtkWidget * remmina_public_create_combo_map(const gpointer *key_value_list, const gchar *def, gboolean use_icon, const gchar *domain)
guint remmina_public_get_current_workspace(GdkScreen *screen)
GtkWidget * remmina_public_create_combo_text_d(const gchar *text, const gchar *def, const gchar *empty_choice)
GtkWidget * remmina_public_create_combo(gboolean use_icon)
void remmina_public_popup_position(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data)
gchar * remmina_public_str_replace(const gchar *string, const gchar *search, const gchar *replacement)
gboolean remmina_gtk_check_version(guint major, guint minor, guint micro)
gint remmina_public_open_xdisplay(const gchar *disp)
guint16 remmina_public_get_keycode_for_keyval(GdkKeymap *keymap, guint keyval)
gboolean remmina_public_get_xauth_cookie(const gchar *display, gchar **msg)
gboolean remmina_public_get_modifier_for_keycode(GdkKeymap *keymap, guint16 keycode)
void remmina_public_get_server_port(const gchar *server, gint defaultport, gchar **host, gint *port)
void remmina_public_load_combo_text_d(GtkWidget *combo, const gchar *text, const gchar *def, const gchar *empty_choice)
GtkWidget * remmina_public_create_combo_mapint(const gpointer *key_value_list, gint def, gboolean use_icon, const gchar *domain)