/* * Remmina - The GTK+ Remote Desktop Client * Copyright (C) 2009-2011 Vic Lee * Copyright (C) 2014-2015 Antenore Gatta, Fabio Castelli, Giovanni Panozzo * Copyright (C) 2016-2023 Antenore Gatta, Giovanni Panozzo * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * * In addition, as a special exception, the copyright holders give * permission to link the code of portions of this program with the * OpenSSL library under certain conditions as described in each * individual source file, and distribute linked combinations * including the two. * You must obey the GNU General Public License in all respects * for all of the code used other than OpenSSL. * If you modify * file(s) with this exception, you may extend this exception to your * version of the file(s), but you are not obligated to do so. * If you * do not wish to do so, delete this exception statement from your * version. * If you delete this exception statement from all source * files in the program, then also delete it here. * */ #include "config.h" #include #include #include #include #include #include #ifdef HAVE_NETDB_H #include #endif #ifdef HAVE_SYS_SOCKET_H #include #endif #ifdef HAVE_SYS_UN_H #include #endif #ifdef GDK_WINDOWING_X11 #include #include #include #include #elif defined(GDK_WINDOWING_WAYLAND) #include #endif #include "remmina_public.h" #include "remmina/remmina_trace_calls.h" GtkWidget* remmina_public_create_combo_entry(const gchar *text, const gchar *def, gboolean descending) { TRACE_CALL(__func__); GtkWidget *combo; gboolean found; gchar *buf, *ptr1, *ptr2; gint i; //g_debug("text: %s\n", text); //g_debug("def: %s\n", def); combo = gtk_combo_box_text_new_with_entry(); found = FALSE; if (text && text[0] != '\0') { buf = g_strdup(text); ptr1 = buf; i = 0; while (ptr1 && *ptr1 != '\0') { ptr2 = strchr(ptr1, CHAR_DELIMITOR); if (ptr2) *ptr2++ = '\0'; if (descending) { gtk_combo_box_text_prepend_text(GTK_COMBO_BOX_TEXT(combo), ptr1); if (!found && g_strcmp0(ptr1, def) == 0) { gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0); found = TRUE; } }else { gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), ptr1); if (!found && g_strcmp0(ptr1, def) == 0) { gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i); found = TRUE; } } ptr1 = ptr2; i++; } g_free(buf); } if (!found && def && def[0] != '\0') { gtk_entry_set_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(combo))), def); } return combo; } GtkWidget* remmina_public_create_combo_text_d(const gchar *text, const gchar *def, const gchar *empty_choice) { TRACE_CALL(__func__); GtkWidget *combo; GtkListStore *store; GtkCellRenderer *text_renderer; store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING); combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store)); text_renderer = gtk_cell_renderer_text_new(); gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(combo), text_renderer, TRUE); gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo), text_renderer, "text", 1); remmina_public_load_combo_text_d(combo, text, def, empty_choice); return combo; } void remmina_public_load_combo_text_d(GtkWidget *combo, const gchar *text, const gchar *def, const gchar *empty_choice) { TRACE_CALL(__func__); GtkListStore *store; GtkTreeIter iter; gint i; gchar *buf, *ptr1, *ptr2; store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combo))); gtk_list_store_clear(store); i = 0; if (empty_choice) { gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, 0, "", 1, empty_choice, -1); gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i); i++; } if (text == NULL || text[0] == '\0') return; buf = g_strdup(text); ptr1 = buf; while (ptr1 && *ptr1 != '\0') { ptr2 = strchr(ptr1, CHAR_DELIMITOR); if (ptr2) *ptr2++ = '\0'; gtk_list_store_append(store, &iter); gtk_list_store_set(store, &iter, 0, ptr1, 1, ptr1, -1); if (i == 0 || g_strcmp0(ptr1, def) == 0) { gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i); } i++; ptr1 = ptr2; } g_free(buf); } GtkWidget* remmina_public_create_combo(gboolean use_icon) { TRACE_CALL(__func__); GtkWidget *combo; GtkListStore *store; GtkCellRenderer *renderer; if (use_icon) { store = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); }else { store = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING); } combo = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store)); gtk_widget_set_hexpand(combo, TRUE); if (use_icon) { renderer = gtk_cell_renderer_pixbuf_new(); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, FALSE); gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo), renderer, "icon-name", 2); } renderer = gtk_cell_renderer_text_new(); gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(combo), renderer, TRUE); gtk_cell_layout_add_attribute(GTK_CELL_LAYOUT(combo), renderer, "text", 1); if (use_icon) g_object_set(G_OBJECT(renderer), "xpad", 5, NULL); return combo; } GtkWidget* remmina_public_create_combo_map(const gpointer *key_value_list, const gchar *def, gboolean use_icon, const gchar *domain) { TRACE_CALL(__func__); gint i; GtkWidget *combo; GtkListStore *store; GtkTreeIter iter; combo = remmina_public_create_combo(use_icon); store = GTK_LIST_STORE(gtk_combo_box_get_model(GTK_COMBO_BOX(combo))); for (i = 0; key_value_list[i]; i += (use_icon ? 3 : 2)) { gtk_list_store_append(store, &iter); gtk_list_store_set( store, &iter, 0, key_value_list[i], 1, key_value_list[i + 1] && ((char*)key_value_list[i + 1])[0] ? g_dgettext(domain, key_value_list[i + 1]) : "", -1); if (use_icon) { gtk_list_store_set(store, &iter, 2, key_value_list[i + 2], -1); } if (i == 0 || g_strcmp0(key_value_list[i], def) == 0) { gtk_combo_box_set_active(GTK_COMBO_BOX(combo), i / (use_icon ? 3 : 2)); } } return combo; } GtkWidget* remmina_public_create_combo_mapint(const gpointer *key_value_list, gint def, gboolean use_icon, const gchar *domain) { TRACE_CALL(__func__); gchar buf[20]; g_snprintf(buf, sizeof(buf), "%i", def); return remmina_public_create_combo_map(key_value_list, buf, use_icon, domain); } void remmina_public_create_group(GtkGrid *grid, const gchar *group, gint row, gint rows, gint cols) { TRACE_CALL(__func__); GtkWidget *widget; gchar *str; widget = gtk_label_new(NULL); gtk_widget_show(widget); gtk_widget_set_halign(GTK_WIDGET(widget), GTK_ALIGN_START); gtk_widget_set_valign(GTK_WIDGET(widget), GTK_ALIGN_CENTER); str = g_markup_printf_escaped("%s", group); gtk_label_set_markup(GTK_LABEL(widget), str); g_free(str); gtk_grid_attach(GTK_GRID(grid), widget, 0, row, 1, 2); widget = gtk_label_new(NULL); gtk_widget_show(widget); gtk_grid_attach(GTK_GRID(grid), widget, 0, row + 1, 1, 1); } gchar* remmina_public_combo_get_active_text(GtkComboBox *combo) { TRACE_CALL(__func__); GtkTreeModel *model; GtkTreeIter iter; gchar *s; if (GTK_IS_COMBO_BOX_TEXT(combo)) { return gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo)); } if (!gtk_combo_box_get_active_iter(combo, &iter)) return NULL; model = gtk_combo_box_get_model(combo); gtk_tree_model_get(model, &iter, 0, &s, -1); return s; } #if !GTK_CHECK_VERSION(3, 22, 0) void remmina_public_popup_position(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer user_data) { TRACE_CALL(__func__); GtkWidget *widget; gint tx, ty; GtkAllocation allocation; widget = GTK_WIDGET(user_data); if (gtk_widget_get_window(widget) == NULL) { *x = 0; *y = 0; *push_in = TRUE; return; } gdk_window_get_origin(gtk_widget_get_window(widget), &tx, &ty); gtk_widget_get_allocation(widget, &allocation); /* I’m unsure why the author made the check about a GdkWindow inside the * widget argument. This function generally is called passing by a ToolButton * which hasn’t any GdkWindow, therefore the positioning is wrong * I think the gtk_widget_get_has_window() check should be removed * * While leaving the previous check intact I’m checking also if the provided * widget is a GtkToggleToolButton and position the menu accordingly. */ if (gtk_widget_get_has_window(widget) || g_strcmp0(gtk_widget_get_name(widget), "GtkToggleToolButton") == 0) { tx += allocation.x; ty += allocation.y; } *x = tx; *y = ty + allocation.height - 1; *push_in = TRUE; } #endif gchar* remmina_public_combine_path(const gchar *path1, const gchar *path2) { TRACE_CALL(__func__); if (!path1 || path1[0] == '\0') return g_strdup(path2); if (path1[strlen(path1) - 1] == '/') return g_strdup_printf("%s%s", path1, path2); return g_strdup_printf("%s/%s", path1, path2); } //static int remmina_public_open_unix_sock(const char *unixsock, GError **error) gint remmina_public_open_unix_sock(const char *unixsock) { struct sockaddr_un addr; int fd; if (strlen(unixsock) + 1 > sizeof(addr.sun_path)) { //g_set_error(error, REMMINA_ERROR, REMMINA_ERROR_FAILED, g_debug(_("Address is too long for UNIX socket_path: %s"), unixsock); return -1; } memset(&addr, 0, sizeof addr); addr.sun_family = AF_UNIX; strcpy(addr.sun_path, unixsock); if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { //g_set_error(error, REMMINA_ERROR, REMMINA_ERROR_FAILED, g_debug(_("Creating UNIX socket failed: %s"), g_strerror(errno)); return -1; } if (connect(fd, (struct sockaddr *)&addr, sizeof addr) < 0) { //g_set_error(error, REMMINA_ERROR, REMMINA_ERROR_FAILED, g_debug(_("Connecting to UNIX socket failed: %s"), g_strerror(errno)); close(fd); return -1; } return fd; } void remmina_public_get_server_port_old(const gchar *server, gint defaultport, gchar **host, gint *port) { TRACE_CALL(__func__); gchar *str, *ptr, *ptr2; str = g_strdup(server); if (str) { /* [server]:port format */ ptr = strchr(str, '['); if (ptr) { ptr++; ptr2 = strchr(ptr, ']'); if (ptr2) { *ptr2++ = '\0'; if (*ptr2 == ':') defaultport = atoi(ptr2 + 1); } if (host) *host = g_strdup(ptr); if (port) *port = defaultport; g_free(str); g_debug ("(%s) - host: %s", __func__, *host); g_debug ("(%s) - port: %d", __func__, *port); return; } /* server:port format, IPv6 cannot use this format */ ptr = strchr(str, ':'); if (ptr) { ptr2 = strchr(ptr + 1, ':'); if (ptr2 == NULL) { *ptr++ = '\0'; defaultport = atoi(ptr); } /* More than one ':' means this is IPv6 address. Treat it as a whole address */ } } if (host) *host = str; else g_free(str); if (port) *port = defaultport; g_debug ("(%s) - host: %s", __func__, *host); g_debug ("(%s) - port: %d", __func__, *port); } void remmina_public_get_server_port(const gchar *server, gint defaultport, gchar **host, gint *port) { TRACE_CALL(__func__); const gchar *nul_terminated_server = NULL; if (server != NULL) { if(strstr(g_strdup(server), "ID:") != NULL) { g_debug ("(%s) - Using remmina_public_get_server_port_old to parse the repeater ID", __func__); remmina_public_get_server_port_old (server, defaultport, host, port); return; } GNetworkAddress *address; GError *err = NULL; nul_terminated_server = g_strdup (server); g_debug ("(%s) - Parsing server: %s, default port: %d", __func__, server, defaultport); address = (GNetworkAddress*)g_network_address_parse ((const gchar *)nul_terminated_server, defaultport, &err); if (address == NULL) { g_debug ("(%s) - Error converting server string: %s, with error: %s", __func__, nul_terminated_server, err->message); *host = NULL; if (err) g_error_free(err); } else { *host = g_strdup(g_network_address_get_hostname (address)); *port = g_network_address_get_port (address); } } else *host = NULL; if (port == 0) *port = defaultport; g_debug ("(%s) - host: %s", __func__, *host); g_debug ("(%s) - port: %d", __func__, *port); return; } gboolean remmina_public_get_xauth_cookie(const gchar *display, gchar **msg) { TRACE_CALL(__func__); gchar buf[200]; gchar *out = NULL; gchar *ptr; GError *error = NULL; gboolean ret; if (!display) display = gdk_display_get_name(gdk_display_get_default()); g_snprintf(buf, sizeof(buf), "xauth list %s", display); ret = g_spawn_command_line_sync(buf, &out, NULL, NULL, &error); if (ret) { if ((ptr = g_strrstr(out, "MIT-MAGIC-COOKIE-1")) == NULL) { *msg = g_strdup_printf("xauth returns %s", out); ret = FALSE; }else { ptr += 19; while (*ptr == ' ') ptr++; *msg = g_strndup(ptr, 32); } g_free(out); }else { *msg = g_strdup(error->message); } return ret; } gint remmina_public_open_xdisplay(const gchar *disp) { TRACE_CALL(__func__); gchar *display; gchar *ptr; gint port; struct sockaddr_un addr; gint sock = -1; display = g_strdup(disp); ptr = g_strrstr(display, ":"); if (ptr) { *ptr++ = '\0'; /* Assume you are using a local display… might need to implement remote display in the future */ if (display[0] == '\0' || strcmp(display, "unix") == 0) { port = atoi(ptr); sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock >= 0) { memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; snprintf(addr.sun_path, sizeof(addr.sun_path), X_UNIX_SOCKET, port); if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) { close(sock); sock = -1; } } } } g_free(display); return sock; } /* Find hardware keycode for the requested keyval */ guint16 remmina_public_get_keycode_for_keyval(GdkKeymap *keymap, guint keyval) { TRACE_CALL(__func__); GdkKeymapKey *keys = NULL; gint length = 0; guint16 keycode = 0; if (gdk_keymap_get_entries_for_keyval(keymap, keyval, &keys, &length)) { keycode = keys[0].keycode; g_free(keys); } return keycode; } /* Check if the requested keycode is a key modifier */ gboolean remmina_public_get_modifier_for_keycode(GdkKeymap *keymap, guint16 keycode) { TRACE_CALL(__func__); //g_return_val_if_fail(keycode > 0, FALSE); if (keycode > 0) return FALSE; #ifdef GDK_WINDOWING_X11 return gdk_x11_keymap_key_is_modifier(keymap, keycode); #else return FALSE; #endif } /* Load a GtkBuilder object from a filename */ GtkBuilder* remmina_public_gtk_builder_new_from_file(gchar *filename) { TRACE_CALL(__func__); GError *err = NULL; gchar *ui_path = g_strconcat(REMMINA_RUNTIME_UIDIR, G_DIR_SEPARATOR_S, filename, NULL); GtkBuilder *builder = gtk_builder_new(); gtk_builder_add_from_file(builder, ui_path, &err); if (err != NULL) { g_print("Error adding build from file. Error: %s", err->message); g_error_free(err); } g_free(ui_path); return builder; } /* Load a GtkBuilder object from a resource */ GtkBuilder* remmina_public_gtk_builder_new_from_resource(gchar *resource) { TRACE_CALL(__func__); GError *err = NULL; GtkBuilder *builder = gtk_builder_new(); gtk_builder_add_from_resource (builder, resource, &err); //GtkBuilder *builder = gtk_builder_new_from_resource (resource); if (err != NULL) { g_print("Error adding build from resource. Error: %s", err->message); g_error_free(err); } return builder; } /* Change parent container for a widget * If possible use this function instead of the deprecated gtk_widget_reparent */ void remmina_public_gtk_widget_reparent(GtkWidget *widget, GtkContainer *container) { TRACE_CALL(__func__); g_object_ref(widget); gtk_container_remove(GTK_CONTAINER(gtk_widget_get_parent(widget)), widget); gtk_container_add(container, widget); g_object_unref(widget); } /* Validate the inserted value for a new resolution */ gboolean remmina_public_resolution_validation_func(const gchar *new_str, gchar **error) { TRACE_CALL(__func__); gint i; gint width, height; gboolean splitted; gboolean result; width = 0; height = 0; splitted = FALSE; result = TRUE; for (i = 0; new_str[i] != '\0'; i++) { if (new_str[i] == 'x') { if (splitted) { result = FALSE; break; } splitted = TRUE; continue; } if (new_str[i] < '0' || new_str[i] > '9') { result = FALSE; break; } if (splitted) { height = 1; }else { width = 1; } } if (width == 0 || height == 0) result = FALSE; if (!result) *error = g_strdup(_("Please enter format 'widthxheight'.")); return result; } /* Used to send desktop notifications */ void remmina_public_send_notification(const gchar *notification_id, const gchar *notification_title, const gchar *notification_message) { TRACE_CALL(__func__); g_autoptr(GNotification) n = NULL; gint priority = G_NOTIFICATION_PRIORITY_NORMAL; n = g_notification_new(notification_title); g_notification_set_body(n, notification_message); if (g_strcmp0 (notification_id, "remmina-security-trust-all-id") == 0) { g_debug ("remmina_public_send_notification: We got a remmina-security-trust-all-id notification"); priority = G_NOTIFICATION_PRIORITY_HIGH; /** parameter 5 is the tab index for the security tab in the preferences * TODO: Do not hardcode the parameter * TODO: Do not hardcode implement DBus interface correctly of this won't work*/ g_notification_set_default_action_and_target (n, "app.preferences", "i", 5); g_notification_add_button_with_target (n, _("Change security settings"), "app.preferences", "i", 5); } #if GLIB_CHECK_VERSION(2, 42, 0) g_notification_set_priority(n, priority); #endif g_application_send_notification(g_application_get_default(), notification_id, n); } /* Replaces all occurrences of search in a new copy of string by replacement. */ gchar* remmina_public_str_replace(const gchar *string, const gchar *search, const gchar *replacement) { TRACE_CALL(__func__); gchar *str, **arr; g_return_val_if_fail(string != NULL, NULL); g_return_val_if_fail(search != NULL, NULL); if (replacement == NULL) replacement = ""; arr = g_strsplit(string, search, -1); if (arr != NULL && arr[0] != NULL) str = g_strjoinv(replacement, arr); else str = g_strdup(string); g_strfreev(arr); return str; } /* Replaces all occurrences of search in a new copy of string by replacement * and overwrites the original string */ gchar* remmina_public_str_replace_in_place(gchar *string, const gchar *search, const gchar *replacement) { TRACE_CALL(__func__); gchar *new_string = remmina_public_str_replace(string, search, replacement); string = g_strdup(new_string); g_free(new_string); return string; } int remmina_public_split_resolution_string(const char *resolution_string, int *w, int *h) { int lw, lh; if (resolution_string == NULL || resolution_string[0] == 0) return 0; if (sscanf(resolution_string, "%dx%d", &lw, &lh) != 2) return 0; *w = lw; *h = lh; return 1; } /* Return TRUE if current gtk version library in use is greater or equal than * the required major.minor.micro */ gboolean remmina_gtk_check_version(guint major, guint minor, guint micro) { guint rtmajor, rtminor, rtmicro; rtmajor = gtk_get_major_version(); if (rtmajor > major) { return TRUE; }else if (rtmajor == major) { rtminor = gtk_get_minor_version(); if (rtminor > minor) { return TRUE; }else if (rtminor == minor) { rtmicro = gtk_get_micro_version(); if (rtmicro >= micro) { return TRUE; }else { return FALSE; } }else { return FALSE; } }else { return FALSE; } }