gnome-control-center/panels/system/remote-desktop/cc-desktop-sharing-page.c
Ray Strode aa99ca5b5b system, remote-desktop: Set port row insensitive when appropriate
Right now we set a subtitle of "" when the port is invalid. This
leads to the "Port" title getting center aligned vertically.

We also keep the port sensitive even when it's empty and there is
little value in copying it.

This commit changes the subtitle to " " so it serves as a
placeholder to keep the "Port" title top aligned. This commit
also makes the port insensitive in that case so the user never
copies the placeholder space.
2024-03-03 09:26:40 -05:00

650 lines
21 KiB
C

/*
* Copyright 2024 Red Hat Inc
*
* 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 3 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, see <http://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "cc-desktop-sharing-page"
#include "cc-desktop-sharing-page.h"
#include "cc-encryption-fingerprint-dialog.h"
#include "cc-gnome-remote-desktop.h"
#include "cc-hostname.h"
#include "cc-password-utils.h"
#include "cc-list-row.h"
#include "cc-tls-certificate.h"
#include "cc-systemd-service.h"
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <errno.h>
#include <glib/gi18n.h>
#include <gio/gio.h>
#include <gtk/gtk.h>
#include <locale.h>
#ifdef GDK_WINDOWING_WAYLAND
#include <gdk/wayland/gdkwayland.h>
#endif
#include <pwd.h>
#include <pwquality.h>
#include <unistd.h>
#include "org.gnome.RemoteDesktop.h"
#define GNOME_REMOTE_DESKTOP_SCHEMA_ID "org.gnome.desktop.remote-desktop"
#define GNOME_REMOTE_DESKTOP_RDP_SCHEMA_ID "org.gnome.desktop.remote-desktop.rdp"
#define REMOTE_DESKTOP_STORE_CREDENTIALS_TIMEOUT_S 1
#define REMOTE_DESKTOP_SERVICE "gnome-remote-desktop.service"
#define RDP_SERVER_DBUS_SERVICE "org.gnome.RemoteDesktop.User"
#define RDP_SERVER_OBJECT_PATH "/org/gnome/RemoteDesktop/Rdp/Server"
struct _CcDesktopSharingPage {
AdwBin parent_instance;
GtkWidget *toast_overlay;
GsdRemoteDesktopRdpServer *rdp_server;
AdwSwitchRow *desktop_sharing_row;
AdwSwitchRow *remote_control_row;
AdwActionRow *hostname_row;
AdwActionRow *port_row;
GtkWidget *username_entry;
GtkWidget *password_entry;
GtkWidget *generate_password_button;
GtkWidget *verify_encryption_button;
CcEncryptionFingerprintDialog *fingerprint_dialog;
guint desktop_sharing_name_watch;
guint store_credentials_id;
GTlsCertificate *certificate;
GSettings *rdp_settings;
GCancellable *cancellable;
};
G_DEFINE_TYPE (CcDesktopSharingPage, cc_desktop_sharing_page, ADW_TYPE_BIN)
static void
on_generate_password_button_clicked (CcDesktopSharingPage *self)
{
g_autofree char *new_password = cc_generate_password ();
gtk_editable_set_text (GTK_EDITABLE (self->password_entry), new_password);
}
static void
on_verify_encryption_button_clicked (CcDesktopSharingPage *self)
{
GtkNative *native;
g_return_if_fail (self->certificate);
if (!self->fingerprint_dialog)
self->fingerprint_dialog = g_object_new (CC_TYPE_ENCRYPTION_FINGERPRINT_DIALOG, NULL);
native = gtk_widget_get_native (GTK_WIDGET (self));
gtk_window_set_transient_for (GTK_WINDOW (self->fingerprint_dialog), GTK_WINDOW (native));
cc_encryption_fingerprint_dialog_set_certificate (self->fingerprint_dialog, self->certificate);
gtk_window_present (GTK_WINDOW (self->fingerprint_dialog));
}
static char *
get_hostname (void)
{
return cc_hostname_get_display_hostname (cc_hostname_get_default ());
}
static gboolean
check_schema_available (CcDesktopSharingPage *self,
const gchar *schema_id)
{
GSettingsSchemaSource *source;
g_autoptr(GSettingsSchema) schema = NULL;
source = g_settings_schema_source_get_default ();
if (!source)
return FALSE;
schema = g_settings_schema_source_lookup (source, schema_id, TRUE);
if (!schema)
return FALSE;
return TRUE;
}
static gboolean
store_credentials_timeout (gpointer user_data)
{
CcDesktopSharingPage *self = (CcDesktopSharingPage*) user_data;
const char *username, *password;
username = gtk_editable_get_text (GTK_EDITABLE (self->username_entry));
password = gtk_editable_get_text (GTK_EDITABLE (self->password_entry));
if (username && password)
{
cc_grd_store_rdp_credentials (username, password, self->cancellable);
}
self->store_credentials_id = 0;
return G_SOURCE_REMOVE;
}
static gboolean
is_desktop_sharing_enabled (CcDesktopSharingPage *self)
{
if (!g_settings_get_boolean (self->rdp_settings, "enable"))
return FALSE;
return cc_is_service_active (REMOTE_DESKTOP_SERVICE, G_BUS_TYPE_SESSION);
}
static void
disable_gnome_desktop_sharing_service (CcDesktopSharingPage *self)
{
g_autoptr(GError) error = NULL;
g_settings_set_boolean (self->rdp_settings, "enable", FALSE);
if (!cc_disable_service (REMOTE_DESKTOP_SERVICE,
G_BUS_TYPE_SESSION,
&error))
g_warning ("Failed to enable remote desktop service: %s", error->message);
}
static void
enable_gnome_desktop_sharing_service (CcDesktopSharingPage *self)
{
g_autoptr(GError) error = NULL;
if (is_desktop_sharing_enabled (self))
return;
if (!cc_enable_service (REMOTE_DESKTOP_SERVICE, G_BUS_TYPE_SESSION, &error))
{
g_warning ("Failed to enable remote desktop service: %s", error->message);
disable_gnome_desktop_sharing_service (self);
}
}
static void
on_credentials_changed (CcDesktopSharingPage *self)
{
g_clear_handle_id (&self->store_credentials_id,
g_source_remove);
self->store_credentials_id =
g_timeout_add_seconds (REMOTE_DESKTOP_STORE_CREDENTIALS_TIMEOUT_S,
store_credentials_timeout,
self);
}
static void
calc_default_tls_paths (char **out_dir_path,
char **out_cert_path,
char **out_key_path)
{
g_autofree char *dir_path = NULL;
dir_path = g_build_filename (g_get_user_data_dir(), "gnome-remote-desktop", "certificates", NULL);
if (out_cert_path)
*out_cert_path = g_build_filename(dir_path, "rdp-tls.crt", NULL);
if (out_key_path)
*out_key_path = g_build_filename(dir_path, "rdp-tls.key", NULL);
if (out_dir_path)
*out_dir_path = g_steal_pointer (&dir_path);
}
static void
set_tls_certificate (CcDesktopSharingPage *self,
GTlsCertificate *tls_certificate)
{
g_set_object (&self->certificate, tls_certificate);
gtk_widget_set_sensitive (self->verify_encryption_button, TRUE);
}
static void
on_certificate_generated (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
{
CcDesktopSharingPage *self;
g_autoptr(GTlsCertificate) tls_certificate = NULL;
g_autoptr(GError) error = NULL;
g_autofree char *cert_path = NULL;
g_autofree char *key_path = NULL;
tls_certificate = bonsai_tls_certificate_new_generate_finish (res, &error);
if (!tls_certificate)
{
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_warning ("Failed to generate TLS certificate: %s", error->message);
return;
}
self = (CcDesktopSharingPage *)user_data;
calc_default_tls_paths (NULL, &cert_path, &key_path);
g_settings_set_string (self->rdp_settings, "tls-cert", cert_path);
g_settings_set_string (self->rdp_settings, "tls-key", key_path);
set_tls_certificate (self, tls_certificate);
enable_gnome_desktop_sharing_service (self);
}
static void
enable_gnome_desktop_sharing (CcDesktopSharingPage *self)
{
g_autofree char *dir_path = NULL;
g_autofree char *cert_path = NULL;
g_autofree char *key_path = NULL;
g_autoptr(GFile) dir = NULL;
g_autoptr(GFile) cert_file = NULL;
g_autoptr(GFile) key_file = NULL;
g_autoptr(GError) error = NULL;
g_settings_set_boolean (self->rdp_settings, "enable", TRUE);
cert_path = g_settings_get_string (self->rdp_settings, "tls-cert");
key_path = g_settings_get_string (self->rdp_settings, "tls-key");
if (strlen (cert_path) > 0 &&
strlen (key_path) > 0)
{
g_autoptr(GTlsCertificate) tls_certificate = NULL;
tls_certificate = g_tls_certificate_new_from_file (cert_path, &error);
if (tls_certificate)
{
set_tls_certificate (self, tls_certificate);
enable_gnome_desktop_sharing_service (self);
return;
}
g_warning ("Configured TLS certificate invalid: %s", error->message);
return;
}
calc_default_tls_paths (&dir_path, &cert_path, &key_path);
dir = g_file_new_for_path (dir_path);
if (!g_file_query_exists (dir, NULL))
{
if (!g_file_make_directory_with_parents (dir, NULL, &error))
{
g_warning ("Failed to create remote desktop certificate directory: %s",
error->message);
return;
}
}
cert_file = g_file_new_for_path (cert_path);
key_file = g_file_new_for_path (key_path);
if (g_file_query_exists (cert_file, NULL) &&
g_file_query_exists (key_file, NULL))
{
g_autoptr(GTlsCertificate) tls_certificate = NULL;
tls_certificate = g_tls_certificate_new_from_file (cert_path, &error);
if (tls_certificate)
{
g_settings_set_string (self->rdp_settings, "tls-cert", cert_path);
g_settings_set_string (self->rdp_settings, "tls-key", key_path);
set_tls_certificate (self, tls_certificate);
enable_gnome_desktop_sharing_service (self);
return;
}
g_warning ("Existing TLS certificate invalid: %s", error->message);
return;
}
bonsai_tls_certificate_new_generate_async (cert_path,
key_path,
"US",
"GNOME",
self->cancellable,
on_certificate_generated,
self);
}
static void
on_desktop_sharing_active_changed (CcDesktopSharingPage *self)
{
if (adw_switch_row_get_active (self->desktop_sharing_row))
enable_gnome_desktop_sharing (self);
else
disable_gnome_desktop_sharing_service (self);
}
static void
add_toast (CcDesktopSharingPage *self,
const char *message)
{
adw_toast_overlay_add_toast (ADW_TOAST_OVERLAY (self->toast_overlay),
adw_toast_new (message));
}
static void
on_address_copy_clicked (CcDesktopSharingPage *self,
GtkButton *button)
{
gdk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (button)),
adw_action_row_get_subtitle (self->hostname_row));
add_toast (self, _("Device address copied"));
}
static void
on_port_copy_clicked (CcDesktopSharingPage *self,
GtkButton *button)
{
gdk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (button)),
adw_action_row_get_subtitle (self->port_row));
add_toast (self, _("Port number copied"));
}
static void
on_username_copy_clicked (CcDesktopSharingPage *self,
GtkButton *button)
{
GtkEditable *editable = GTK_EDITABLE (self->username_entry);
gdk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (button)),
gtk_editable_get_text (editable));
add_toast (self, _("Username copied"));
}
static void
on_password_copy_clicked (CcDesktopSharingPage *self,
GtkButton *button)
{
GtkEditable *editable = GTK_EDITABLE (self->password_entry);
gdk_clipboard_set_text (gtk_widget_get_clipboard (GTK_WIDGET (button)),
gtk_editable_get_text (editable));
add_toast (self, _("Password copied"));
}
static void
setup_desktop_sharing_page (CcDesktopSharingPage *self)
{
const gchar *username = NULL;
const gchar *password = NULL;
g_autofree char *hostname = NULL;
self->rdp_settings = g_settings_new (GNOME_REMOTE_DESKTOP_RDP_SCHEMA_ID);
hostname = get_hostname ();
adw_action_row_set_subtitle (self->hostname_row, hostname);
username = cc_grd_lookup_rdp_username (self->cancellable);
password = cc_grd_lookup_rdp_password (self->cancellable);
if (username != NULL)
gtk_editable_set_text (GTK_EDITABLE (self->username_entry), username);
if (password != NULL)
gtk_editable_set_text (GTK_EDITABLE (self->password_entry), password);
g_signal_connect_swapped (self->username_entry,
"notify::text",
G_CALLBACK (on_credentials_changed),
self);
g_signal_connect_swapped (self->password_entry,
"notify::text",
G_CALLBACK (on_credentials_changed),
self);
if (username == NULL)
{
struct passwd *pw = getpwuid (getuid ());
if (pw != NULL)
username = g_strdup (pw->pw_name);
else
g_warning ("Failed to get username: %s", g_strerror (errno));
}
gtk_editable_set_text (GTK_EDITABLE (self->username_entry), username);
if (password == NULL)
{
g_autofree gchar *pw = cc_generate_password ();
if (pw != NULL)
gtk_editable_set_text (GTK_EDITABLE (self->password_entry), pw);
}
g_signal_connect_object (self->desktop_sharing_row, "notify::active",
G_CALLBACK (on_desktop_sharing_active_changed), self,
G_CONNECT_SWAPPED);
adw_switch_row_set_active (self->desktop_sharing_row, is_desktop_sharing_enabled (self));
g_settings_bind (self->rdp_settings,
"enable",
self->desktop_sharing_row,
"active",
G_SETTINGS_BIND_DEFAULT);
g_settings_bind (self->rdp_settings,
"view-only",
self->remote_control_row,
"active",
G_SETTINGS_BIND_DEFAULT | G_SETTINGS_BIND_INVERT_BOOLEAN);
g_object_bind_property (self->desktop_sharing_row, "active",
self->remote_control_row, "sensitive",
G_BINDING_SYNC_CREATE);
g_object_bind_property (self->password_entry, "sensitive",
self->generate_password_button, "sensitive",
G_BINDING_SYNC_CREATE);
}
static void
desktop_sharing_name_appeared (GDBusConnection *connection,
const gchar *name,
const gchar *name_owner,
gpointer user_data)
{
CcDesktopSharingPage *self = (CcDesktopSharingPage *)user_data;
g_clear_handle_id (&self->desktop_sharing_name_watch, g_bus_unwatch_name);
gtk_widget_set_visible (GTK_WIDGET (self), TRUE);
setup_desktop_sharing_page (self);
}
static void
check_desktop_sharing_available (CcDesktopSharingPage *self)
{
if (!check_schema_available (self, GNOME_REMOTE_DESKTOP_SCHEMA_ID) ||
!check_schema_available (self, GNOME_REMOTE_DESKTOP_RDP_SCHEMA_ID))
{
gtk_widget_set_visible (GTK_WIDGET (self), FALSE);
return;
}
self->desktop_sharing_name_watch = g_bus_watch_name (G_BUS_TYPE_SESSION,
"org.gnome.Mutter.RemoteDesktop",
G_BUS_NAME_WATCHER_FLAGS_NONE,
desktop_sharing_name_appeared,
NULL,
self,
NULL);
}
static void
cc_desktop_sharing_page_dispose (GObject *object)
{
CcDesktopSharingPage *self = (CcDesktopSharingPage *)object;
g_cancellable_cancel (self->cancellable);
g_clear_object (&self->cancellable);
g_clear_pointer ((GtkWindow **) &self->fingerprint_dialog, gtk_window_destroy);
g_clear_handle_id (&self->store_credentials_id, g_source_remove);
g_clear_object (&self->rdp_server);
g_clear_object (&self->rdp_settings);
G_OBJECT_CLASS (cc_desktop_sharing_page_parent_class)->dispose (object);
}
static void
cc_desktop_sharing_page_class_init (CcDesktopSharingPageClass * klass)
{
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = cc_desktop_sharing_page_dispose;
gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/control-center/system/remote-desktop/cc-desktop-sharing-page.ui");
gtk_widget_class_bind_template_child (widget_class, CcDesktopSharingPage, toast_overlay);
gtk_widget_class_bind_template_child (widget_class, CcDesktopSharingPage, desktop_sharing_row);
gtk_widget_class_bind_template_child (widget_class, CcDesktopSharingPage, remote_control_row);
gtk_widget_class_bind_template_child (widget_class, CcDesktopSharingPage, hostname_row);
gtk_widget_class_bind_template_child (widget_class, CcDesktopSharingPage, port_row);
gtk_widget_class_bind_template_child (widget_class, CcDesktopSharingPage, username_entry);
gtk_widget_class_bind_template_child (widget_class, CcDesktopSharingPage, password_entry);
gtk_widget_class_bind_template_child (widget_class, CcDesktopSharingPage, generate_password_button);
gtk_widget_class_bind_template_child (widget_class, CcDesktopSharingPage, verify_encryption_button);
gtk_widget_class_bind_template_callback (widget_class, on_address_copy_clicked);
gtk_widget_class_bind_template_callback (widget_class, on_port_copy_clicked);
gtk_widget_class_bind_template_callback (widget_class, on_username_copy_clicked);
gtk_widget_class_bind_template_callback (widget_class, on_password_copy_clicked);
gtk_widget_class_bind_template_callback (widget_class, on_generate_password_button_clicked);
gtk_widget_class_bind_template_callback (widget_class, on_verify_encryption_button_clicked);
}
static gboolean
format_port_for_row (GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer user_data)
{
int port = g_value_get_int (from_value);
if (port <= 0)
g_value_set_string (to_value, " ");
else
g_value_take_string (to_value, g_strdup_printf ("%u", port));
return TRUE;
}
static gboolean
sensitize_row_from_port (GBinding *binding,
const GValue *from_value,
GValue *to_value,
gpointer user_data)
{
int port = g_value_get_int (from_value);
g_value_set_boolean (to_value, port > 0);
return TRUE;
}
static void
on_connected_to_remote_desktop_rdp_server (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
CcDesktopSharingPage *self = user_data;
g_autoptr (GError) error = NULL;
g_clear_object (&self->rdp_server);
self->rdp_server = gsd_remote_desktop_rdp_server_proxy_new_finish (result, &error);
if (error)
{
if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_warning ("Failed to create remote desktop proxy: %s", error->message);
return;
}
g_object_bind_property_full (self->rdp_server, "port",
self->port_row, "subtitle",
G_BINDING_SYNC_CREATE,
format_port_for_row,
NULL,
NULL,
NULL);
g_object_bind_property_full (self->rdp_server, "port",
self->port_row, "sensitive",
G_BINDING_SYNC_CREATE,
sensitize_row_from_port,
NULL,
NULL,
NULL);
}
static void
connect_to_remote_desktop_rdp_server (CcDesktopSharingPage *self)
{
g_autoptr (GError) error = NULL;
g_autoptr (GDBusConnection) connection = NULL;
connection = g_bus_get_sync (G_BUS_TYPE_SESSION, self->cancellable, &error);
if (error)
g_warning ("Could not connect to system message bus: %s", error->message);
if (!connection)
return;
gsd_remote_desktop_rdp_server_proxy_new (connection,
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START_AT_CONSTRUCTION,
RDP_SERVER_DBUS_SERVICE,
RDP_SERVER_OBJECT_PATH,
self->cancellable,
(GAsyncReadyCallback)
on_connected_to_remote_desktop_rdp_server,
self);
}
static void
cc_desktop_sharing_page_init (CcDesktopSharingPage *self)
{
g_autoptr(GtkCssProvider) provider = NULL;
gtk_widget_init_template (GTK_WIDGET (self));
self->cancellable = g_cancellable_new ();
check_desktop_sharing_available (self);
connect_to_remote_desktop_rdp_server (self);
provider = gtk_css_provider_new ();
gtk_css_provider_load_from_resource (provider,
"/org/gnome/control-center/system/remote-desktop/remote-desktop.css");
gtk_style_context_add_provider_for_display (gdk_display_get_default (),
GTK_STYLE_PROVIDER (provider),
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
}