summaryrefslogtreecommitdiffstats
path: root/src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp')
-rw-r--r--src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp460
1 files changed, 460 insertions, 0 deletions
diff --git a/src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp b/src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp
new file mode 100644
index 00000000..168adb9b
--- /dev/null
+++ b/src/VBox/Additions/x11/VBoxClient/display-svga-session.cpp
@@ -0,0 +1,460 @@
+/* $Id: display-svga-session.cpp $ */
+/** @file
+ * Guest Additions - VMSVGA Desktop Environment user session assistant.
+ *
+ * This service connects to VBoxDRMClient IPC server, listens for
+ * its commands and reports current display offsets to it. If IPC
+ * server is not available, it forks legacy 'VBoxClient --vmsvga
+ * service and terminates.
+ */
+
+/*
+ * Copyright (C) 2017-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * 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, in version 3 of the
+ * License.
+ *
+ * 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/*
+ * This service is an IPC client for VBoxDRMClient daemon. It is also
+ * a proxy bridge to a Desktop Environment specific code (so called
+ * Desktop Environment helpers).
+ *
+ * Once started, it will try to enumerate and probe all the registered
+ * helpers and if appropriate helper found, it will forward incoming IPC
+ * commands to it as well as send helper's commands back to VBoxDRMClient.
+ * Generic helper is a special one. It will be used by default if all the
+ * other helpers are failed on probe. Moreover, generic helper provides
+ * helper functions that can be used by other helpers as well. For example,
+ * once Gnome3 Desktop Environment is running on X11, it will be also use
+ * display offsets change notification monitor of a generic helper.
+ *
+ * Multiple instances of this daemon are allowed to run in parallel
+ * with the following limitations.
+ * A single user cannot run multiple daemon instances per single TTY device,
+ * however, multiple instances are allowed for the user on different
+ * TTY devices (i.e. in case if user runs multiple X servers on different
+ * terminals). On multiple TTY devices multiple users can run multiple
+ * daemon instances (i.e. in case of "switch user" DE configuration when
+ * multiple X/Wayland servers are running on separate TTY devices).
+ */
+
+#include "VBoxClient.h"
+#include "display-ipc.h"
+#include "display-helper.h"
+
+#include <VBox/VBoxGuestLib.h>
+
+#include <iprt/localipc.h>
+#include <iprt/asm.h>
+#include <iprt/errcore.h>
+#include <iprt/path.h>
+#include <iprt/linux/sysfs.h>
+
+
+/** Handle to IPC client connection. */
+VBOX_DRMIPC_CLIENT g_hClient = VBOX_DRMIPC_CLIENT_INITIALIZER;
+
+/** IPC client handle critical section. */
+static RTCRITSECT g_hClientCritSect;
+
+/** List of available Desktop Environment specific display helpers. */
+static const VBCLDISPLAYHELPER *g_apDisplayHelpers[] =
+{
+ &g_DisplayHelperGnome3, /* GNOME3 helper. */
+ &g_DisplayHelperGeneric, /* Generic helper. */
+ NULL, /* Terminate list. */
+};
+
+/** Selected Desktop Environment specific display helper. */
+static const VBCLDISPLAYHELPER *g_pDisplayHelper = NULL;
+
+/** IPC connection session handle. */
+static RTLOCALIPCSESSION g_hSession = 0;
+
+/**
+ * Callback for display offsets change events provided by Desktop Environment specific display helper.
+ *
+ * @returns IPRT status code.
+ * @param cDisplays Number of displays which have changed offset.
+ * @param aDisplays Display data.
+ */
+static DECLCALLBACK(int) vbclSVGASessionDisplayOffsetChanged(uint32_t cDisplays, struct VBOX_DRMIPC_VMWRECT *aDisplays)
+{
+ int rc = RTCritSectEnter(&g_hClientCritSect);
+
+ if (RT_SUCCESS(rc))
+ {
+ rc = vbDrmIpcReportDisplayOffsets(&g_hClient, cDisplays, aDisplays);
+ int rc2 = RTCritSectLeave(&g_hClientCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("vbclSVGASessionDisplayOffsetChanged: unable to leave critical session, rc=%Rrc\n", rc2);
+ }
+ else
+ VBClLogError("vbclSVGASessionDisplayOffsetChanged: unable to enter critical session, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnInit}
+ */
+static DECLCALLBACK(int) vbclSVGASessionInit(void)
+{
+ int rc;
+ RTLOCALIPCSESSION hSession;
+ int idxDisplayHelper = 0;
+
+ /** Custom log prefix to be used for logger instance of this process. */
+ static const char *pszLogPrefix = "VBoxClient VMSVGA:";
+
+ VBClLogSetLogPrefix(pszLogPrefix);
+
+ rc = RTCritSectInit(&g_hClientCritSect);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to init locking, rc=%Rrc\n", rc);
+ return rc;
+ }
+
+ /* Go through list of available Desktop Environment specific helpers and try to pick up one. */
+ while (g_apDisplayHelpers[idxDisplayHelper])
+ {
+ if (g_apDisplayHelpers[idxDisplayHelper]->pfnProbe)
+ {
+ VBClLogInfo("probing Desktop Environment helper '%s'\n",
+ g_apDisplayHelpers[idxDisplayHelper]->pszName);
+
+ rc = g_apDisplayHelpers[idxDisplayHelper]->pfnProbe();
+
+ /* Found compatible helper. */
+ if (RT_SUCCESS(rc))
+ {
+ /* Initialize it. */
+ if (g_apDisplayHelpers[idxDisplayHelper]->pfnInit)
+ {
+ rc = g_apDisplayHelpers[idxDisplayHelper]->pfnInit();
+ }
+
+ /* Some helpers might have no .pfnInit(), that's ok. */
+ if (RT_SUCCESS(rc))
+ {
+ /* Subscribe to display offsets change event. */
+ if (g_apDisplayHelpers[idxDisplayHelper]->pfnSubscribeDisplayOffsetChangeNotification)
+ {
+ g_apDisplayHelpers[idxDisplayHelper]->
+ pfnSubscribeDisplayOffsetChangeNotification(
+ vbclSVGASessionDisplayOffsetChanged);
+ }
+
+ g_pDisplayHelper = g_apDisplayHelpers[idxDisplayHelper];
+ break;
+ }
+ else
+ VBClLogError("compatible Desktop Environment "
+ "helper has been found, but it cannot be initialized, rc=%Rrc\n", rc);
+ }
+ }
+
+ idxDisplayHelper++;
+ }
+
+ /* Make sure we found compatible Desktop Environment specific helper. */
+ if (g_pDisplayHelper)
+ {
+ VBClLogInfo("using Desktop Environment specific display helper '%s'\n",
+ g_pDisplayHelper->pszName);
+ }
+ else
+ {
+ VBClLogError("unable to find Desktop Environment specific display helper\n");
+ return VERR_NOT_IMPLEMENTED;
+ }
+
+ /* Attempt to connect to VBoxDRMClient IPC server. */
+ rc = RTLocalIpcSessionConnect(&hSession, VBOX_DRMIPC_SERVER_NAME, 0);
+ if (RT_SUCCESS(rc))
+ {
+ g_hSession = hSession;
+ }
+ else
+ VBClLogError("unable to connect to IPC server, rc=%Rrc\n", rc);
+
+ /* We cannot initialize ourselves, start legacy service and terminate. */
+ if (RT_FAILURE(rc))
+ {
+ /* Free helper resources. */
+ if (g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification)
+ g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification();
+
+ if (g_pDisplayHelper->pfnTerm)
+ {
+ rc = g_pDisplayHelper->pfnTerm();
+ VBClLogInfo("helper service terminated, rc=%Rrc\n", rc);
+ }
+
+ rc = VbglR3DrmLegacyClientStart();
+ VBClLogInfo("starting legacy service, rc=%Rrc\n", rc);
+ /* Force return status, so parent thread wont be trying to start worker thread. */
+ rc = VERR_NOT_AVAILABLE;
+ }
+
+ return rc;
+}
+
+/**
+ * A callback function which is triggered on IPC data receive.
+ *
+ * @returns IPRT status code.
+ * @param idCmd DRM IPC command ID.
+ * @param pvData DRM IPC command payload.
+ * @param cbData Size of DRM IPC command payload.
+ */
+static DECLCALLBACK(int) vbclSVGASessionRxCallBack(uint8_t idCmd, void *pvData, uint32_t cbData)
+{
+ VBOXDRMIPCCLTCMD enmCmd =
+ (idCmd > VBOXDRMIPCCLTCMD_INVALID && idCmd < VBOXDRMIPCCLTCMD_MAX) ?
+ (VBOXDRMIPCCLTCMD)idCmd : VBOXDRMIPCCLTCMD_INVALID;
+
+ int rc = VERR_INVALID_PARAMETER;
+
+ AssertReturn(pvData, VERR_INVALID_PARAMETER);
+ AssertReturn(cbData, VERR_INVALID_PARAMETER);
+ AssertReturn(g_pDisplayHelper, VERR_INVALID_PARAMETER);
+
+ switch (enmCmd)
+ {
+ case VBOXDRMIPCCLTCMD_SET_PRIMARY_DISPLAY:
+ {
+ if (g_pDisplayHelper->pfnSetPrimaryDisplay)
+ {
+ PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY pCmd = (PVBOX_DRMIPC_COMMAND_SET_PRIMARY_DISPLAY)pvData;
+ static uint32_t idPrimaryDisplayCached = VBOX_DRMIPC_MONITORS_MAX;
+
+ if ( pCmd->idDisplay < VBOX_DRMIPC_MONITORS_MAX
+ && idPrimaryDisplayCached != pCmd->idDisplay)
+ {
+ rc = g_pDisplayHelper->pfnSetPrimaryDisplay(pCmd->idDisplay);
+ /* Update cache. */
+ idPrimaryDisplayCached = pCmd->idDisplay;
+ }
+ else
+ VBClLogVerbose(1, "do not set %u as a primary display\n", pCmd->idDisplay);
+ }
+
+ break;
+ }
+ default:
+ {
+ VBClLogError("received unknown IPC command 0x%x\n", idCmd);
+ break;
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * Reconnect to DRM IPC server.
+ */
+static int vbclSVGASessionReconnect(void)
+{
+ int rc = VERR_GENERAL_FAILURE;
+
+ rc = RTCritSectEnter(&g_hClientCritSect);
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to enter critical section on reconnect, rc=%Rrc\n", rc);
+ return rc;
+ }
+
+ /* Check if session was not closed before. */
+ if (RT_VALID_PTR(g_hSession))
+ {
+ rc = RTLocalIpcSessionClose(g_hSession);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to release IPC connection on reconnect, rc=%Rrc\n", rc);
+
+ rc = vbDrmIpcClientReleaseResources(&g_hClient);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to release IPC session resources, rc=%Rrc\n", rc);
+ }
+
+ rc = RTLocalIpcSessionConnect(&g_hSession, VBOX_DRMIPC_SERVER_NAME, 0);
+ if (RT_SUCCESS(rc))
+ {
+ rc = vbDrmIpcClientInit(&g_hClient, RTThreadSelf(), g_hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbclSVGASessionRxCallBack);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to re-initialize IPC session, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("unable to reconnect to IPC server, rc=%Rrc\n", rc);
+
+ int rc2 = RTCritSectLeave(&g_hClientCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("unable to leave critical section on reconnect, rc=%Rrc\n", rc);
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnWorker}
+ */
+static DECLCALLBACK(int) vbclSVGASessionWorker(bool volatile *pfShutdown)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Notify parent thread that we started successfully. */
+ rc = RTThreadUserSignal(RTThreadSelf());
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to notify parent thread about successful start\n");
+
+ rc = RTCritSectEnter(&g_hClientCritSect);
+
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to enter critical section on worker start, rc=%Rrc\n", rc);
+ return rc;
+ }
+
+ rc = vbDrmIpcClientInit(&g_hClient, RTThreadSelf(), g_hSession, VBOX_DRMIPC_TX_QUEUE_SIZE, vbclSVGASessionRxCallBack);
+ int rc2 = RTCritSectLeave(&g_hClientCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("unable to leave critical section on worker start, rc=%Rrc\n", rc);
+
+ if (RT_FAILURE(rc))
+ {
+ VBClLogError("cannot initialize IPC session, rc=%Rrc\n", rc);
+ return rc;
+ }
+
+ for (;;)
+ {
+ rc = vbDrmIpcConnectionHandler(&g_hClient);
+
+ /* Try to shutdown thread as soon as possible. */
+ if (ASMAtomicReadBool(pfShutdown))
+ {
+ /* Shutdown requested. */
+ break;
+ }
+
+ /* Normal case, there was no incoming messages for a while. */
+ if (rc == VERR_TIMEOUT)
+ {
+ continue;
+ }
+ else if (RT_FAILURE(rc))
+ {
+ VBClLogError("unable to handle IPC connection, rc=%Rrc\n", rc);
+
+ /* Relax a bit before spinning the loop. */
+ RTThreadSleep(VBOX_DRMIPC_RX_RELAX_MS);
+ /* Try to reconnect to server. */
+ rc = vbclSVGASessionReconnect();
+ }
+ }
+
+ /* Check if session was not closed before. */
+ if (RT_VALID_PTR(g_hSession))
+ {
+ rc2 = RTCritSectEnter(&g_hClientCritSect);
+ if (RT_SUCCESS(rc2))
+ {
+ rc2 = vbDrmIpcClientReleaseResources(&g_hClient);
+ if (RT_FAILURE(rc2))
+ VBClLogError("cannot release IPC session resources, rc=%Rrc\n", rc2);
+
+ rc2 = RTCritSectLeave(&g_hClientCritSect);
+ if (RT_FAILURE(rc2))
+ VBClLogError("unable to leave critical section on worker end, rc=%Rrc\n", rc);
+ }
+ else
+ VBClLogError("unable to enter critical section on worker end, rc=%Rrc\n", rc);
+ }
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnStop}
+ */
+static DECLCALLBACK(void) vbclSVGASessionStop(void)
+{
+ int rc;
+
+ /* Check if session was not closed before. */
+ if (!RT_VALID_PTR(g_hSession))
+ return;
+
+ /* Attempt to release any waiting syscall related to RTLocalIpcSessionXXX(). */
+ rc = RTLocalIpcSessionFlush(g_hSession);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to flush data to IPC connection, rc=%Rrc\n", rc);
+
+ rc = RTLocalIpcSessionCancel(g_hSession);
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to cancel IPC session, rc=%Rrc\n", rc);
+}
+
+/**
+ * @interface_method_impl{VBCLSERVICE,pfnTerm}
+ */
+static DECLCALLBACK(int) vbclSVGASessionTerm(void)
+{
+ int rc = VINF_SUCCESS;
+
+ if (g_hSession)
+ {
+ rc = RTLocalIpcSessionClose(g_hSession);
+ g_hSession = 0;
+
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to close IPC connection, rc=%Rrc\n", rc);
+ }
+
+ if (g_pDisplayHelper)
+ {
+ if (g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification)
+ g_pDisplayHelper->pfnUnsubscribeDisplayOffsetChangeNotification();
+
+ if (g_pDisplayHelper->pfnTerm)
+ {
+ rc = g_pDisplayHelper->pfnTerm();
+ if (RT_FAILURE(rc))
+ VBClLogError("unable to terminate Desktop Environment helper '%s', rc=%Rrc\n",
+ rc, g_pDisplayHelper->pszName);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+VBCLSERVICE g_SvcDisplaySVGASession =
+{
+ "vmsvga-session", /* szName */
+ "VMSVGA display assistant", /* pszDescription */
+ ".vboxclient-vmsvga-session", /* pszPidFilePathTemplate */
+ NULL, /* pszUsage */
+ NULL, /* pszOptions */
+ NULL, /* pfnOption */
+ vbclSVGASessionInit, /* pfnInit */
+ vbclSVGASessionWorker, /* pfnWorker */
+ vbclSVGASessionStop, /* pfnStop */
+ vbclSVGASessionTerm, /* pfnTerm */
+};