commit 65e586f1e74656d51fa38cabcfd13c867c69a2bb Author: CentOS Sources Date: Thu Aug 1 10:43:11 2019 -0400 import gnome-remote-desktop-0.1.6-5.el8 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b82e207 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +SOURCES/gnome-remote-desktop-0.1.6.tar.xz diff --git a/.gnome-remote-desktop.metadata b/.gnome-remote-desktop.metadata new file mode 100644 index 0000000..33914e5 --- /dev/null +++ b/.gnome-remote-desktop.metadata @@ -0,0 +1 @@ +25504e6190dbfae00c7a648d1a4dd37c4ecc92b2 SOURCES/gnome-remote-desktop-0.1.6.tar.xz diff --git a/SOURCES/0001-meson.build-Bump-pipewire-requirement-to-0.2.2.patch b/SOURCES/0001-meson.build-Bump-pipewire-requirement-to-0.2.2.patch new file mode 100644 index 0000000..62b84b2 --- /dev/null +++ b/SOURCES/0001-meson.build-Bump-pipewire-requirement-to-0.2.2.patch @@ -0,0 +1,25 @@ +From 8f760d73df6011330cd09da7ca7b8a3f40c9a3ef Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Tue, 7 Aug 2018 13:35:43 +0200 +Subject: [PATCH] meson.build: Bump pipewire requirement to 0.2.2 + +--- + meson.build | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/meson.build b/meson.build +index 6951b89..34ec5ea 100644 +--- a/meson.build ++++ b/meson.build +@@ -10,7 +10,7 @@ gnome = import('gnome') + glib_dep = dependency('glib-2.0') + gio_dep = dependency('gio-2.0') + gio_unix_dep = dependency('gio-unix-2.0') +-pipewire_dep = dependency('libpipewire-0.1') ++pipewire_dep = dependency('libpipewire-0.2', version: '>= 0.2.2') + systemd_dep = dependency('systemd') + libvncserver_dep = dependency('libvncserver') + libsecret_dep = dependency('libsecret-1') +-- +2.17.1 + diff --git a/SOURCES/0001-session-vnc-Don-t-requeue-close-session-idle.patch b/SOURCES/0001-session-vnc-Don-t-requeue-close-session-idle.patch new file mode 100644 index 0000000..20a3f56 --- /dev/null +++ b/SOURCES/0001-session-vnc-Don-t-requeue-close-session-idle.patch @@ -0,0 +1,84 @@ +From add0ea34fd1d6835c99aebeb4e56b805b38e53ec Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Mon, 1 Oct 2018 18:02:39 +0200 +Subject: [PATCH 1/2] session/vnc: Don't requeue close session idle + +If being closed due to a PipeWire error, RFB will still process state +and invoke callbacks when cleaning up the RFB screen, meaning we'd +requeue the close session idle handler. Avoid this by avoiding +requeueing if there is already one queued, and don't mark is as unqueued +until after actually stopping the session. +--- + src/grd-session-vnc.c | 28 ++++++++++++++++++---------- + 1 file changed, 18 insertions(+), 10 deletions(-) + +diff --git a/src/grd-session-vnc.c b/src/grd-session-vnc.c +index ce4dd29..3c98eeb 100644 +--- a/src/grd-session-vnc.c ++++ b/src/grd-session-vnc.c +@@ -165,6 +165,16 @@ grd_session_vnc_draw_buffer (GrdSessionVnc *session_vnc, + rfbProcessEvents (session_vnc->rfb_screen, 0); + } + ++static void ++maybe_queue_close_session_idle (GrdSessionVnc *session_vnc) ++{ ++ if (session_vnc->close_session_idle_id) ++ return; ++ ++ session_vnc->close_session_idle_id = ++ g_idle_add (close_session_idle, session_vnc); ++} ++ + static void + handle_client_gone (rfbClientPtr rfb_client) + { +@@ -172,8 +182,7 @@ handle_client_gone (rfbClientPtr rfb_client) + + g_debug ("VNC client gone"); + +- session_vnc->close_session_idle_id = +- g_idle_add (close_session_idle, session_vnc); ++ maybe_queue_close_session_idle (session_vnc); + } + + static void +@@ -670,12 +679,6 @@ grd_session_vnc_stop (GrdSession *session) + + g_debug ("Stopping VNC session"); + +- if (session_vnc->close_session_idle_id) +- { +- g_source_remove (session_vnc->close_session_idle_id); +- session_vnc->close_session_idle_id = 0; +- } +- + g_clear_object (&session_vnc->pipewire_stream); + + grd_session_vnc_detach_source (session_vnc); +@@ -683,6 +686,12 @@ grd_session_vnc_stop (GrdSession *session) + g_clear_object (&session_vnc->connection); + g_clear_pointer (&session_vnc->rfb_screen->frameBuffer, g_free); + g_clear_pointer (&session_vnc->rfb_screen, (GDestroyNotify) rfbScreenCleanup); ++ ++ if (session_vnc->close_session_idle_id) ++ { ++ g_source_remove (session_vnc->close_session_idle_id); ++ session_vnc->close_session_idle_id = 0; ++ } + } + + static gboolean +@@ -703,8 +712,7 @@ on_pipwire_stream_closed (GrdVncPipeWireStream *stream, + { + g_warning ("PipeWire stream closed, closing client"); + +- session_vnc->close_session_idle_id = +- g_idle_add (close_session_idle, session_vnc); ++ maybe_queue_close_session_idle (session_vnc); + } + + static void +-- +2.17.1 + diff --git a/SOURCES/0001-vnc-Add-anonymous-TLS-encryption-support.patch b/SOURCES/0001-vnc-Add-anonymous-TLS-encryption-support.patch new file mode 100644 index 0000000..fe25694 --- /dev/null +++ b/SOURCES/0001-vnc-Add-anonymous-TLS-encryption-support.patch @@ -0,0 +1,953 @@ +From fcfef86768d3dc63a2e7da799beb011800dff2ad Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 14 Jun 2018 12:21:37 +0200 +Subject: [PATCH] vnc: Add anonymous TLS encryption support + +Add support for encrypting the VNC connection using anonymous TLS. In +effect this means that the channel is encrypted using TLS but that no +authentication of the peers are done. This means the connection is still +vulnerable to man-in-the-middle attacks where an attacker proxies the +VNC connection. +--- + meson.build | 1 + + src/grd-enums.h | 6 + + src/grd-session-vnc.c | 98 +++- + src/grd-session-vnc.h | 16 + + src/grd-settings.c | 27 ++ + src/grd-settings.h | 2 + + src/grd-vnc-server.c | 45 ++ + src/grd-vnc-tls.c | 444 ++++++++++++++++++ + src/grd-vnc-tls.h | 28 ++ + src/meson.build | 5 +- + ...g.gnome.desktop.remote-desktop.gschema.xml | 10 + + 11 files changed, 666 insertions(+), 16 deletions(-) + create mode 100644 src/grd-vnc-tls.c + create mode 100644 src/grd-vnc-tls.h + +diff --git a/meson.build b/meson.build +index d8e20d2..f8c8cee 100644 +--- a/meson.build ++++ b/meson.build +@@ -15,6 +15,7 @@ systemd_dep = dependency('systemd') + libvncserver_dep = dependency('libvncserver') + libsecret_dep = dependency('libsecret-1') + libnotify_dep = dependency('libnotify') ++gnutls_dep = dependency('gnutls') + + cdata = configuration_data() + cdata.set_quoted('GETTEXT_PACKAGE', 'gnome-remote-desktop') +diff --git a/src/grd-enums.h b/src/grd-enums.h +index ffab821..4333863 100644 +--- a/src/grd-enums.h ++++ b/src/grd-enums.h +@@ -27,4 +27,10 @@ typedef enum + GRD_VNC_AUTH_METHOD_PASSWORD + } GrdVncAuthMethod; + ++typedef enum ++{ ++ GRD_VNC_ENCRYPTION_NONE = 1 << 0, ++ GRD_VNC_ENCRYPTION_TLS_ANON = 1 << 1, ++} GrdVncEncryption; ++ + #endif /* GRD_ENUMS_H */ +diff --git a/src/grd-session-vnc.c b/src/grd-session-vnc.c +index 5d40971..ce4dd29 100644 +--- a/src/grd-session-vnc.c ++++ b/src/grd-session-vnc.c +@@ -44,7 +44,9 @@ struct _GrdSessionVnc + { + GrdSession parent; + ++ GrdVncServer *vnc_server; + GSocketConnection *connection; ++ GList *socket_grabs; + GSource *source; + rfbScreenInfoPtr rfb_screen; + rfbClientPtr rfb_client; +@@ -465,12 +467,30 @@ check_rfb_password (rfbClientPtr rfb_client, + } + } + ++int ++grd_session_vnc_get_fd (GrdSessionVnc *session_vnc) ++{ ++ return session_vnc->rfb_screen->inetdSock; ++} ++ + int + grd_session_vnc_get_framebuffer_stride (GrdSessionVnc *session_vnc) + { + return session_vnc->rfb_screen->paddedWidthInBytes; + } + ++rfbClientPtr ++grd_session_vnc_get_rfb_client (GrdSessionVnc *session_vnc) ++{ ++ return session_vnc->rfb_client; ++} ++ ++GrdVncServer * ++grd_session_vnc_get_vnc_server (GrdSessionVnc *session_vnc) ++{ ++ return session_vnc->vnc_server; ++} ++ + static void + init_vnc_session (GrdSessionVnc *session_vnc) + { +@@ -509,33 +529,74 @@ init_vnc_session (GrdSessionVnc *session_vnc) + rfbProcessEvents (rfb_screen, 0); + } + ++void ++grd_session_vnc_grab_socket (GrdSessionVnc *session_vnc, ++ GrdVncSocketGrabFunc grab_func) ++{ ++ session_vnc->socket_grabs = g_list_prepend (session_vnc->socket_grabs, ++ grab_func); ++} ++ ++void ++grd_session_vnc_ungrab_socket (GrdSessionVnc *session_vnc, ++ GrdVncSocketGrabFunc grab_func) ++{ ++ session_vnc->socket_grabs = g_list_remove (session_vnc->socket_grabs, ++ grab_func); ++} ++ ++static gboolean ++vnc_socket_grab_func (GrdSessionVnc *session_vnc, ++ GError **error) ++{ ++ if (rfbIsActive (session_vnc->rfb_screen)) ++ { ++ rfbProcessEvents (session_vnc->rfb_screen, 0); ++ ++ if (session_vnc->pending_framebuffer_resize && ++ session_vnc->rfb_client->preferredEncoding != -1) ++ { ++ resize_vnc_framebuffer (session_vnc, ++ session_vnc->pending_framebuffer_width, ++ session_vnc->pending_framebuffer_height); ++ session_vnc->pending_framebuffer_resize = FALSE; ++ } ++ } ++ ++ return TRUE; ++} ++ + static gboolean + handle_socket_data (GSocket *socket, + GIOCondition condition, + gpointer user_data) + { +- GrdSessionVnc *session_vnc = user_data; ++ GrdSessionVnc *session_vnc = GRD_SESSION_VNC (user_data); ++ GrdSession *session = GRD_SESSION (session_vnc); + +- if (condition & G_IO_IN) ++ if (condition & (G_IO_ERR | G_IO_HUP)) ++ { ++ g_warning ("Client disconnected"); ++ ++ grd_session_stop (session); ++ } ++ else if (condition & G_IO_IN) + { +- if (rfbIsActive (session_vnc->rfb_screen)) ++ GrdVncSocketGrabFunc grab_func; ++ g_autoptr (GError) error = NULL; ++ ++ grab_func = g_list_first (session_vnc->socket_grabs)->data; ++ if (!grab_func (session_vnc, &error)) + { +- rfbProcessEvents (session_vnc->rfb_screen, 0); ++ g_warning ("Error when reading socket: %s", error->message); + +- if (session_vnc->pending_framebuffer_resize && +- session_vnc->rfb_client->preferredEncoding != -1) +- { +- resize_vnc_framebuffer (session_vnc, +- session_vnc->pending_framebuffer_width, +- session_vnc->pending_framebuffer_height); +- session_vnc->pending_framebuffer_resize = FALSE; +- } ++ grd_session_stop (session); + } + } + else + { +- g_debug ("Unhandled socket condition %d\n", condition); +- return G_SOURCE_REMOVE; ++ g_warning ("Unhandled socket condition %d\n", condition); ++ g_assert_not_reached (); + } + + return G_SOURCE_CONTINUE; +@@ -548,7 +609,10 @@ grd_session_vnc_attach_source (GrdSessionVnc *session_vnc) + + socket = g_socket_connection_get_socket (session_vnc->connection); + session_vnc->source = g_socket_create_source (socket, +- G_IO_IN | G_IO_PRI, ++ (G_IO_IN | ++ G_IO_PRI | ++ G_IO_ERR | ++ G_IO_HUP), + NULL); + g_source_set_callback (session_vnc->source, + (GSourceFunc) handle_socket_data, +@@ -574,8 +638,10 @@ grd_session_vnc_new (GrdVncServer *vnc_server, + "context", context, + NULL); + ++ session_vnc->vnc_server = vnc_server; + session_vnc->connection = g_object_ref (connection); + ++ grd_session_vnc_grab_socket (session_vnc, vnc_socket_grab_func); + grd_session_vnc_attach_source (session_vnc); + + init_vnc_session (session_vnc); +@@ -590,6 +656,8 @@ grd_session_vnc_dispose (GObject *object) + + g_assert (!session_vnc->rfb_screen); + ++ g_clear_pointer (&session_vnc->socket_grabs, g_list_free); ++ + g_clear_pointer (&session_vnc->pressed_keys, g_hash_table_unref); + + G_OBJECT_CLASS (grd_session_vnc_parent_class)->dispose (object); +diff --git a/src/grd-session-vnc.h b/src/grd-session-vnc.h +index 6bd067a..33245bc 100644 +--- a/src/grd-session-vnc.h ++++ b/src/grd-session-vnc.h +@@ -25,6 +25,7 @@ + + #include + #include ++#include + + #include "grd-session.h" + #include "grd-types.h" +@@ -35,6 +36,9 @@ G_DECLARE_FINAL_TYPE (GrdSessionVnc, + GRD, SESSION_VNC, + GrdSession); + ++typedef gboolean (* GrdVncSocketGrabFunc) (GrdSessionVnc *session_vnc, ++ GError **error); ++ + GrdSessionVnc *grd_session_vnc_new (GrdVncServer *vnc_server, + GSocketConnection *connection); + +@@ -45,6 +49,18 @@ void grd_session_vnc_queue_resize_framebuffer (GrdSessionVnc *session_vnc, + void grd_session_vnc_draw_buffer (GrdSessionVnc *session_vnc, + void *data); + ++int grd_session_vnc_get_fd (GrdSessionVnc *session_vnc); ++ + int grd_session_vnc_get_framebuffer_stride (GrdSessionVnc *session_vnc); + ++rfbClientPtr grd_session_vnc_get_rfb_client (GrdSessionVnc *session_vnc); ++ ++void grd_session_vnc_grab_socket (GrdSessionVnc *session_vnc, ++ GrdVncSocketGrabFunc grab_func); ++ ++void grd_session_vnc_ungrab_socket (GrdSessionVnc *session_vnc, ++ GrdVncSocketGrabFunc grab_func); ++ ++GrdVncServer * grd_session_vnc_get_vnc_server (GrdSessionVnc *session_vnc); ++ + #endif /* GRD_SESSION_VNC_H */ +diff --git a/src/grd-settings.c b/src/grd-settings.c +index a3a2afa..c886b7e 100644 +--- a/src/grd-settings.c ++++ b/src/grd-settings.c +@@ -46,6 +46,7 @@ struct _GrdSettings + GSettings *settings; + gboolean view_only; + GrdVncAuthMethod auth_method; ++ GrdVncEncryption encryption; + } vnc; + }; + +@@ -87,6 +88,12 @@ grd_settings_get_vnc_auth_method (GrdSettings *settings) + return settings->vnc.auth_method; + } + ++GrdVncEncryption ++grd_settings_get_vnc_encryption (GrdSettings *settings) ++{ ++ return settings->vnc.encryption; ++} ++ + static void + update_vnc_view_only (GrdSettings *settings) + { +@@ -101,6 +108,13 @@ update_vnc_auth_method (GrdSettings *settings) + "auth-method"); + } + ++static void ++update_vnc_encryption (GrdSettings *settings) ++{ ++ settings->vnc.encryption = g_settings_get_flags (settings->vnc.settings, ++ "encryption"); ++} ++ + static void + on_vnc_settings_changed (GSettings *vnc_settings, + const char *key, +@@ -116,6 +130,11 @@ on_vnc_settings_changed (GSettings *vnc_settings, + update_vnc_auth_method (settings); + g_signal_emit (settings, signals[VNC_AUTH_METHOD_CHANGED], 0); + } ++ else if (strcmp (key, "encryption") == 0) ++ { ++ update_vnc_encryption (settings); ++ g_signal_emit (settings, signals[VNC_ENCRYPTION_CHANGED], 0); ++ } + } + + static void +@@ -137,6 +156,7 @@ grd_settings_init (GrdSettings *settings) + + update_vnc_view_only (settings); + update_vnc_auth_method (settings); ++ update_vnc_encryption (settings); + } + + static void +@@ -160,4 +180,11 @@ grd_settings_class_init (GrdSettingsClass *klass) + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); ++ signals[VNC_ENCRYPTION_CHANGED] = ++ g_signal_new ("vnc-encryption-changed", ++ G_TYPE_FROM_CLASS (klass), ++ G_SIGNAL_RUN_LAST, ++ 0, ++ NULL, NULL, NULL, ++ G_TYPE_NONE, 0); + } +diff --git a/src/grd-settings.h b/src/grd-settings.h +index 9b23b09..4bca403 100644 +--- a/src/grd-settings.h ++++ b/src/grd-settings.h +@@ -40,4 +40,6 @@ gboolean grd_settings_get_vnc_view_only (GrdSettings *settings); + + GrdVncAuthMethod grd_settings_get_vnc_auth_method (GrdSettings *settings); + ++GrdVncEncryption grd_settings_get_vnc_encryption (GrdSettings *settings); ++ + #endif /* GRD_SETTINGS_H */ +diff --git a/src/grd-vnc-server.c b/src/grd-vnc-server.c +index a8fed02..769b7ec 100644 +--- a/src/grd-vnc-server.c ++++ b/src/grd-vnc-server.c +@@ -24,11 +24,13 @@ + + #include "grd-vnc-server.h" + ++#include + #include + #include + + #include "grd-context.h" + #include "grd-session-vnc.h" ++#include "grd-vnc-tls.h" + + #define GRD_VNC_SERVER_PORT 5900 + +@@ -131,6 +133,43 @@ on_incoming (GSocketService *service, + return TRUE; + } + ++static void ++sync_encryption_settings (GrdVncServer *vnc_server) ++{ ++ GrdSettings *settings = grd_context_get_settings (vnc_server->context); ++ rfbSecurityHandler *tls_security_handler; ++ GrdVncEncryption encryption; ++ ++ tls_security_handler = grd_vnc_tls_get_security_handler (); ++ encryption = grd_settings_get_vnc_encryption (settings); ++ ++ if (encryption == (GRD_VNC_ENCRYPTION_NONE | GRD_VNC_ENCRYPTION_TLS_ANON)) ++ { ++ rfbRegisterSecurityHandler (tls_security_handler); ++ rfbUnregisterChannelSecurityHandler (tls_security_handler); ++ } ++ else if (encryption == GRD_VNC_ENCRYPTION_NONE) ++ { ++ rfbUnregisterSecurityHandler (tls_security_handler); ++ rfbUnregisterChannelSecurityHandler (tls_security_handler); ++ } ++ else ++ { ++ if (encryption != GRD_VNC_ENCRYPTION_TLS_ANON) ++ g_warning ("Invalid VNC encryption setting, falling back to TLS-ANON"); ++ ++ rfbRegisterChannelSecurityHandler (tls_security_handler); ++ rfbUnregisterSecurityHandler (tls_security_handler); ++ } ++} ++ ++static void ++on_vnc_encryption_changed (GrdSettings *settings, ++ GrdVncServer *vnc_server) ++{ ++ sync_encryption_settings (vnc_server); ++} ++ + gboolean + grd_vnc_server_start (GrdVncServer *vnc_server, + GError **error) +@@ -219,12 +258,18 @@ static void + grd_vnc_server_constructed (GObject *object) + { + GrdVncServer *vnc_server = GRD_VNC_SERVER (object); ++ GrdSettings *settings = grd_context_get_settings (vnc_server->context); + + if (grd_context_get_debug_flags (vnc_server->context) & GRD_DEBUG_VNC) + rfbLogEnable (1); + else + rfbLogEnable (0); + ++ g_signal_connect (settings, "vnc-encryption-changed", ++ G_CALLBACK (on_vnc_encryption_changed), ++ vnc_server); ++ sync_encryption_settings (vnc_server); ++ + G_OBJECT_CLASS (grd_vnc_server_parent_class)->constructed (object); + } + +diff --git a/src/grd-vnc-tls.c b/src/grd-vnc-tls.c +new file mode 100644 +index 0000000..8fc0fc2 +--- /dev/null ++++ b/src/grd-vnc-tls.c +@@ -0,0 +1,444 @@ ++/* ++ * Copyright (C) 2018 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 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., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ * ++ */ ++ ++#include "grd-vnc-tls.h" ++ ++#include ++#include ++#include ++#include ++ ++#include "grd-session-vnc.h" ++#include "grd-vnc-server.h" ++ ++typedef struct _GrdVncTlsContext ++{ ++ gnutls_anon_server_credentials_t anon_credentials; ++ gnutls_dh_params_t dh_params; ++} GrdVncTlsContext; ++ ++typedef enum _GrdTlsHandshakeState ++{ ++ GRD_TLS_HANDSHAKE_STATE_INIT, ++ GRD_TLS_HANDSHAKE_STATE_DURING, ++ GRD_TLS_HANDSHAKE_STATE_FINISHED ++} GrdTlsHandshakeState; ++ ++typedef struct _GrdVncTlsSession ++{ ++ GrdVncTlsContext *tls_context; ++ ++ int fd; ++ ++ gnutls_session_t tls_session; ++ GrdTlsHandshakeState handshake_state; ++ ++ char *peek_buffer; ++ int peek_buffer_size; ++ int peek_buffer_len; ++} GrdVncTlsSession; ++ ++static gboolean ++tls_handshake_grab_func (GrdSessionVnc *session_vnc, ++ GError **error); ++ ++static GrdVncTlsContext * ++grd_vnc_tls_context_new (void) ++{ ++ GrdVncTlsContext *tls_context; ++ const unsigned int dh_bits = 1024; ++ ++ tls_context = g_new0 (GrdVncTlsContext, 1); ++ ++ gnutls_global_init (); ++ ++ gnutls_anon_allocate_server_credentials (&tls_context->anon_credentials); ++ ++ gnutls_dh_params_init (&tls_context->dh_params); ++ gnutls_dh_params_generate2 (tls_context->dh_params, dh_bits); ++ ++ gnutls_anon_set_server_dh_params (tls_context->anon_credentials, ++ tls_context->dh_params); ++ ++ return tls_context; ++} ++ ++static void ++grd_vnc_tls_context_free (GrdVncTlsContext *tls_context) ++{ ++ gnutls_dh_params_deinit (tls_context->dh_params); ++ gnutls_anon_free_server_credentials (tls_context->anon_credentials); ++ gnutls_global_deinit (); ++} ++ ++GrdVncTlsContext * ++ensure_tls_context (GrdVncServer *vnc_server) ++{ ++ GrdVncTlsContext *tls_context; ++ ++ tls_context = g_object_get_data (G_OBJECT (vnc_server), "vnc-tls-context"); ++ if (!tls_context) ++ { ++ tls_context = grd_vnc_tls_context_new (); ++ g_object_set_data_full (G_OBJECT (vnc_server), "vnc-tls-context", ++ tls_context, ++ (GDestroyNotify) grd_vnc_tls_context_free); ++ } ++ ++ return tls_context; ++} ++ ++static gboolean ++perform_anon_tls_handshake (GrdVncTlsSession *tls_session, ++ GError **error) ++{ ++ GrdVncTlsContext *tls_context = tls_session->tls_context; ++ const char kx_priority[] = "NORMAL:+ANON-DH"; ++ int ret; ++ ++ gnutls_init (&tls_session->tls_session, GNUTLS_SERVER | GNUTLS_NO_SIGNAL); ++ ++ gnutls_set_default_priority (tls_session->tls_session); ++ gnutls_priority_set_direct (tls_session->tls_session, kx_priority, NULL); ++ ++ gnutls_credentials_set (tls_session->tls_session, ++ GNUTLS_CRD_ANON, ++ tls_context->anon_credentials); ++ gnutls_transport_set_ptr (tls_session->tls_session, ++ GINT_TO_POINTER (tls_session->fd)); ++ ++ ret = gnutls_handshake (tls_session->tls_session); ++ if (ret != GNUTLS_E_SUCCESS && !gnutls_error_is_fatal (ret)) ++ { ++ tls_session->handshake_state = GRD_TLS_HANDSHAKE_STATE_DURING; ++ return TRUE; ++ } ++ ++ if (ret != GNUTLS_E_SUCCESS) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "%s", gnutls_strerror (ret)); ++ gnutls_deinit (tls_session->tls_session); ++ tls_session->tls_session = NULL; ++ return FALSE; ++ } ++ ++ tls_session->handshake_state = GRD_TLS_HANDSHAKE_STATE_FINISHED; ++ return TRUE; ++} ++ ++static gboolean ++continue_tls_handshake (GrdVncTlsSession *tls_session, ++ GError **error) ++{ ++ int ret; ++ ++ ret = gnutls_handshake (tls_session->tls_session); ++ if (ret != GNUTLS_E_SUCCESS && !gnutls_error_is_fatal (ret)) ++ return TRUE; ++ ++ if (ret != GNUTLS_E_SUCCESS) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "%s", gnutls_strerror (ret)); ++ gnutls_deinit (tls_session->tls_session); ++ tls_session->tls_session = NULL; ++ return FALSE; ++ } ++ ++ tls_session->handshake_state = GRD_TLS_HANDSHAKE_STATE_FINISHED; ++ return TRUE; ++} ++ ++static void ++grd_vnc_tls_session_free (GrdVncTlsSession *tls_session) ++{ ++ g_clear_pointer (&tls_session->peek_buffer, g_free); ++ g_clear_pointer (&tls_session->tls_session, (GDestroyNotify) gnutls_deinit); ++ g_free (tls_session); ++} ++ ++static GrdVncTlsSession * ++grd_vnc_tls_session_from_vnc_session (GrdSessionVnc *session_vnc) ++{ ++ return g_object_get_data (G_OBJECT (session_vnc), "vnc-tls-session"); ++} ++ ++static int ++do_read (GrdVncTlsSession *tls_session, ++ char *buf, ++ int len) ++{ ++ do ++ { ++ int ret; ++ ++ ret = gnutls_record_recv (tls_session->tls_session, buf, len); ++ if (ret == GNUTLS_E_AGAIN || ++ ret == GNUTLS_E_INTERRUPTED) ++ { ++ continue; ++ } ++ else if (ret < 0) ++ { ++ g_debug ("gnutls_record_recv failed: %s", gnutls_strerror (ret)); ++ errno = EIO; ++ return -1; ++ } ++ else ++ { ++ return ret; ++ } ++ } ++ while (TRUE); ++} ++ ++static int ++grd_vnc_tls_read_from_socket (rfbClientPtr rfb_client, ++ char *buf, ++ int len) ++{ ++ GrdSessionVnc *session_vnc = rfb_client->screen->screenData; ++ GrdVncTlsSession *tls_session = ++ grd_vnc_tls_session_from_vnc_session (session_vnc); ++ int to_read = len; ++ int len_read = 0; ++ ++ if (to_read < tls_session->peek_buffer_len) ++ { ++ memcpy (buf, tls_session->peek_buffer, to_read); ++ memmove (buf, ++ tls_session->peek_buffer + to_read, ++ tls_session->peek_buffer_len - to_read); ++ len_read = to_read; ++ to_read = 0; ++ } ++ else ++ { ++ memcpy (buf, ++ tls_session->peek_buffer, ++ tls_session->peek_buffer_len); ++ to_read -= tls_session->peek_buffer_len; ++ len_read = tls_session->peek_buffer_len; ++ ++ g_clear_pointer (&tls_session->peek_buffer, ++ g_free); ++ tls_session->peek_buffer_len = 0; ++ tls_session->peek_buffer_size = 0; ++ } ++ ++ if (to_read > 0) ++ { ++ int ret; ++ ++ ret = do_read (tls_session, buf + len_read, to_read); ++ if (ret == -1) ++ return -1; ++ ++ len_read += ret; ++ } ++ ++ return len_read; ++} ++ ++static int ++grd_vnc_tls_peek_at_socket (rfbClientPtr rfb_client, ++ char *buf, ++ int len) ++{ ++ GrdSessionVnc *session_vnc = rfb_client->screen->screenData; ++ GrdVncTlsSession *tls_session = ++ grd_vnc_tls_session_from_vnc_session (session_vnc); ++ int peekable_len; ++ ++ if (tls_session->peek_buffer_len < len) ++ { ++ int ret; ++ ++ if (len > tls_session->peek_buffer_size) ++ { ++ tls_session->peek_buffer = g_renew (char, ++ tls_session->peek_buffer, ++ len); ++ tls_session->peek_buffer_size = len; ++ } ++ ++ ret = do_read (tls_session, ++ tls_session->peek_buffer + tls_session->peek_buffer_len, ++ len - tls_session->peek_buffer_len); ++ if (ret == -1) ++ return -1; ++ ++ tls_session->peek_buffer_len += ret; ++ } ++ ++ peekable_len = MIN (len, tls_session->peek_buffer_len); ++ memcpy (buf, tls_session->peek_buffer, peekable_len); ++ ++ return peekable_len; ++} ++ ++static rfbBool ++grd_vnc_tls_has_pending_on_socket (rfbClientPtr rfb_client) ++{ ++ GrdSessionVnc *session_vnc = rfb_client->screen->screenData; ++ GrdVncTlsSession *tls_session = ++ grd_vnc_tls_session_from_vnc_session (session_vnc); ++ ++ if (tls_session->peek_buffer_len > 0) ++ return TRUE; ++ ++ if (gnutls_record_check_pending (tls_session->tls_session) > 0) ++ return TRUE; ++ ++ return FALSE; ++} ++ ++static int ++grd_vnc_tls_write_to_socket (rfbClientPtr rfb_client, ++ const char *buf, ++ int len) ++{ ++ GrdSessionVnc *session_vnc = rfb_client->screen->screenData; ++ GrdVncTlsSession *tls_session = ++ grd_vnc_tls_session_from_vnc_session (session_vnc); ++ ++ do ++ { ++ int ret; ++ ++ ret = gnutls_record_send (tls_session->tls_session, buf, len); ++ if (ret == GNUTLS_E_AGAIN || ++ ret == GNUTLS_E_INTERRUPTED) ++ { ++ continue; ++ } ++ else if (ret < 0) ++ { ++ g_debug ("gnutls_record_send failed: %s", gnutls_strerror (ret)); ++ errno = EIO; ++ return -1; ++ } ++ else ++ { ++ return ret; ++ } ++ } ++ while (TRUE); ++} ++ ++static gboolean ++perform_handshake (GrdSessionVnc *session_vnc, ++ GError **error) ++{ ++ GrdVncTlsSession *tls_session = ++ grd_vnc_tls_session_from_vnc_session (session_vnc); ++ ++ switch (tls_session->handshake_state) ++ { ++ case GRD_TLS_HANDSHAKE_STATE_INIT: ++ if (!perform_anon_tls_handshake (tls_session, error)) ++ return FALSE; ++ break; ++ case GRD_TLS_HANDSHAKE_STATE_DURING: ++ if (!continue_tls_handshake (tls_session, error)) ++ return FALSE; ++ break; ++ case GRD_TLS_HANDSHAKE_STATE_FINISHED: ++ break; ++ } ++ ++ switch (tls_session->handshake_state) ++ { ++ case GRD_TLS_HANDSHAKE_STATE_INIT: ++ break; ++ case GRD_TLS_HANDSHAKE_STATE_DURING: ++ break; ++ case GRD_TLS_HANDSHAKE_STATE_FINISHED: ++ grd_session_vnc_ungrab_socket (session_vnc, tls_handshake_grab_func); ++ rfbSendSecurityTypeList (grd_session_vnc_get_rfb_client (session_vnc), ++ RFB_SECURITY_TAG_CHANNEL); ++ break; ++ } ++ ++ return TRUE; ++} ++ ++static gboolean ++tls_handshake_grab_func (GrdSessionVnc *session_vnc, ++ GError **error) ++{ ++ g_autoptr (GError) handshake_error = NULL; ++ ++ if (!perform_handshake (session_vnc, &handshake_error)) ++ { ++ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED, ++ "TLS handshake failed: %s", handshake_error->message); ++ return FALSE; ++ } ++ ++ return TRUE; ++} ++ ++static void ++rfb_tls_security_handler (rfbClientPtr rfb_client) ++{ ++ GrdSessionVnc *session_vnc = rfb_client->screen->screenData; ++ GrdVncTlsSession *tls_session; ++ g_autoptr(GError) error = NULL; ++ ++ tls_session = grd_vnc_tls_session_from_vnc_session (session_vnc); ++ if (!tls_session) ++ { ++ GrdVncServer *vnc_server = grd_session_vnc_get_vnc_server (session_vnc); ++ ++ tls_session = g_new0 (GrdVncTlsSession, 1); ++ tls_session->fd = grd_session_vnc_get_fd (session_vnc); ++ tls_session->tls_context = ensure_tls_context (vnc_server); ++ g_object_set_data_full (G_OBJECT (session_vnc), "vnc-tls-session", ++ tls_session, ++ (GDestroyNotify) grd_vnc_tls_session_free); ++ ++ rfb_client->readFromSocket = grd_vnc_tls_read_from_socket; ++ rfb_client->peekAtSocket = grd_vnc_tls_peek_at_socket; ++ rfb_client->hasPendingOnSocket = grd_vnc_tls_has_pending_on_socket; ++ rfb_client->writeToSocket = grd_vnc_tls_write_to_socket; ++ ++ grd_session_vnc_grab_socket (session_vnc, tls_handshake_grab_func); ++ } ++ ++ if (!perform_handshake (session_vnc, &error)) ++ { ++ g_warning ("TLS handshake failed: %s", error->message); ++ rfbCloseClient (rfb_client); ++ } ++} ++ ++static rfbSecurityHandler anon_tls_security_handler = { ++ .type = rfbTLS, ++ .handler = rfb_tls_security_handler, ++ .securityTags = RFB_SECURITY_TAG_CHANNEL, ++}; ++ ++rfbSecurityHandler * ++grd_vnc_tls_get_security_handler (void) ++{ ++ return &anon_tls_security_handler; ++} +diff --git a/src/grd-vnc-tls.h b/src/grd-vnc-tls.h +new file mode 100644 +index 0000000..135ef8c +--- /dev/null ++++ b/src/grd-vnc-tls.h +@@ -0,0 +1,28 @@ ++/* ++ * Copyright (C) 2018 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 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., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ * ++ */ ++ ++#ifndef GRD_VNC_TLS_H ++#define GRD_VNC_TLS_H ++ ++#include ++ ++rfbSecurityHandler * grd_vnc_tls_get_security_handler (void); ++ ++#endif /* GRD_VNC_TLS_H */ +diff --git a/src/meson.build b/src/meson.build +index 70e2102..b633ad7 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -19,6 +19,8 @@ daemon_sources = files([ + 'grd-vnc-pipewire-stream.h', + 'grd-vnc-server.c', + 'grd-vnc-server.h', ++ 'grd-vnc-tls.c', ++ 'grd-vnc-tls.h', + ]) + + gen_daemon_sources = [] +@@ -49,7 +51,8 @@ executable('gnome-remote-desktop-daemon', + pipewire_dep, + libvncserver_dep, + libsecret_dep, +- libnotify_dep], ++ libnotify_dep, ++ gnutls_dep], + include_directories: [configinc], + install: true, + install_dir: libexecdir) +diff --git a/src/org.gnome.desktop.remote-desktop.gschema.xml b/src/org.gnome.desktop.remote-desktop.gschema.xml +index a5c2022..846e65b 100644 +--- a/src/org.gnome.desktop.remote-desktop.gschema.xml ++++ b/src/org.gnome.desktop.remote-desktop.gschema.xml +@@ -23,5 +23,15 @@ + * password - by requiring the remote client to provide a known password + + ++ ++ ['tls-anon'] ++ Allowed encryption method to use ++ ++ Allowed encryption methods. Includes the following: ++ ++ * none - no encryption ++ * tls-anon - anonymous (unauthenticated) TLS ++ ++ + + +-- +2.17.1 + diff --git a/SOURCES/0001-vnc-Allow-overriding-password-with-env-var.patch b/SOURCES/0001-vnc-Allow-overriding-password-with-env-var.patch new file mode 100644 index 0000000..ba3cc31 --- /dev/null +++ b/SOURCES/0001-vnc-Allow-overriding-password-with-env-var.patch @@ -0,0 +1,42 @@ +From 1467e4c26f47ad3747903392a026698a169870aa Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Wed, 10 Apr 2019 15:59:54 +0200 +Subject: [PATCH] vnc: Allow overriding password with env var + +For testing purposes. Also overrides VNC auth method setting. +--- + src/grd-settings.c | 11 ++++++++++- + 1 file changed, 10 insertions(+), 1 deletion(-) + +diff --git a/src/grd-settings.c b/src/grd-settings.c +index c886b7e..d6c4a25 100644 +--- a/src/grd-settings.c ++++ b/src/grd-settings.c +@@ -71,6 +71,12 @@ char * + grd_settings_get_vnc_password (GrdSettings *settings, + GError **error) + { ++ const char *test_password_override; ++ ++ test_password_override = g_getenv ("GNOME_REMOTE_DESKTOP_TEST_VNC_PASSWORD"); ++ if (test_password_override) ++ return g_strdup (test_password_override); ++ + return secret_password_lookup_sync (GRD_VNC_PASSWORD_SCHEMA, + NULL, error, + NULL); +@@ -85,7 +91,10 @@ grd_settings_get_vnc_view_only (GrdSettings *settings) + GrdVncAuthMethod + grd_settings_get_vnc_auth_method (GrdSettings *settings) + { +- return settings->vnc.auth_method; ++ if (g_getenv ("GNOME_REMOTE_DESKTOP_TEST_VNC_PASSWORD")) ++ return GRD_VNC_AUTH_METHOD_PASSWORD; ++ else ++ return settings->vnc.auth_method; + } + + GrdVncEncryption +-- +2.21.0 + diff --git a/SOURCES/0002-vnc-pipewire-stream-Close-session-when-disconnected.patch b/SOURCES/0002-vnc-pipewire-stream-Close-session-when-disconnected.patch new file mode 100644 index 0000000..cd1c5e4 --- /dev/null +++ b/SOURCES/0002-vnc-pipewire-stream-Close-session-when-disconnected.patch @@ -0,0 +1,28 @@ +From 59188d81cf8936cd9f5400df040d875427251bf2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Mon, 1 Oct 2018 18:05:07 +0200 +Subject: [PATCH 2/2] vnc-pipewire-stream: Close session when disconnected + +When there is an active stream, and we're disconnected from PipeWire +(e.g. because it terminated), close the session. +--- + src/grd-vnc-pipewire-stream.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/grd-vnc-pipewire-stream.c b/src/grd-vnc-pipewire-stream.c +index 66d66a0..d6454b8 100644 +--- a/src/grd-vnc-pipewire-stream.c ++++ b/src/grd-vnc-pipewire-stream.c +@@ -392,6 +392,9 @@ on_state_changed (void *user_data, + } + break; + case PW_REMOTE_STATE_UNCONNECTED: ++ if (stream->pipewire_stream) ++ g_signal_emit (stream, signals[CLOSED], 0); ++ break; + case PW_REMOTE_STATE_CONNECTING: + break; + } +-- +2.17.1 + diff --git a/SOURCES/rhel8.0.0-backports.patch b/SOURCES/rhel8.0.0-backports.patch new file mode 100644 index 0000000..bfea909 --- /dev/null +++ b/SOURCES/rhel8.0.0-backports.patch @@ -0,0 +1,719 @@ +From b9221b1efcfe205409628bf365008e0b248725f3 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Fri, 23 Nov 2018 16:55:20 +0100 +Subject: [PATCH 1/6] vnc: Detach source when client is gone + +LibVNCServer will close the socket for us, so don't keep the socket +source attached after a client is gone. It's not fast enough to close it +in the idle function, as that means we'd get a G_IO_NVAL error when the +source is processed. + +Fixes: https://gitlab.gnome.org/jadahl/gnome-remote-desktop/issues/23 +--- + src/grd-session-vnc.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/grd-session-vnc.c b/src/grd-session-vnc.c +index 3c98eeb..fbf41e2 100644 +--- a/src/grd-session-vnc.c ++++ b/src/grd-session-vnc.c +@@ -182,6 +182,7 @@ handle_client_gone (rfbClientPtr rfb_client) + + g_debug ("VNC client gone"); + ++ grd_session_vnc_detach_source (session_vnc); + maybe_queue_close_session_idle (session_vnc); + } + +-- +2.19.1 + + +From 5ce2a1a6f80581d7d5b79b3a86f6707d22ad94ba Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Mon, 26 Nov 2018 16:37:12 +0100 +Subject: [PATCH 2/6] session/vnc: Always set pixel format translate functions + +Internally LibVNCServer uses RGBX, but we provide BGRX. Currently the +LibVNCServer API doesn't allow to override this, but internally, it +supports any order of the color components. We relied on this already, +to avoid an extra pixel conversion, but we failed to update the +conversion table. + +Fixes: https://gitlab.gnome.org/jadahl/gnome-remote-desktop/issues/20 +--- + src/grd-session-vnc.c | 4 +++- + 1 file changed, 3 insertions(+), 1 deletion(-) + +diff --git a/src/grd-session-vnc.c b/src/grd-session-vnc.c +index fbf41e2..c6be742 100644 +--- a/src/grd-session-vnc.c ++++ b/src/grd-session-vnc.c +@@ -115,11 +115,13 @@ resize_vnc_framebuffer (GrdSessionVnc *session_vnc, + BGRX_BYTES_PER_PIXEL); + + /* +- * Our format is hard coded to BGRX but LibVNCServer asusumes it's RGBX; ++ * Our format is hard coded to BGRX but LibVNCServer assumes it's RGBX; + * lets override that. + */ ++ + swap_uint8 (&session_vnc->rfb_screen->serverFormat.redShift, + &session_vnc->rfb_screen->serverFormat.blueShift); ++ rfb_screen->setTranslateFunction (session_vnc->rfb_client); + } + + void +-- +2.19.1 + + +From b3bb9ebb47a9883c1022a1ad3dc90aa9bb9c2d27 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Mon, 17 Dec 2018 13:12:38 +0100 +Subject: [PATCH 3/6] session/vnc: Update server format earlier too + +We update the server protocol to swap the blue and red shift levels, as +LibVNCServer only RGBX by default, but we pass BGRX. Doing this just +after resizing the framebuffer where the format is updated is too late +though, as for some reason LibVNCServer's tight encoding still gets it +wrong when encoding JPEG rects. + +Fixes: https://gitlab.gnome.org/jadahl/gnome-remote-desktop/issues/20 +--- + src/grd-session-vnc.c | 22 +++++++++++++++------- + 1 file changed, 15 insertions(+), 7 deletions(-) + +diff --git a/src/grd-session-vnc.c b/src/grd-session-vnc.c +index c6be742..b86c3a7 100644 +--- a/src/grd-session-vnc.c ++++ b/src/grd-session-vnc.c +@@ -94,6 +94,19 @@ swap_uint8 (uint8_t *a, + *b = tmp; + } + ++static void ++update_server_format (GrdSessionVnc *session_vnc) ++{ ++ rfbScreenInfoPtr rfb_screen = session_vnc->rfb_screen; ++ ++ /* ++ * Our format is hard coded to BGRX but LibVNCServer assumes it's RGBX; ++ * lets override that. ++ */ ++ swap_uint8 (&rfb_screen->serverFormat.redShift, ++ &rfb_screen->serverFormat.blueShift); ++} ++ + static void + resize_vnc_framebuffer (GrdSessionVnc *session_vnc, + int width, +@@ -114,13 +127,7 @@ resize_vnc_framebuffer (GrdSessionVnc *session_vnc, + BGRX_SAMPLES_PER_PIXEL, + BGRX_BYTES_PER_PIXEL); + +- /* +- * Our format is hard coded to BGRX but LibVNCServer assumes it's RGBX; +- * lets override that. +- */ +- +- swap_uint8 (&session_vnc->rfb_screen->serverFormat.redShift, +- &session_vnc->rfb_screen->serverFormat.blueShift); ++ update_server_format (session_vnc); + rfb_screen->setTranslateFunction (session_vnc->rfb_client); + } + +@@ -517,6 +524,7 @@ init_vnc_session (GrdSessionVnc *session_vnc) + rfb_screen = rfbGetScreen (0, NULL, + screen_width, screen_height, + 8, 3, 4); ++ update_server_format (session_vnc); + + socket = g_socket_connection_get_socket (session_vnc->connection); + rfb_screen->inetdSock = g_socket_get_fd (socket); +-- +2.19.1 + + +From 930d1c127e4a2252e3288cd3c752debe88d938c4 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Mon, 17 Dec 2018 14:35:17 +0100 +Subject: [PATCH 4/6] vnc: Request cursor sprite as PipeWire metadata + +This allows sending cursor updates, including position and cursor +sprite, without having to embed it into the framebuffer. In effect, when +there is a hardware cursor in the compositor, moving the cursor will not +result in any actual frames being copied. + +The actual cursor sprite sent over VNC is derived from the cursor sprite +used in the compositor. The conversion is lossy, as the VNC cursor +sprite is a 3 bits per pixel (transparen, white or black), and the cursor +sprite from the compositor is 32 bits per pixel. + +This bumps the PipeWire requirement to 0.2.5. +--- + meson.build | 2 +- + src/grd-session-vnc.c | 24 +++++++ + src/grd-session-vnc.h | 7 ++ + src/grd-session.c | 10 +++ + src/grd-types.h | 5 ++ + src/grd-vnc-cursor.c | 125 ++++++++++++++++++++++++++++++++++ + src/grd-vnc-cursor.h | 37 ++++++++++ + src/grd-vnc-pipewire-stream.c | 94 +++++++++++++++++++++++-- + src/meson.build | 2 + + 9 files changed, 299 insertions(+), 7 deletions(-) + create mode 100644 src/grd-vnc-cursor.c + create mode 100644 src/grd-vnc-cursor.h + +diff --git a/meson.build b/meson.build +index cb0d5fe..b6bfb6f 100644 +--- a/meson.build ++++ b/meson.build +@@ -10,7 +10,7 @@ gnome = import('gnome') + glib_dep = dependency('glib-2.0') + gio_dep = dependency('gio-2.0') + gio_unix_dep = dependency('gio-unix-2.0') +-pipewire_dep = dependency('libpipewire-0.2', version: '>= 0.2.2') ++pipewire_dep = dependency('libpipewire-0.2', version: '>= 0.2.5') + systemd_dep = dependency('systemd') + libvncserver_dep = dependency('libvncserver') + libsecret_dep = dependency('libsecret-1') +diff --git a/src/grd-session-vnc.c b/src/grd-session-vnc.c +index b86c3a7..247c130 100644 +--- a/src/grd-session-vnc.c ++++ b/src/grd-session-vnc.c +@@ -174,6 +174,30 @@ grd_session_vnc_draw_buffer (GrdSessionVnc *session_vnc, + rfbProcessEvents (session_vnc->rfb_screen, 0); + } + ++void ++grd_session_vnc_set_cursor (GrdSessionVnc *session_vnc, ++ rfbCursorPtr rfb_cursor) ++{ ++ rfbSetCursor (session_vnc->rfb_screen, rfb_cursor); ++} ++ ++void ++grd_session_vnc_move_cursor (GrdSessionVnc *session_vnc, ++ int x, ++ int y) ++{ ++ if (session_vnc->rfb_screen->cursorX == x || ++ session_vnc->rfb_screen->cursorY == y) ++ return; ++ ++ LOCK (session_vnc->rfb_screen->cursorMutex); ++ session_vnc->rfb_screen->cursorX = x; ++ session_vnc->rfb_screen->cursorY = y; ++ UNLOCK (session_vnc->rfb_screen->cursorMutex); ++ ++ session_vnc->rfb_client->cursorWasMoved = TRUE; ++} ++ + static void + maybe_queue_close_session_idle (GrdSessionVnc *session_vnc) + { +diff --git a/src/grd-session-vnc.h b/src/grd-session-vnc.h +index 33245bc..789693e 100644 +--- a/src/grd-session-vnc.h ++++ b/src/grd-session-vnc.h +@@ -51,6 +51,13 @@ void grd_session_vnc_draw_buffer (GrdSessionVnc *session_vnc, + + int grd_session_vnc_get_fd (GrdSessionVnc *session_vnc); + ++void grd_session_vnc_set_cursor (GrdSessionVnc *session_vnc, ++ rfbCursorPtr rfb_cursor); ++ ++void grd_session_vnc_move_cursor (GrdSessionVnc *session_vnc, ++ int x, ++ int y); ++ + int grd_session_vnc_get_framebuffer_stride (GrdSessionVnc *session_vnc); + + rfbClientPtr grd_session_vnc_get_rfb_client (GrdSessionVnc *session_vnc); +diff --git a/src/grd-session.c b/src/grd-session.c +index 212770b..3697584 100644 +--- a/src/grd-session.c ++++ b/src/grd-session.c +@@ -47,6 +47,13 @@ enum + + static guint signals[LAST_SIGNAL]; + ++typedef enum _GrdScreenCastCursorMode ++{ ++ GRD_SCREEN_CAST_CURSOR_MODE_HIDDEN = 0, ++ GRD_SCREEN_CAST_CURSOR_MODE_EMBEDDED = 1, ++ GRD_SCREEN_CAST_CURSOR_MODE_METADATA = 2, ++} GrdScreenCastCursorMode; ++ + typedef struct _GrdSessionPrivate + { + GrdContext *context; +@@ -296,6 +303,9 @@ on_screen_cast_session_proxy_acquired (GObject *object, + priv->screen_cast_session = session_proxy; + + g_variant_builder_init (&properties_builder, G_VARIANT_TYPE ("a{sv}")); ++ g_variant_builder_add (&properties_builder, "{sv}", ++ "cursor-mode", ++ g_variant_new_uint32 (GRD_SCREEN_CAST_CURSOR_MODE_METADATA)); + + /* TODO: Support something other than primary monitor */ + grd_dbus_screen_cast_session_call_record_monitor (session_proxy, +diff --git a/src/grd-types.h b/src/grd-types.h +index af92c88..89dd22f 100644 +--- a/src/grd-types.h ++++ b/src/grd-types.h +@@ -31,4 +31,9 @@ typedef struct _GrdPipeWireStream GrdPipeWireStream; + typedef struct _GrdPipeWireStreamMonitor GrdPipeWireStreamMonitor; + typedef struct _GrdVncServer GrdVncServer; + ++typedef enum _GrdPixelFormat ++{ ++ GRD_PIXEL_FORMAT_RGBA8888, ++} GrdPixelFormat; ++ + #endif /* GRD_TYPES_H */ +diff --git a/src/grd-vnc-cursor.c b/src/grd-vnc-cursor.c +new file mode 100644 +index 0000000..c0cb1aa +--- /dev/null ++++ b/src/grd-vnc-cursor.c +@@ -0,0 +1,125 @@ ++/* ++ * Copyright (C) 2018 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 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., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ * ++ */ ++ ++#include "config.h" ++ ++#include "grd-vnc-cursor.h" ++ ++#include ++#include ++ ++static void ++get_pixel_components (uint32_t pixel, ++ GrdPixelFormat format, ++ uint8_t *a, ++ uint8_t *r, ++ uint8_t *g, ++ uint8_t *b) ++{ ++ g_assert (format == GRD_PIXEL_FORMAT_RGBA8888); ++ ++ *a = (pixel & 0xff000000) >> 24; ++ *b = (pixel & 0xff0000) >> 16; ++ *g = (pixel & 0xff00) >> 8; ++ *r = pixel & 0xff; ++} ++ ++static gboolean ++is_practically_black (uint8_t r, ++ uint8_t g, ++ uint8_t b) ++{ ++ if (r <= 0x62 && ++ g <= 0x62 && ++ b <= 0x62) ++ return TRUE; ++ else ++ return FALSE; ++} ++ ++static gboolean ++is_practically_opaque (uint8_t a) ++{ ++ return a > 0xe0; ++} ++ ++rfbCursorPtr ++grd_vnc_create_cursor (int width, ++ int height, ++ int stride, ++ GrdPixelFormat format, ++ uint8_t *buf) ++{ ++ g_autofree char *cursor = NULL; ++ g_autofree char *mask = NULL; ++ int y; ++ ++ g_return_val_if_fail (format == GRD_PIXEL_FORMAT_RGBA8888, NULL); ++ ++ cursor = g_new0 (char, width * height); ++ mask = g_new0 (char, width * height); ++ ++ for (y = 0; y < height; y++) ++ { ++ uint32_t *pixel_row; ++ int x; ++ ++ pixel_row = (uint32_t *) &buf[y * stride]; ++ ++ for (x = 0; x < width; x++) ++ { ++ uint32_t pixel = pixel_row[x]; ++ uint8_t a, r, g, b; ++ ++ get_pixel_components (pixel, ++ format, ++ &a, &r, &g, &b); ++ ++ if (is_practically_opaque (a)) ++ { ++ if (is_practically_black (r, g, b)) ++ cursor[y * width + x] = ' '; ++ else ++ cursor[y * width + x] = 'x'; ++ ++ mask[y * width + x] = 'x'; ++ } ++ else ++ { ++ cursor[y * width + x] = ' '; ++ mask[y * width + x] = ' '; ++ } ++ } ++ } ++ ++ return rfbMakeXCursor (width, height, cursor, mask); ++} ++ ++rfbCursorPtr ++grd_vnc_create_empty_cursor (int width, ++ int height) ++{ ++ g_autofree char *cursor = NULL; ++ cursor = g_new0 (char, width * height); ++ ++ memset (cursor, ' ', width * height); ++ ++ return rfbMakeXCursor (width, height, cursor, cursor); ++} +diff --git a/src/grd-vnc-cursor.h b/src/grd-vnc-cursor.h +new file mode 100644 +index 0000000..36bd0fb +--- /dev/null ++++ b/src/grd-vnc-cursor.h +@@ -0,0 +1,37 @@ ++/* ++ * Copyright (C) 2018 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 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., 59 Temple Place - Suite 330, Boston, MA ++ * 02111-1307, USA. ++ * ++ */ ++ ++#ifndef GRD_VNC_CURSOR_H ++#define GRD_VNC_CURSOR_H ++ ++#include ++ ++#include "grd-types.h" ++ ++rfbCursorPtr grd_vnc_create_cursor (int width, ++ int height, ++ int stride, ++ GrdPixelFormat format, ++ uint8_t *buf); ++ ++rfbCursorPtr grd_vnc_create_empty_cursor (int width, ++ int height); ++ ++#endif /* GRD_VNC_CURSOR_H */ +diff --git a/src/grd-vnc-pipewire-stream.c b/src/grd-vnc-pipewire-stream.c +index d6454b8..c341887 100644 +--- a/src/grd-vnc-pipewire-stream.c ++++ b/src/grd-vnc-pipewire-stream.c +@@ -28,6 +28,8 @@ + #include + #include + ++#include "grd-vnc-cursor.h" ++ + enum + { + CLOSED, +@@ -43,6 +45,7 @@ typedef struct _GrdSpaType + struct spa_type_media_subtype media_subtype; + struct spa_type_format_video format_video; + struct spa_type_video_format video_format; ++ uint32_t meta_cursor; + } GrdSpaType; + + typedef struct _GrdPipeWireSource +@@ -79,6 +82,11 @@ struct _GrdVncPipeWireStream + G_DEFINE_TYPE (GrdVncPipeWireStream, grd_vnc_pipewire_stream, + G_TYPE_OBJECT) + ++#define CURSOR_META_SIZE(width, height) \ ++ (sizeof(struct spa_meta_cursor) + \ ++ sizeof(struct spa_meta_bitmap) + width * height * 4) ++ ++ + static void + init_spa_type (GrdSpaType *type, + struct spa_type_map *map) +@@ -87,6 +95,7 @@ init_spa_type (GrdSpaType *type, + spa_type_media_subtype_map (map, &type->media_subtype); + spa_type_format_video_map (map, &type->format_video); + spa_type_video_format_map (map, &type->video_format); ++ type->meta_cursor = spa_type_map_get_id (map, SPA_TYPE_META__Cursor); + } + + static gboolean +@@ -196,7 +205,7 @@ on_stream_format_changed (void *user_data, + int height; + int stride; + int size; +- const struct spa_pod *params[2]; ++ const struct spa_pod *params[3]; + + if (!format) + { +@@ -231,7 +240,29 @@ on_stream_format_changed (void *user_data, + ":", pipewire_type->param_meta.type, "I", pipewire_type->meta.Header, + ":", pipewire_type->param_meta.size, "i", sizeof(struct spa_meta_header)); + +- pw_stream_finish_format (stream->pipewire_stream, 0, params, 2); ++ params[2] = spa_pod_builder_object( ++ &pod_builder, ++ pipewire_type->param.idMeta, pipewire_type->param_meta.Meta, ++ ":", pipewire_type->param_meta.type, "I", stream->spa_type.meta_cursor, ++ ":", pipewire_type->param_meta.size, "iru", CURSOR_META_SIZE (64,64), ++ SPA_POD_PROP_MIN_MAX (CURSOR_META_SIZE (1,1), ++ CURSOR_META_SIZE (256,256))); ++ ++ pw_stream_finish_format (stream->pipewire_stream, 0, ++ params, G_N_ELEMENTS (params)); ++} ++ ++static gboolean ++spa_pixel_format_to_grd_pixel_format (GrdSpaType *spa_type, ++ uint32_t spa_format, ++ GrdPixelFormat *out_format) ++{ ++ if (spa_format == spa_type->video_format.RGBA) ++ *out_format = GRD_PIXEL_FORMAT_RGBA8888; ++ else ++ return FALSE; ++ ++ return TRUE; + } + + static int +@@ -243,12 +274,19 @@ do_render (struct spa_loop *loop, + void *user_data) + { + GrdVncPipeWireStream *stream = GRD_VNC_PIPEWIRE_STREAM (user_data); ++ GrdSpaType *spa_type = &stream->spa_type; + struct spa_buffer *buffer = ((struct spa_buffer **) data)[0]; + uint8_t *map; + void *src_data; ++ struct spa_meta_cursor *spa_meta_cursor; + +- if (buffer->datas[0].type == stream->pipewire_type->data.MemFd || +- buffer->datas[0].type == stream->pipewire_type->data.DmaBuf) ++ if (buffer->datas[0].chunk->size == 0) ++ { ++ map = NULL; ++ src_data = NULL; ++ } ++ else if (buffer->datas[0].type == stream->pipewire_type->data.MemFd || ++ buffer->datas[0].type == stream->pipewire_type->data.DmaBuf) + { + map = mmap (NULL, buffer->datas[0].maxsize + buffer->datas[0].mapoffset, + PROT_READ, MAP_PRIVATE, buffer->datas[0].fd, 0); +@@ -264,7 +302,51 @@ do_render (struct spa_loop *loop, + return -EINVAL; + } + +- grd_session_vnc_draw_buffer (stream->session, src_data); ++ spa_meta_cursor = spa_buffer_find_meta (buffer, spa_type->meta_cursor); ++ if (spa_meta_cursor && spa_meta_cursor_is_valid (spa_meta_cursor)) ++ { ++ struct spa_meta_bitmap *spa_meta_bitmap; ++ GrdPixelFormat format; ++ ++ spa_meta_bitmap = SPA_MEMBER (spa_meta_cursor, ++ spa_meta_cursor->bitmap_offset, ++ struct spa_meta_bitmap); ++ ++ if (spa_meta_bitmap->size.width > 0 && ++ spa_meta_bitmap->size.height > 0 && ++ spa_pixel_format_to_grd_pixel_format (spa_type, ++ spa_meta_bitmap->format, ++ &format)) ++ { ++ uint8_t *buf; ++ rfbCursorPtr rfb_cursor; ++ ++ buf = SPA_MEMBER (spa_meta_bitmap, spa_meta_bitmap->offset, uint8_t); ++ rfb_cursor = grd_vnc_create_cursor (spa_meta_bitmap->size.width, ++ spa_meta_bitmap->size.height, ++ spa_meta_bitmap->stride, ++ format, ++ buf); ++ rfb_cursor->xhot = spa_meta_cursor->hotspot.x; ++ rfb_cursor->yhot = spa_meta_cursor->hotspot.y; ++ ++ grd_session_vnc_set_cursor (stream->session, rfb_cursor); ++ } ++ else ++ { ++ rfbCursorPtr empty_cursor; ++ ++ empty_cursor = grd_vnc_create_empty_cursor (1, 1); ++ grd_session_vnc_set_cursor (stream->session, empty_cursor); ++ } ++ ++ grd_session_vnc_move_cursor (stream->session, ++ spa_meta_cursor->position.x, ++ spa_meta_cursor->position.y); ++ } ++ ++ if (src_data) ++ grd_session_vnc_draw_buffer (stream->session, src_data); + + if (map) + munmap (map, buffer->datas[0].maxsize + buffer->datas[0].mapoffset); +@@ -306,7 +388,7 @@ connect_to_stream (GrdVncPipeWireStream *stream, + struct spa_rectangle max_rect; + struct spa_fraction min_framerate; + struct spa_fraction max_framerate; +- const struct spa_pod *params[1]; ++ const struct spa_pod *params[2]; + int ret; + + pipewire_stream = pw_stream_new (stream->pipewire_remote, +diff --git a/src/meson.build b/src/meson.build +index b633ad7..31c7221 100644 +--- a/src/meson.build ++++ b/src/meson.build +@@ -15,6 +15,8 @@ daemon_sources = files([ + 'grd-stream.c', + 'grd-stream.h', + 'grd-types.h', ++ 'grd-vnc-cursor.c', ++ 'grd-vnc-cursor.h', + 'grd-vnc-pipewire-stream.c', + 'grd-vnc-pipewire-stream.h', + 'grd-vnc-server.c', +-- +2.19.1 + + +From 6aff333ef474abfe18e0da964032e08e6cda2cd6 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 3 Jan 2019 18:07:33 +0100 +Subject: [PATCH 5/6] session/vnc: Set rfbScreenPtr pointer earlier + +Otherwise the format update_server_format() function doesn't find it. +--- + src/grd-session-vnc.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/grd-session-vnc.c b/src/grd-session-vnc.c +index 247c130..dcd8599 100644 +--- a/src/grd-session-vnc.c ++++ b/src/grd-session-vnc.c +@@ -548,6 +548,8 @@ init_vnc_session (GrdSessionVnc *session_vnc) + rfb_screen = rfbGetScreen (0, NULL, + screen_width, screen_height, + 8, 3, 4); ++ session_vnc->rfb_screen = rfb_screen; ++ + update_server_format (session_vnc); + + socket = g_socket_connection_get_socket (session_vnc->connection); +@@ -567,8 +569,6 @@ init_vnc_session (GrdSessionVnc *session_vnc) + rfb_screen->frameBuffer = g_malloc0 (screen_width * screen_height * 4); + memset (rfb_screen->frameBuffer, 0x1f, screen_width * screen_height * 4); + +- session_vnc->rfb_screen = rfb_screen; +- + rfbInitServer (rfb_screen); + rfbProcessEvents (rfb_screen, 0); + } +-- +2.19.1 + + +From 5d9ffd0efe8b32d95c1a566f431e7d14fa95f3fa Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20=C3=85dahl?= +Date: Thu, 3 Jan 2019 18:08:52 +0100 +Subject: [PATCH 6/6] vnc-pipewire-stream: Assume no bitmap offset means no + bitmap change + +This is according to the agreed semantics: +https://github.com/PipeWire/pipewire/commit/8984c6f48d2106f143d3f6d5df976f788ecfde30 +--- + src/grd-vnc-pipewire-stream.c | 18 +++++++++++++----- + 1 file changed, 13 insertions(+), 5 deletions(-) + +diff --git a/src/grd-vnc-pipewire-stream.c b/src/grd-vnc-pipewire-stream.c +index c341887..cee569a 100644 +--- a/src/grd-vnc-pipewire-stream.c ++++ b/src/grd-vnc-pipewire-stream.c +@@ -308,11 +308,19 @@ do_render (struct spa_loop *loop, + struct spa_meta_bitmap *spa_meta_bitmap; + GrdPixelFormat format; + +- spa_meta_bitmap = SPA_MEMBER (spa_meta_cursor, +- spa_meta_cursor->bitmap_offset, +- struct spa_meta_bitmap); ++ if (spa_meta_cursor->bitmap_offset) ++ { ++ spa_meta_bitmap = SPA_MEMBER (spa_meta_cursor, ++ spa_meta_cursor->bitmap_offset, ++ struct spa_meta_bitmap); ++ } ++ else ++ { ++ spa_meta_bitmap = NULL; ++ } + +- if (spa_meta_bitmap->size.width > 0 && ++ if (spa_meta_bitmap && ++ spa_meta_bitmap->size.width > 0 && + spa_meta_bitmap->size.height > 0 && + spa_pixel_format_to_grd_pixel_format (spa_type, + spa_meta_bitmap->format, +@@ -332,7 +340,7 @@ do_render (struct spa_loop *loop, + + grd_session_vnc_set_cursor (stream->session, rfb_cursor); + } +- else ++ else if (spa_meta_bitmap) + { + rfbCursorPtr empty_cursor; + +-- +2.19.1 + diff --git a/SPECS/gnome-remote-desktop.spec b/SPECS/gnome-remote-desktop.spec new file mode 100644 index 0000000..67ea66e --- /dev/null +++ b/SPECS/gnome-remote-desktop.spec @@ -0,0 +1,119 @@ +%global systemd_unit gnome-remote-desktop.service + +Name: gnome-remote-desktop +Version: 0.1.6 +Release: 5%{?dist} +Summary: GNOME Remote Desktop screen share service + +License: GPLv2+ +URL: https://gitlab.gnome.org/jadahl/gnome-remote-desktop +Source0: https://gitlab.gnome.org/jadahl/gnome-remote-desktop/uploads/c6862c12f0b741714d5a27e0693322fe/gnome-remote-desktop-0.1.6.tar.xz + +# Adds encryption support (requires patched LibVNCServer) +Patch0: 0001-vnc-Add-anonymous-TLS-encryption-support.patch + +# Align pipewire requirement with Fedora +Patch1: 0001-meson.build-Bump-pipewire-requirement-to-0.2.2.patch + +# Crash fix (rhbz#1627469) +Patch2: 0001-session-vnc-Don-t-requeue-close-session-idle.patch +Patch3: 0002-vnc-pipewire-stream-Close-session-when-disconnected.patch + +# Backport various fixes (rhbz#1659118) +Patch4: rhel8.0.0-backports.patch + +# Backport password override, for testing (rhbz#1713330) +Patch5: 0001-vnc-Allow-overriding-password-with-env-var.patch + +BuildRequires: git +BuildRequires: gcc +BuildRequires: meson >= 0.36.0 +BuildRequires: pkgconfig +BuildRequires: pkgconfig(glib-2.0) >= 2.32 +BuildRequires: pkgconfig(gio-unix-2.0) >= 2.32 +BuildRequires: pkgconfig(libpipewire-0.2) >= 0.2.5 +BuildRequires: pkgconfig(libvncserver) >= 0.9.11-7 +BuildRequires: pkgconfig(libsecret-1) +BuildRequires: pkgconfig(libnotify) +BuildRequires: pkgconfig(gnutls) +BuildRequires: python3-devel + +%{?systemd_requires} +BuildRequires: systemd + +Requires: pipewire >= 0.2.5 + +%description +GNOME Remote Desktop is a remote desktop and screen sharing service for the +GNOME desktop environment. + + +%prep +%autosetup -S git + + +%build +%meson +%meson_build + + +%install +%meson_install + + +%post +%systemd_user_post %{systemd_unit} + + +%preun +%systemd_user_preun %{systemd_unit} + + +%postun +%systemd_user_postun_with_restart %{systemd_unit} + + +%files +%license COPYING +%doc README +%{_libexecdir}/gnome-remote-desktop-daemon +%{_userunitdir}/gnome-remote-desktop.service +%{_datadir}/glib-2.0/schemas/org.gnome.desktop.remote-desktop.gschema.xml +%{_datadir}/glib-2.0/schemas/org.gnome.desktop.remote-desktop.enums.xml + + +%changelog +* Thu May 30 2019 Tomáš Popela - 0.1.6-5 +- Bump the version to make gating happy - that's bug 1681618 +- Resolves: rhbz#1713330 + +* Fri May 24 2019 Jonas Ådahl - 0.1.6-4 +- Backport password override test helper (rhbz#1713330) + +* Thu Jan 3 2019 Jonas Ådahl - 0.1.6-3 +- Backport various fixes (rhbz#1659118) + +* Mon Oct 1 2018 Jonas Ådahl - 0.1.6-2 +- Don't crash when PipeWire disconnects (rhbz#1627469) + +* Tue Aug 7 2018 Jonas Ådahl - 0.1.6 +- Update to 0.1.6 +- Apply ANON-TLS patch +- Depend on pipewire 0.2.2 + +* Tue Aug 29 2017 Jonas Ådahl - 0.1.2-3 +- Use %%autosetup +- Install licence file + +* Tue Aug 22 2017 Jonas Ådahl - 0.1.2-2 +- Remove gschema compilation step as that had been deprecated + +* Mon Aug 21 2017 Jonas Ådahl - 0.1.2-1 +- Update to 0.1.2 +- Changed tabs to spaces +- Added systemd user macros +- Install to correct systemd user unit directory +- Compile gsettings schemas after install and uninstall + +* Mon Aug 21 2017 Jonas Ådahl - 0.1.1-1 +- First packaged version