/* * Copyright (C) 2015 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. * * Written by: * Jonas Ã…dahl */ #include "config.h" #include "grd-session-vnc.h" #include #include #include #include #include "grd-clipboard-vnc.h" #include "grd-context.h" #include "grd-prompt.h" #include "grd-settings.h" #include "grd-stream.h" #include "grd-utils.h" #include "grd-vnc-server.h" #include "grd-vnc-pipewire-stream.h" /* BGRx */ #define BGRX_BITS_PER_SAMPLE 8 #define BGRX_SAMPLES_PER_PIXEL 3 #define BGRX_BYTES_PER_PIXEL 4 struct _GrdSessionVnc { GrdSession parent; GSocketConnection *connection; GSource *source; rfbScreenInfoPtr rfb_screen; rfbClientPtr rfb_client; gboolean pending_framebuffer_resize; int pending_framebuffer_width; int pending_framebuffer_height; guint close_session_idle_id; GrdVncAuthMethod auth_method; GrdPrompt *prompt; GCancellable *prompt_cancellable; GrdVncPipeWireStream *pipewire_stream; GrdStream *stream; int prev_x; int prev_y; int prev_button_mask; GHashTable *pressed_keys; GrdClipboardVnc *clipboard_vnc; GrdVncScreenShareMode screen_share_mode; gboolean is_view_only; GrdVncMonitorConfig *monitor_config; }; G_DEFINE_TYPE (GrdSessionVnc, grd_session_vnc, GRD_TYPE_SESSION) static void grd_session_vnc_detach_source (GrdSessionVnc *session_vnc); static gboolean close_session_idle (gpointer user_data); static rfbBool check_rfb_password (rfbClientPtr rfb_client, const char *response_encrypted, int len); static void swap_uint8 (uint8_t *a, uint8_t *b) { uint8_t tmp; tmp = *a; *a = *b; *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, int height) { rfbScreenInfoPtr rfb_screen = session_vnc->rfb_screen; uint8_t *framebuffer; if (!session_vnc->rfb_client->useNewFBSize) g_warning ("Client does not support NewFBSize"); g_free (rfb_screen->frameBuffer); framebuffer = g_malloc0 (width * height * BGRX_BYTES_PER_PIXEL); rfbNewFramebuffer (rfb_screen, (char *) framebuffer, width, height, BGRX_BITS_PER_SAMPLE, BGRX_SAMPLES_PER_PIXEL, BGRX_BYTES_PER_PIXEL); update_server_format (session_vnc); rfb_screen->setTranslateFunction (session_vnc->rfb_client); } void grd_session_vnc_queue_resize_framebuffer (GrdSessionVnc *session_vnc, int width, int height) { rfbScreenInfoPtr rfb_screen = session_vnc->rfb_screen; if (rfb_screen->width == width && rfb_screen->height == height) return; if (session_vnc->rfb_client->preferredEncoding == -1) { session_vnc->pending_framebuffer_resize = TRUE; session_vnc->pending_framebuffer_width = width; session_vnc->pending_framebuffer_height = height; return; } resize_vnc_framebuffer (session_vnc, width, height); } void grd_session_vnc_take_buffer (GrdSessionVnc *session_vnc, void *data) { if (session_vnc->pending_framebuffer_resize) { free (data); return; } free (session_vnc->rfb_screen->frameBuffer); session_vnc->rfb_screen->frameBuffer = data; rfbMarkRectAsModified (session_vnc->rfb_screen, 0, 0, session_vnc->rfb_screen->width, session_vnc->rfb_screen->height); rfbProcessEvents (session_vnc->rfb_screen, 0); } void grd_session_vnc_flush (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; } void grd_session_vnc_set_client_clipboard_text (GrdSessionVnc *session_vnc, char *text, int text_length) { rfbSendServerCutText(session_vnc->rfb_screen, text, text_length); } static void grd_vnc_monitor_config_free (GrdVncMonitorConfig *monitor_config) { g_clear_pointer (&monitor_config->connectors, g_strfreev); g_free (monitor_config->virtual_monitors); g_free (monitor_config); } 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); } gboolean grd_session_vnc_is_client_gone (GrdSessionVnc *session_vnc) { return !session_vnc->rfb_client; } static void handle_client_gone (rfbClientPtr rfb_client) { GrdSessionVnc *session_vnc = rfb_client->screen->screenData; g_debug ("VNC client gone"); grd_session_vnc_detach_source (session_vnc); maybe_queue_close_session_idle (session_vnc); session_vnc->rfb_client = NULL; } static void prompt_response_callback (GObject *source_object, GAsyncResult *async_result, gpointer user_data) { GrdSessionVnc *session_vnc = GRD_SESSION_VNC (user_data); g_autoptr(GError) error = NULL; GrdPromptResponse response; if (!grd_prompt_query_finish (session_vnc->prompt, async_result, &response, &error)) { if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) { g_warning ("Failed to query user about session: %s", error->message); rfbRefuseOnHoldClient (session_vnc->rfb_client); return; } else { return; } } switch (response) { case GRD_PROMPT_RESPONSE_ACCEPT: grd_session_start (GRD_SESSION (session_vnc)); rfbStartOnHoldClient (session_vnc->rfb_client); return; case GRD_PROMPT_RESPONSE_CANCEL: rfbRefuseOnHoldClient (session_vnc->rfb_client); return; } g_assert_not_reached (); } static void show_sharing_desktop_prompt (GrdSessionVnc *session_vnc, const char *host) { g_autoptr (GrdPromptDefinition) prompt_definition = NULL; session_vnc->prompt = g_object_new (GRD_TYPE_PROMPT, NULL); session_vnc->prompt_cancellable = g_cancellable_new (); prompt_definition = g_new0 (GrdPromptDefinition, 1); prompt_definition->summary = g_strdup_printf (_("Do you want to share your desktop?")); prompt_definition->body = g_strdup_printf (_("A user on the computer '%s' is trying to remotely " "view or control your desktop."), host); prompt_definition->cancel_label = g_strdup_printf (_("Refuse")); prompt_definition->accept_label = g_strdup_printf (_("Accept")); grd_prompt_query_async (session_vnc->prompt, prompt_definition, session_vnc->prompt_cancellable, prompt_response_callback, session_vnc); } static enum rfbNewClientAction handle_new_client (rfbClientPtr rfb_client) { GrdSessionVnc *session_vnc = GRD_SESSION_VNC (rfb_client->screen->screenData); GrdContext *context = grd_session_get_context (GRD_SESSION (session_vnc)); GrdSettings *settings = grd_context_get_settings (context); g_debug ("New VNC client"); g_object_get (G_OBJECT (settings), "vnc-auth-method", &session_vnc->auth_method, NULL); session_vnc->rfb_client = rfb_client; rfb_client->clientGoneHook = handle_client_gone; switch (session_vnc->auth_method) { case GRD_VNC_AUTH_METHOD_PROMPT: show_sharing_desktop_prompt (session_vnc, rfb_client->host); grd_session_vnc_detach_source (session_vnc); return RFB_CLIENT_ON_HOLD; case GRD_VNC_AUTH_METHOD_PASSWORD: session_vnc->rfb_screen->passwordCheck = check_rfb_password; /* * authPasswdData needs to be non NULL in libvncserver to trigger * password authentication. */ session_vnc->rfb_screen->authPasswdData = (gpointer) 1; return RFB_CLIENT_ACCEPT; } g_assert_not_reached (); } static void handle_key_event (rfbBool down, rfbKeySym keysym, rfbClientPtr rfb_client) { GrdSessionVnc *session_vnc = GRD_SESSION_VNC (rfb_client->screen->screenData); GrdSession *session = GRD_SESSION (session_vnc); if (session_vnc->is_view_only) return; if (down) { if (g_hash_table_add (session_vnc->pressed_keys, GUINT_TO_POINTER (keysym))) { grd_session_notify_keyboard_keysym (session, keysym, GRD_KEY_STATE_PRESSED); } } else { if (g_hash_table_remove (session_vnc->pressed_keys, GUINT_TO_POINTER (keysym))) { grd_session_notify_keyboard_keysym (session, keysym, GRD_KEY_STATE_RELEASED); } } } static gboolean notify_keyboard_key_released (gpointer key, gpointer value, gpointer user_data) { GrdSession *session = user_data; uint32_t keysym = GPOINTER_TO_UINT (key); grd_session_notify_keyboard_keysym (session, keysym, GRD_KEY_STATE_RELEASED); return TRUE; } static void handle_release_all_keys (rfbClientPtr rfb_client) { GrdSessionVnc *session_vnc = rfb_client->screen->screenData; g_hash_table_foreach_remove (session_vnc->pressed_keys, notify_keyboard_key_released, session_vnc); } static void handle_set_clipboard_text (char *text, int text_length, rfbClientPtr rfb_client) { GrdSessionVnc *session_vnc = rfb_client->screen->screenData; GrdClipboardVnc *clipboard_vnc = session_vnc->clipboard_vnc; grd_clipboard_vnc_set_clipboard_text (clipboard_vnc, text, text_length); } static void grd_session_vnc_notify_axis (GrdSessionVnc *session_vnc, int button_mask_bit_index) { GrdSession *session = GRD_SESSION (session_vnc); GrdPointerAxis axis; int steps; switch (button_mask_bit_index) { case 3: axis = GRD_POINTER_AXIS_VERTICAL; steps = -1; break; case 4: axis = GRD_POINTER_AXIS_VERTICAL; steps = 1; break; case 5: axis = GRD_POINTER_AXIS_HORIZONTAL; steps = -1; break; case 6: axis = GRD_POINTER_AXIS_HORIZONTAL; steps = 1; break; default: return; } grd_session_notify_pointer_axis_discrete (session, axis, steps); } static void handle_pointer_event (int button_mask, int x, int y, rfbClientPtr rfb_client) { GrdSessionVnc *session_vnc = rfb_client->screen->screenData; GrdSession *session = GRD_SESSION (session_vnc); if (session_vnc->is_view_only) return; if (session_vnc->stream && (x != session_vnc->prev_x || y != session_vnc->prev_y)) { rfbScreenInfoPtr rfb_screen = session_vnc->rfb_screen; GrdEventMotionAbs motion_abs = {}; motion_abs.input_rect_width = rfb_screen->width; motion_abs.input_rect_height = rfb_screen->height; motion_abs.x = x; motion_abs.y = y; grd_session_notify_pointer_motion_absolute (session, session_vnc->stream, &motion_abs); session_vnc->prev_x = x; session_vnc->prev_y = y; } if (button_mask != session_vnc->prev_button_mask) { unsigned int i; int buttons[] = { BTN_LEFT, /* 0 */ BTN_MIDDLE, /* 1 */ BTN_RIGHT, /* 2 */ 0, /* 3 - vertical scroll: up */ 0, /* 4 - vertical scroll: down */ 0, /* 5 - horizontal scroll: left */ 0, /* 6 - horizontal scroll: right */ BTN_SIDE, /* 7 */ BTN_EXTRA, /* 8 */ }; for (i = 0; i < G_N_ELEMENTS (buttons); i++) { int button = buttons[i]; int prev_button_state = (session_vnc->prev_button_mask >> i) & 0x01; int cur_button_state = (button_mask >> i) & 0x01; if (prev_button_state != cur_button_state) { if (button) { grd_session_notify_pointer_button (session, button, cur_button_state); } else { grd_session_vnc_notify_axis (session_vnc, i); } } } session_vnc->prev_button_mask = button_mask; } rfbDefaultPtrAddEvent (button_mask, x, y, rfb_client); } static int handle_set_desktop_size (int width, int height, int num_screens, struct rfbExtDesktopScreen *screens, rfbClientPtr rfb_client) { GrdSessionVnc *session_vnc = rfb_client->screen->screenData; GrdVncVirtualMonitor *monitor; if (!session_vnc->monitor_config->is_virtual) { g_warning ("[VNC] Ignoring SetDesktopSize request, mirror-primary mode is used"); return rfbExtDesktopSize_ResizeProhibited; } g_debug ("[VNC] Trying to set new size %dx%d", width, height); g_assert (session_vnc->monitor_config->monitor_count == 1); g_assert (session_vnc->monitor_config->virtual_monitors); monitor = session_vnc->monitor_config->virtual_monitors; monitor->width = GRD_VNC_CLAMP_DESKTOP_SIZE (width); monitor->height = GRD_VNC_CLAMP_DESKTOP_SIZE (height); grd_vnc_pipewire_stream_resize (session_vnc->pipewire_stream, monitor); return rfbExtDesktopSize_Success; } static rfbBool check_rfb_password (rfbClientPtr rfb_client, const char *response_encrypted, int len) { GrdSessionVnc *session_vnc = rfb_client->screen->screenData; GrdContext *context = grd_session_get_context (GRD_SESSION (session_vnc)); GrdSettings *settings = grd_context_get_settings (context); g_autofree char *password = NULL; g_autoptr(GError) error = NULL; uint8_t challenge_encrypted[CHALLENGESIZE]; switch (session_vnc->auth_method) { case GRD_VNC_AUTH_METHOD_PROMPT: return TRUE; case GRD_VNC_AUTH_METHOD_PASSWORD: break; } password = grd_settings_get_vnc_password (settings, &error); if (!password) { if (error) g_warning ("[VNC] Couldn't retrieve VNC password: %s", error->message); else g_message ("[VNC] Password is not set, denying client"); return FALSE; } if (strlen (password) == 0) { g_warning ("VNC password was empty, denying"); return FALSE; } memcpy(challenge_encrypted, rfb_client->authChallenge, CHALLENGESIZE); rfbEncryptBytes(challenge_encrypted, password); if (memcmp (challenge_encrypted, response_encrypted, len) == 0) { grd_session_start (GRD_SESSION (session_vnc)); grd_session_vnc_detach_source (session_vnc); return TRUE; } else { return FALSE; } } int grd_session_vnc_get_stride_for_width (GrdSessionVnc *session_vnc, int width) { return width * BGRX_BYTES_PER_PIXEL; } static void init_vnc_session (GrdSessionVnc *session_vnc) { GSocket *socket; int screen_width; int screen_height; rfbScreenInfoPtr rfb_screen; GrdVncMonitorConfig *monitor_config; /* Arbitrary framebuffer size, will get the proper size from the stream. */ screen_width = GRD_VNC_DEFAULT_WIDTH; screen_height = GRD_VNC_DEFAULT_HEIGHT; rfb_screen = rfbGetScreen (0, NULL, screen_width, screen_height, 8, 3, 4); session_vnc->rfb_screen = rfb_screen; update_server_format (session_vnc); session_vnc->clipboard_vnc = grd_clipboard_vnc_new (session_vnc); socket = g_socket_connection_get_socket (session_vnc->connection); rfb_screen->inetdSock = g_socket_get_fd (socket); rfb_screen->desktopName = "GNOME"; rfb_screen->versionString = "GNOME Remote Desktop (VNC)"; rfb_screen->neverShared = TRUE; rfb_screen->newClientHook = handle_new_client; rfb_screen->screenData = session_vnc; /* Amount of milliseconds to wait before sending an update */ rfb_screen->deferUpdateTime = 0; rfb_screen->kbdAddEvent = handle_key_event; rfb_screen->kbdReleaseAllKeys = handle_release_all_keys; rfb_screen->setXCutText = handle_set_clipboard_text; rfb_screen->ptrAddEvent = handle_pointer_event; rfb_screen->setDesktopSizeHook = handle_set_desktop_size; rfb_screen->frameBuffer = g_malloc0 (screen_width * screen_height * 4); memset (rfb_screen->frameBuffer, 0x1f, screen_width * screen_height * 4); session_vnc->monitor_config = g_new0 (GrdVncMonitorConfig, 1); monitor_config = session_vnc->monitor_config; monitor_config->virtual_monitors = NULL; /* No multi-monitor support yet */ monitor_config->monitor_count = 1; if (session_vnc->screen_share_mode == GRD_VNC_SCREEN_SHARE_MODE_EXTEND) { monitor_config->is_virtual = TRUE; monitor_config->virtual_monitors = g_new0 (GrdVncVirtualMonitor, 1); monitor_config->virtual_monitors->pos_x = 0; monitor_config->virtual_monitors->pos_y = 0; monitor_config->virtual_monitors->width = GRD_VNC_CLAMP_DESKTOP_SIZE (screen_width); monitor_config->virtual_monitors->height = GRD_VNC_CLAMP_DESKTOP_SIZE (screen_height); } else { monitor_config->is_virtual = FALSE; g_autoptr (GStrvBuilder) connector_builder = NULL; char **connectors; connector_builder = g_strv_builder_new (); g_strv_builder_add (connector_builder, ""); connectors = g_strv_builder_end (connector_builder); session_vnc->monitor_config->connectors = connectors; } rfbInitServer (rfb_screen); rfbProcessEvents (rfb_screen, 0); } static gboolean handle_socket_data (GSocket *socket, GIOCondition condition, gpointer user_data) { GrdSessionVnc *session_vnc = user_data; if (condition & G_IO_IN) { 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; /** * This is a workaround. libvncserver is unable to handle clipboard * changes early and either disconnects the client or crashes g-r-d * if it receives rfbSendServerCutText too early altough the * authentification process is already done. * Doing this after resizing the framebuffer, seems to work fine, * so enable the clipboard here and not when the remote desktop * session proxy is acquired. */ grd_clipboard_vnc_maybe_enable_clipboard (session_vnc->clipboard_vnc); } } } else { g_debug ("Unhandled socket condition %d\n", condition); return G_SOURCE_REMOVE; } return G_SOURCE_CONTINUE; } static void grd_session_vnc_attach_source (GrdSessionVnc *session_vnc) { GSocket *socket; socket = g_socket_connection_get_socket (session_vnc->connection); session_vnc->source = g_socket_create_source (socket, G_IO_IN | G_IO_PRI, NULL); g_source_set_callback (session_vnc->source, (GSourceFunc) handle_socket_data, session_vnc, NULL); g_source_attach (session_vnc->source, NULL); } static void grd_session_vnc_detach_source (GrdSessionVnc *session_vnc) { if (!session_vnc->source) return; g_source_destroy (session_vnc->source); g_clear_pointer (&session_vnc->source, g_source_unref); } static void on_view_only_changed (GrdSettings *settings, GParamSpec *pspec, GrdSessionVnc *session_vnc) { g_object_get (G_OBJECT (settings), "vnc-view-only", &session_vnc->is_view_only, NULL); } GrdSessionVnc * grd_session_vnc_new (GrdVncServer *vnc_server, GSocketConnection *connection) { GrdSessionVnc *session_vnc; GrdContext *context; GrdSettings *settings; context = grd_vnc_server_get_context (vnc_server); session_vnc = g_object_new (GRD_TYPE_SESSION_VNC, "context", context, NULL); session_vnc->connection = g_object_ref (connection); settings = grd_context_get_settings (context); g_object_get (G_OBJECT (settings), "vnc-screen-share-mode", &session_vnc->screen_share_mode, "vnc-view-only", &session_vnc->is_view_only, NULL); g_signal_connect (settings, "notify::vnc-view-only", G_CALLBACK (on_view_only_changed), session_vnc); grd_session_vnc_attach_source (session_vnc); init_vnc_session (session_vnc); return session_vnc; } static void grd_session_vnc_dispose (GObject *object) { GrdSessionVnc *session_vnc = GRD_SESSION_VNC (object); g_assert (!session_vnc->rfb_screen); g_clear_pointer (&session_vnc->pressed_keys, g_hash_table_unref); G_OBJECT_CLASS (grd_session_vnc_parent_class)->dispose (object); } static void grd_session_vnc_stop (GrdSession *session) { GrdSessionVnc *session_vnc = GRD_SESSION_VNC (session); g_debug ("Stopping VNC session"); g_clear_object (&session_vnc->pipewire_stream); if (session_vnc->stream) { grd_stream_disconnect_proxy_signals (session_vnc->stream); g_clear_object (&session_vnc->stream); } grd_session_vnc_detach_source (session_vnc); grd_close_connection_and_notify (session_vnc->connection); g_clear_object (&session_vnc->connection); g_clear_object (&session_vnc->clipboard_vnc); g_clear_pointer (&session_vnc->rfb_screen->frameBuffer, g_free); g_clear_pointer (&session_vnc->rfb_screen, rfbScreenCleanup); g_clear_pointer (&session_vnc->monitor_config, grd_vnc_monitor_config_free); if (session_vnc->prompt_cancellable) { g_cancellable_cancel (session_vnc->prompt_cancellable); g_clear_object (&session_vnc->prompt_cancellable); } g_clear_object (&session_vnc->prompt); g_clear_handle_id (&session_vnc->close_session_idle_id, g_source_remove); } static gboolean close_session_idle (gpointer user_data) { GrdSessionVnc *session_vnc = GRD_SESSION_VNC (user_data); grd_session_stop (GRD_SESSION (session_vnc)); session_vnc->close_session_idle_id = 0; return G_SOURCE_REMOVE; } static void on_remote_desktop_session_started (GrdSession *session) { GrdSessionVnc *session_vnc = GRD_SESSION_VNC (session); if (session_vnc->monitor_config->is_virtual) g_debug ("[VNC] Remote Desktop session will use virtual monitors"); else g_debug ("[VNC] Remote Desktop session will mirror the primary monitor"); /* Not supporting multi-monitor at the moment */ g_assert (session_vnc->monitor_config->monitor_count == 1); if (session_vnc->monitor_config->is_virtual) { grd_session_record_virtual (session, 0, GRD_SCREEN_CAST_CURSOR_MODE_METADATA, TRUE); } else { const char *connector; connector = session_vnc->monitor_config->connectors[0]; grd_session_record_monitor (session, 0, connector, GRD_SCREEN_CAST_CURSOR_MODE_METADATA); } } static void on_pipewire_stream_closed (GrdVncPipeWireStream *stream, GrdSessionVnc *session_vnc) { g_warning ("PipeWire stream closed, closing client"); maybe_queue_close_session_idle (session_vnc); } static void on_stream_ready (GrdStream *stream, GrdSessionVnc *session_vnc) { uint32_t pipewire_node_id; GrdVncVirtualMonitor *virtual_monitor; g_autoptr (GError) error = NULL; virtual_monitor = session_vnc->monitor_config->virtual_monitors; pipewire_node_id = grd_stream_get_pipewire_node_id (stream); session_vnc->pipewire_stream = grd_vnc_pipewire_stream_new (session_vnc, pipewire_node_id, virtual_monitor, &error); if (!session_vnc->pipewire_stream) { g_warning ("Failed to establish PipeWire stream: %s", error->message); return; } g_signal_connect (session_vnc->pipewire_stream, "closed", G_CALLBACK (on_pipewire_stream_closed), session_vnc); if (!session_vnc->source) grd_session_vnc_attach_source (session_vnc); } static void grd_session_vnc_on_stream_created (GrdSession *session, uint32_t stream_id, GrdStream *stream) { GrdSessionVnc *session_vnc = GRD_SESSION_VNC (session); g_assert (!session_vnc->stream); session_vnc->stream = stream; g_signal_connect (stream, "ready", G_CALLBACK (on_stream_ready), session_vnc); } static void grd_session_vnc_init (GrdSessionVnc *session_vnc) { session_vnc->pressed_keys = g_hash_table_new (NULL, NULL); g_signal_connect (session_vnc, "started", G_CALLBACK (on_remote_desktop_session_started), NULL); } static void grd_session_vnc_class_init (GrdSessionVncClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GrdSessionClass *session_class = GRD_SESSION_CLASS (klass); object_class->dispose = grd_session_vnc_dispose; session_class->stop = grd_session_vnc_stop; session_class->on_stream_created = grd_session_vnc_on_stream_created; }